权限与安全
Chord 是一个可读取文件、修改文件、执行命令并调用外部工具的 coding agent。执行前请检查审批内容;权限规则是风险控制,不是操作系统级沙箱。
- 规则如何判定:核心原则、权限模型。
- 放行命令之前:Shell 与 shell 风险:
shell是你能授予的最大能力。 - 放行写文件之前:文件修改风险。
- 凭据与对外接口:凭据与配置、Headless 模式安全边界、网络与外部集成。
- 默认把高风险能力设为
ask - 对明显危险或不需要的能力使用
deny - 仅对低风险、可预期的动作使用
allow - 把 API keys 放在
auth.yaml或环境变量里,不要写进项目文件
常见权限状态:
allow:自动允许ask:执行前要求确认deny:直接拒绝
规则按工具名匹配;内置工具名的完整清单见内置工具。
规则指名了不存在的工具时匹配不到任何东西,因此拼错工具名会让规则静默失效,而不是报错。后台工作通过 shell(配合 run_in_background: true)加 job_output、job_list、job_kill 工具完成。
在 TUI 确认框中,按 V 可以只读查看完整工具参数,包括摘要预览未显示的条目;关闭查看器后回到待确认状态,查看不会批准调用或修改参数。按 E 可以修改参数。
在 TUI 确认框中,M 用于打开当前工具调用的「添加规则」选择界面;进入该界面后,再按 Enter 才会保存所选规则并允许这次调用。对于 delete,选择器不会提供复用价值很低的单文件规则,而是按覆盖本次待审批目标的数量优先列出父目录规则;候选 pattern 采用权限匹配的同一套归一化写法,保存后从其他 checkout 也会命中同一个文件。全局 *(任意删除路径)始终保留;本次请求的所有目标都位于仓库内时,还会提供 **(仓库内任意路径)。这两个宽规则都不会默认选中。
权限可在 Agent 配置中定义。推荐从下面这套个人开发模板开始,再按项目风险收紧或放宽:
permission: "*": allow handoff: deny delegate: deny delete: ask web_fetch: "localhost:8000": ask "169.254.0.0/16": deny "10.0.0.0/8": deny "192.168.0.0/16": deny shell: "sudo *": ask "rm *": ask "rmdir *": ask "mv *": ask "git add *": ask "git checkout *": ask "git clean *": ask "git commit *": ask "git push *": ask "git reset *": ask "git restore *": ask "git tag *": ask这套配置的含义:默认允许大多数工具;禁用 handoff 与 delegate;删除文件、选定的 WebFetch URL pattern、以及常见高风险 shell/git 命令需要确认。权限规则按「最后匹配优先」生效,因此 web_fetch 和 shell 下更具体的规则会覆盖顶层 "*": allow。适合单人、可信工作区;共享仓库、团队服务或自动化 headless 部署应进一步收紧。本页以 "*": allow 作为可信工作区基线;若想改用最小授权基线,配置:Agent 配置中的 builder agent 从 "*": deny 起步,只对角色确需的工具逐项放开。
权限匹配会看工具调用与会话所在的仓库。shell 只匹配命令字符串,workdir 参数不参与。文件类工具(read、write、edit、apply_patch、delete、view_image)的目标路径会先归一化,再与规则匹配:位于当前仓库任一 checkout 内的路径按仓库相对形式匹配(如 src/main.go),因此 foo.go、./foo.go、internal/../foo.go 以及同一文件的绝对拼写都会命中同一条规则,也与当前位于哪个 checkout 或子目录无关;各 checkout 之外的路径保持绝对形式。无法识别仓库时(普通目录,或没有 git 二进制),由会话工作目录顶替仓库的位置。
文件类规则的生效范围由写法决定:
*匹配一切路径拼写,表示「任意路径」。- 相对写法(
**、src/**、tmp/*)只匹配归一化后的相对路径,即上面作用域内的路径。**表示「仓库内任意路径」,覆盖所有 checkout;无法识别仓库时表示「工作目录下任意路径」。./**也按同一语义处理。 - 绝对写法(
/Users/me/other/**、~/other/**、/**)只匹配各 checkout 之外的路径。/**表示「所有绝对路径」,和**合起来覆盖的范围相当于*。Windows 上 home 相对写法两种分隔符都可以用(~\other\**或~/other/**)。 - 绝对写法不会命中仓库内的文件;要限制仓库内路径,请改用相对写法。
下面这套配置允许删除仓库内和系统临时目录下的任意文件,而 .git、.chord 以及任何 AGENTS.md 仍需确认:
permission: delete: "*": ask "**": allow "/tmp/*": allow "/private/tmp/*": allow ".git": ask ".git/*": ask "**/.git": ask "**/.git/*": ask ".chord": ask ".chord/*": ask "**/.chord": ask "**/.chord/*": ask "AGENTS.md": ask "**/AGENTS.md": ask权限规则按「最后匹配优先」生效,因此宽泛的 ** 放行写在前面,后面更窄的规则把受保护路径收回确认。仓库外的路径保留调用方使用的拼写,所以 /tmp/* 与 /private/tmp/* 都要列出:macOS 上 /tmp 是指向 /private/tmp 的符号链接,只有拼写一致的那条规则才会命中。
shell 规则只约束提交的命令字符串,并不是文件系统沙箱。放行的命令仍可在内部 cd 到别处、调用其他程序或操作绝对路径。审批策略应使用尽量窄的 shell pattern;若要真正限制文件系统范围,还需使用操作系统级沙箱。
WebFetch 目标匹配
Section titled “WebFetch 目标匹配”web_fetch 的规则 pattern 按网络语义匹配主机,形如 host[:port]:
- host(主机):域名(
example.com)、域名通配(*.internal、*)、单个 IP(127.0.0.1、::1),或 CIDR 网段(10.0.0.0/8、169.254.0.0/16、fd00::/8)。IPv6 地址或 IPv6 CIDR 指定端口时需要方括号,例如[fd00::/8]:443。 - port(端口):省略或写
*表示任意;可以是单个端口(8080)或范围(8000-9000)。请求 URL 未写端口时按其协议取默认(http→80、https→443)。 - 不支持 scheme(协议)和 path(路径)pattern。
web_fetch: "*": allow # 默认放行一切 "0.0.0.0/8": deny "10.0.0.0/8": deny "127.0.0.0/8": deny "169.254.0.0/16": deny # 云元数据 endpoint "192.168.0.0/16": deny "*:8000-9000": ask # 任意主机的这些端口需确认 "*.internal": deny # 内网域名匹配发生在请求发出之前,针对模型给出的 URL;不会按解析后的连接 IP 复检。因此域名解析到内网地址、或 HTTP 重定向到内网,都不会被 IP/CIDR 规则拦住。请把这类规则视为意图层面的管控,而非网络沙箱。
特殊权限语义
Section titled “特殊权限语义”大多数工具都按上面的 allow / ask / deny 字面含义执行,但少数编排工具有意带有额外联动,使权限设置与 Chord 能安全运行的工作流保持一致:
-
edit和apply_patch属于同一个文件编辑工具族,只是面向模型暴露的编辑格式不同(patch作为apply_patch的旧别名仍被接受)。另一个编辑器没有同名显式规则时,一个编辑器的规则会作用到另一个编辑器。这也包括deny:*: allow后面配置edit: deny会同时禁用edit和apply_patch,因为apply_patch继承了编辑工具族的拒绝规则。如果需要两个格式有不同行为,请同时配置edit和apply_patch。例如edit: allow加apply_patch: deny会禁用apply_patch但保留edit,GPT/o 系列模型会退回使用edit;反过来,apply_patch: allow加edit: deny会让默认偏好edit的非 GPT 模型退回使用apply_patch。 -
apply_patch还会对同一个补丁内的路径同时考虑write与delete:新增或修改的文件额外与指名write的规则比对,删除的文件和移动的源路径额外与指名delete的规则比对。这些规则只能让判定更严格(deny>ask>allow),"*": allow这类通配默认值不参与;因此delete: deny不会被「用补丁删文件」绕过。 -
handoff和done会被当作控制 gate。设为deny会隐藏或禁用对应工作流;设为allow或ask都会让工作流可用,真正交接 / 完成时 Chord 仍可能显示本地确认(例如 loop 的done确认)。也就是说,ask不是这两个工具的「更强工作流模式」,它主要表示工具保持可见 / 可用,同时保留 Chord 内建确认 gate。这个取舍可以避免模型看到一个可用控制工具却最终无法完成,同时仍防止静默切换角色或过早退出 loop。 -
done是 loop 工作流的退出信号:Chord 只在 loop 运行期间挂载它;规则拒绝done时/loop on会被拒绝并给出 toast,因此done: deny等于把 loop 的终止权保留给用户。详见使用指南:持续执行模式。 -
delegate会匹配调用参数中的agent_type,因此每个角色都可以只允许委派给指定的 SubAgent 定义。例如,下面按声明顺序先拒绝所有目标,再允许reviewer,并要求委派给tester前进行确认:permission:delegate:"*": denyreviewer: allowtester: ask被拒绝的目标不会出现在 Delegate 工具 schema 或协调 prompt 中;如果所有已配置的 SubAgent 目标都被拒绝,Delegate 会被隐藏。权限规则按「最后匹配优先」生效,因此通配兜底规则应写在具体目标规则之前。
-
delegate也控制一组委派工作流。如果有效的通配deny将它整体禁用,Chord 还会禁用通过cancel取消 SubAgent、从 SubAgent 中隐藏嵌套的delegate/cancel,并把 SubAgent 的notify限制为只通知自己的 owner,而不是任意指定目标。原因是取消或定向通知其他委派任务本身属于管理 delegated workstreams;如果禁用委派却允许这些片段,会形成一个不完整但仍可干扰委派工作的控制面。 -
因此
cancel依赖delegate:即使配置了cancel: allow,只要delegate被禁用,cancel仍会被拒绝。若希望某个角色能取消委派工作,需要同时启用delegate和cancel。 -
question: ask会被归一化为allow。question工具本身就是向用户提出结构化问题并等待回答;如果在提问前再加一次权限确认,只会产生重复弹窗,并不能降低最终决策风险。 -
worktree_exit会匹配它的action参数:keep(省略action时的默认值)或remove。因此可以只拦住删除、不阻挡退出:worktree_exit: {remove: deny}会拒绝删除 checkout,传了discard_changes: true的删除同样被拦住,而单纯的退出仍按通配规则处理。action参与匹配前会去掉首尾空格并统一转成小写(Remove也会命中remove);规则键区分大小写,照小写写即可。worktree_enter和worktree_list没有用于权限匹配的参数:它们的调用按*匹配,所以给它们写的普通规则对每次调用都生效。 -
有两个控制工具不受纯通配规则约束,因为让它们变得可用的那个开关本身就是授权:
compact_context和done。allowlist 角色的"*": deny既不会隐藏它们,也不会拦截其调用,否则用户启用了模型驱动压缩或开启了 loop,却发现毫无反应。只有指名该工具的规则才能覆盖这个默认:deny移除工具,ask保留工具但每次调用需确认,allow与默认一致。像compact_*这样的窄匹配算指名,写成带参数形式的规则也算:这两个工具都不接受用于权限匹配的参数。它们也都没有对外副作用:一个收缩上下文,一个结束 loop,通配规则在这里没有可保护的能力。 -
YOLO 消除的是日常工作中高频确认带来的摩擦:文件编辑和 shell 命令,仅此而已。开启期间,主 agent 的普通工具会完全跳过权限检查:
ask不再弹确认,deny也不会拦截。这个放宽是单向的,只放宽不收紧,关掉 YOLO 即完全恢复原权限。控制工具改变的是 agent 拓扑和会话生命周期,而不是单次操作的风险面,因此 YOLO 下它们仍按配置的规则执行:handoff、delegate、cancel、done、compact_context。它们分为两类:handoff、delegate、cancel会给角色带来它原本没有的能力,因此 YOLO 只放宽一处:ask不再弹共享确认框、直接放行。allow照常放行,deny照常拒绝,通配默认也和关闭时一致。所以 YOLO 不会授予规则之外的新编排能力:builder这类单 agent 角色在 YOLO 开启时仍然是单 agent,是因为它自己的规则 deny 了handoff和delegate。done和compact_context只是结束或收缩当前这段工作,YOLO 不改变它们的专门语义,行为与关闭时完全一致:指名它们的规则照常生效(deny仍会移除或禁用对应工具,compact_context的显式ask在 YOLO 下仍会逐次确认)。- SubAgent 在执行时继承该模式,但始终按自己的完整规则集判定:YOLO 开启期间,它们需要
ask的调用不再弹共享确认框、直接放行;deny依然拒绝(只读 worker 的write: deny仍然生效),done、compact_context保持各自语义。关闭 YOLO 后,SubAgent 的后续调用立即恢复确认。
权限属于 Agent 级配置,不是简单的全局开关。
对于 shell,像 "git *": allow 这样的具体 allow pattern 不会自动放行这类命令:含未引用 shell 分隔符(;、&&、||、|、& 或换行)、含命令替换($(...) 或反引号,双引号里的也算)、含进程替换(<(...) 或 >(...),同样会起子进程)、含环境变量赋值或声明(PATH=...、LD_PRELOAD=... 等任意变量,以及 export / declare / local 的同类写法,不管写在命令前面还是单独成行),以及引号解析不出来(引号没闭合、结尾是反斜杠)。这类调用会继续匹配后续规则,通常回到 ask 或 deny。单引号里或反斜杠转义后的分隔符只是字面量,仍会命中窄规则。这只是安全兜底,不是 shell 沙箱;shell: allow 或 shell: { "*": allow } 这类宽泛规则只应给完全可信的角色使用。
但一条针对具体命令的 allow 会覆盖该命令的全部能力,包括输出重定向。若放行了 echo *,那么 echo secret > ~/.bashrc、echo x >> file、data > /dev/tcp/host/port 都会被允许:重定向目标和命令文本属于同一条 shell 命令,而非独立的工具调用,因此不会被单独匹配或管控。环境变量赋值前缀则不同:LD_PRELOAD=./x.so echo hi 会按 LD_PRELOAD=... echo hi 整段文本匹配,因此不会借 echo * 放行,而是继续匹配后续规则。按命令本身文本写的规则仍然决定结果:赋值让窄 allow 匹配不上时,既不会把被拒绝的命令挤到更宽的规则里,也不会让窄 ask 落到更宽的 allow 上。只有当你能接受某命令的最坏情况(如通过重定向任意写文件)时,才给它命令级 allow;否则保持 ask。
只读 shell 命令(只影响并行,不影响权限)
Section titled “只读 shell 命令(只影响并行,不影响权限)”有些 shell 命令能证明只读,Chord 会让它们和其他只读工具并行,而不是一个个串行。判定偏保守:拿不准就按非只读处理。管道和 && / || / ; 只有两侧都只读才算只读;command / nice 只剥一层;重定向里除了 <、<<< 和 2>&1 这类描述符复制,其余一律按写处理。不认识的 flag 直接判否,新版本工具加了写选项也不会漏放行。rg 只有带 --no-config 才算只读:否则 RIPGREP_CONFIG_PATH 可以加上 --pre,去跑外部命令。会被 shell 改写的词同样判否:glob 展开(*、?、[)、反斜杠的转义消除、brace 展开({...})、以及执行前会解码转义的 $'...'——它们都跑在 flag 解析前面,能把看起来人畜无害的操作数变成 flag(目录里有个叫 -o 的文件,sort * 就会变成 sort -o ...)。这些字符写在引号里只是字面量,不受影响。
这个判定只管并行,不管权限:你写的 ask / deny 照样优先,只读命令该确认还是会确认。只读命令偶尔还是串行,那是判定在保守起见,不是 bug。但如果你看到写操作被拿去并行,那就是 bug,把完整命令串报上来。
Shell 与 shell 风险
Section titled “Shell 与 shell 风险”shell 能执行系统命令,应格外谨慎。无论是前台运行还是作为后台 job,shell 都是刻意设计的非交互工具:Chord 不会把模型可控的 stdin 接入子进程;Unix 子进程会在没有 controlling TTY 的环境中运行;高置信的交互式命令会在执行前被拒绝。普通 stdin 读取(如 shell read/select)会看到 EOF,而不是等待模型输入;如果命令需要输入,请通过 pipe、here-doc、文件或参数显式提供。登录向导、终端编辑器、pager / 全屏 TUI、密码提示、以及需要 /dev/tty 的命令,应在真实终端中手动执行,或改写为显式提供输入/参数的非交互命令。
shell(前台或后台 job)的平台说明:
- 在 Unix 上,Chord 会把子进程放到新的 session 中,并在超时/取消时按进程组清理。
- 在 Windows 上,Chord 仍然保持
shell(前台命令与后台 job)非交互,但这里没有与 Unixsetsid/ 进程组控制完全等价的路径;超时/取消时会退回到直接终止进程,对后代进程的清理可能不如 Unix 完整。
常见改写方式:
- 用
git commit -m "message"或git commit -F file代替会打开编辑器的git commit - amend 时如果要保留现有提交信息,使用明确不会打开编辑器的形式,如
git commit --amend --no-edit或git commit --amend -C HEAD - 避免在
shell中运行交互式 Git patch 流程(git add -p、git commit -p、git stash -p);改为显式指定 pathspec,或在真实终端中手动执行 - 容器命令不要分配 TTY(如
docker exec -it、docker run -t、podman run -t、kubectl exec -it),除非你是在真实终端中手动运行 - 用
npm init -y/--yes,或显式提供所有必要选项 - 需要 sudo 非交互失败时用
sudo -n,避免等待密码提示 - 命令确实支持非交互 stdin 时,用 pipe 或 here-doc 显式提供输入
建议:
- 默认把文件删除、批量改写、网络下载、数据库操作保留为
ask或deny - 如需管控本地/内网服务或敏感 endpoint,使用
web_fetchpattern:按主机/端口(web_fetch: { "localhost:8000": ask })或按地址段(web_fetch: { "169.254.0.0/16": deny, "*:8000-9000": ask }) - 仅对少量可预期的开发命令设置
allow - 不要把权限匹配理解为安全沙箱
重要:Chord 的权限匹配是产品层面的风险控制,不是操作系统级隔离或安全沙箱。
文件修改风险
Section titled “文件修改风险”文件工具会直接操作工作区,不是预演。重要仓库应配合 Git 使用,生产配置、部署脚本和密钥文件建议保持 ask。
哪些操作会改文件
Section titled “哪些操作会改文件”| 操作 | 影响 |
|---|---|
write |
创建文件,或整体替换已有普通文件。 |
edit / apply_patch |
修改局部内容;补丁还可新增、移动和删除文件。独立文件组可能部分成功,重试前先看已应用的修改。 |
delete |
删除文件,不要求模型先完整读取。删除符号链接只移除链接,不删除目标;write 拒绝跟随符号链接。 |
read / view_image / grep |
不修改文件,但读取内容可能进入会话和模型请求。只读不代表敏感数据不会外发。 |
写入前的检查与备份
Section titled “写入前的检查与备份”- 整体覆盖:
write不会仅因模型没读过当前版本而拒绝。未读取、读取不完整或文件后来有变化时,会尝试备份旧内容并在结果中给出位置。内容与模型最近一次写入或编辑的结果一致时,不另做备份。 - 无法读取旧内容:写入仍可能继续,但无法备份,结果只会提示风险。新文件不需要旧版本备份。
- 局部编辑:
edit和apply_patch会核对当前文件的定位内容。即使文件有变化,只要定位仍有效,也可能在警告后继续修改,并尽力备份有风险的非空旧内容。 - 删除文件:模型尚未观察到文件当前内容时,会尝试备份确切的磁盘字节,包括空文件;不会跟随符号链接备份目标。
备份最多每个路径 10 份、每个会话 200 份,单文件上限 10 MiB,会话总上限 50 MiB。局部编辑的备份超限或失败不会阻止修改,且失败可能只记入日志。不要把会话备份当作可靠的版本管理;清理会话也会删除这些备份。
文件读取限制
Section titled “文件读取限制”按路径读取的工具拒绝 /dev/stdin、/dev/stdout、/dev/stderr 等受限设备路径。本地文本工具优先使用 UTF-8 或带 BOM 的 Unicode,也对 GB18030、Big5、Shift-JIS 等编码提供有限支持;无法明确识别或不支持的编码会报错。web_fetch 按 HTTP 响应声明的字符集解码。
- API keys 建议放在
~/.config/chord/auth.yaml - 也可通过环境变量引用
- 不要将真实密钥写入示例配置、脚本或项目仓库
- 为
auth.yaml设置严格权限,如chmod 600 ~/.config/chord/auth.yaml
Headless 模式安全边界
Section titled “Headless 模式安全边界”chord headless 适合作为 bot / gateway 的底层控制面,但它本身不负责多租户隔离、浏览器安全边界或权限托管。
接入聊天平台、自动化系统或团队服务时,应在外层额外控制:
- 允许访问哪些工作目录
- 允许调用哪些命令
- 谁可以批准高风险操作
- 事件如何审计与留痕
网络与外部集成
Section titled “网络与外部集成”Chord 支持接入:
- provider API
- LSP
- MCP
- Hooks
- 本地 shell 命令
这些能力都会扩大运行时边界。接入前建议逐项确认:
- 是否真的需要该能力
- 它会读写哪些资源
- 出错时如何回滚或停用
- 是否会把敏感数据带到外部服务
- 在共享仓库或团队环境中,不要默认全局
allow - 对自动化 Hook 和 MCP 工具做最小权限暴露