Documentation
¶
Overview ¶
blacklist.go —— 黑名单硬规则与命令类判据。
职责:
- 持有内置黑名单模式表(自 internal/agentd/approver.go 迁入)
- 引号字面量剥离与执行包装器识别
- judgeCommand:命令类请求(bash 及其余工具)的判定
边界:
- 不认识工具名、不碰路径:路由在 permgate.go,路径判定在 path.go
path.go —— 写文件目标路径的范围归属判定。
职责:
- 把可能是相对路径、可能经软链的目标路径归一化为真实绝对路径
- 判定它是否落在任务范围(Workdir 或 TaskDir)的子树内
边界:
- 只读文件系统(EvalSymlinks 探测),不创建、不修改任何东西
- 不认识工具名、不做黑名单匹配
已知残余风险(TOCTOU):判定通过后、executor 实际写入前,软链可能被换掉。 闭合它需要在 executor 侧持有文件句柄,而写入动作发生在 agent 进程里, 超出 handoff 的可控范围——spec §5.4 明确接受此风险。
Package permgate 提供权限请求的结构化判据。
职责:
- 把一次权限请求判成三个出口之一:AutoAllow(立即放行)、Consult (交廉价模型审批者)、Escalate(直接升级人工协调者)
- 承载全部判定规则:黑名单模式匹配、引号剥离、执行包装器识别、 写文件目标路径的范围归属
边界:
- 纯计算,无 I/O:不写 store、不碰 adapter、不发网络请求(EvalSymlinks 的文件系统只读探测除外,它是路径判定的必需品)
- 无 deny 权:出口里没有「拒绝」——拒绝只有人能做,与 approver 同源
- 不做状态迁移、不建工单:调用方(manager)据 Verdict 决定后续动作
为什么判据要独立成包:三个 adapter(claude/grok/opencode)的权限载荷形态 完全不同,但判据必须只有一份——判据分散到 adapter 里,就会重演「opencode 有 external_directory、claude 和 grok 没有」这种各家一套的漂移。
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HasExecWrapper ¶
HasExecWrapper 判断命令是否含执行包装器(sh -c / bash -c / eval / xargs / 通用 -c/-e/-E 执行标志等)。
func InScope ¶
InScope 判定目标路径是否落在任务范围内。
参数:
- path: 目标路径,可为相对路径(按 scope.Workdir 解析)
- scope: 任务范围;其中为空的基准目录被跳过,不参与判定
返回:
- in: 是否落在范围内
- base: in=true 时命中的基准目录(归一化后),供日志说明「凭哪条放行」
- err: 路径归一化失败;调用方须按 fail-closed 处理为升级人工
注意:
- 用 filepath.Rel 判归属而非字符串前缀——strings.HasPrefix("/repo-evil/x", "/repo") 为真,前缀匹配会把仓库外的路径判成内部
- 对已存在的最长前缀求 EvalSymlinks——目标文件常常尚不存在(Write 新建), 不解软链则 `ln -s ~ /repo/link` 之后写 /repo/link/.ssh/authorized_keys 直接绕过
func StripQuoted ¶
StripQuoted 把成对引号内的内容清空,保留引号本身。
参数:s 为原始命令串 返回:剥离后的串,如 `git commit -m "去掉 rm -rf 分支"` → `git commit -m ""`
注意:
- 不做完整 shell 解析。反斜杠转义(`"it\"s"`)会让本函数提前认为引号闭合, 结果是**剥得更少**——剥得少意味着更可能仍然命中黑名单、更可能 Escalate, 方向是安全的,因此接受
- 未闭合引号:引号之后的内容全部丢弃
Types ¶
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate 持有编译后的黑名单,是判据的唯一入口。
func New ¶
New 构造判据网关。
参数:
- patterns: 用户自定义黑名单(config.ApproverConfig.Blacklist);内置 黑名单自动前置,无需调用方传入
- log: 包日志入口;nil 时用 slog.Default()
返回:
- 可用的 Gate;任一正则编译失败即返回错误(配置错误应在启动期暴露)
注意:
- **无论审批者是否启用都必须构造**。AutoAllow 是第 0 层静态判据、不是 审批者的职权;漏构造会让未配置审批者的部署被工作区内的每次写入淹没
func (*Gate) Judge ¶
Judge 判定一次权限请求(spec §3.4 的路由 + §7 的 fail-closed 表)。
参数:
- req: 结构化后的权限请求
- scope: 本任务的合法作用范围
返回:Verdict,调用方据 Action 决定后续动作
路由:
- write / edit → 路径归属判定,**并且**对 Text 跑一次黑名单(路径本身 可能命中,如 Write: /etc/sudoers);两项是与关系
- bash → 对 Command 做命令类判定
- 其余 → 对 Text 做命令类判定
注意:
- Truncated 一律直接 Escalate 且不再往下判:看到的是不完整的描述, 危险片段可能落在截断之外,黑名单与模型都不可信
- 本方法**永不因失败而返回 AutoAllow**(spec §7)
type Request ¶
type Request struct {
Tool string // 归一化工具名,取 executor.PermTool* 常量
Text string // 权限描述全文(与工单同源)
Command string // Tool=bash 时的完整命令串
Paths []string // Tool=write|edit 时的目标路径(可为相对路径)
Truncated bool // 描述含 executor.TruncationMarker
}
Request 是结构化后的权限请求。
Text 与 Command 不是重复:Text 是给人看的全文(与工单同源,形如 "Bash: xxx"),Command 是命令类工具的纯命令串。bash 路由判 Command, 其余路由退回判 Text。