读文件和跑命令分开授权、OpenWorker 的两套开关别混用

文章导读
先给判断:把「文件读取」和「命令执行」当成两条独立权限,别用一个总开关一起放。先用只读命令把任务跑通,确认拦截生效、日志可查,再讨论临时放开写操作。这样出问题时你能说清是“读到了不该读的文件”还是“跑了一条改了东西的命令”,两者的回收动作完全不同。
📋 目录
  1. Ⅰ 把文件读取和命令执行拆成两套开关
  2. Ⅱ 命令白名单先只放只读命令
  3. Ⅲ 跑一次任务验证放行范围是否符合预期
  4. Ⅳ 被拦下的命令在日志里是什么样
  5. Ⅴ 任务确实要写文件时怎么临时放开再收回
A A

先给判断:把「文件读取」和「命令执行」当成两条独立权限,别用一个总开关一起放。先用只读命令把任务跑通,确认拦截生效、日志可查,再讨论临时放开写操作。这样出问题时你能说清是“读到了不该读的文件”还是“跑了一条改了东西的命令”,两者的回收动作完全不同。

OpenWorker 这类执行框架通常把文件读取、文件写入、命令执行拆成不同开关,但界面叫法和层级随版本变化,需要到设置里确认是否能独立控制。建议的操作顺序是:记录默认值 → 只开文件读取 → 命令执行走白名单且只放只读命令 → 跑一次任务对比放行与拦截的界面和日志 → 确需写文件时临时放开并限定目录 → 任务结束立即回滚并复验。边界在于:如果命令执行是以 shell 方式跑的,白名单只按命令名前缀匹配就可能被重定向和管道绕过,必须同时禁用元字符或改为参数级匹配。

把文件读取和命令执行拆成两套开关

先去设置里找这两项。常见位置在“工具调用 / 工作区权限 / 执行环境”一类页面,文件读取一般带有目录范围(roots、workspace path)、是否跟随软链接;命令执行一般带有开关、模式(全部允许 / 白名单 / 每次确认 / 关闭)和超时。不同版本的字段名不一样,以你界面上实际显示的为准,但判断方法一致:把文件读取关掉、命令执行打开,看任务是否能跑——如果还能正常读文件,说明这两项在你这个版本里没有真正拆开。

改动前先把默认值抄下来或截图,至少记录四项:文件读取是否默认开启、可读目录默认范围、文件写入是否单独存在、命令执行默认是否关闭。这几条决定了后面回滚的参照点。

可用组合大致是这几种:只开文件读取、关命令执行,适合纯问答和代码理解类任务;开文件读取、命令执行走只读白名单,是推荐起步状态;命令执行打开但文件读取关闭,多数任务会失败,因为读取上下文往往依赖读文件;把文件写入和命令执行绑在同一个开关上时,开命令就等于开写,这种组合最危险。

互相影响主要发生在两处。一是有些实现把“写文件”挂在命令执行下面,命令一放行,cat > file、sed -i 这类就跟着能写;二是文件读取范围过宽时,即使命令白名单很严,agent 仍能读到工作区外的内容。下面是一份通用配置骨架,字段名替换成你实际使用的即可:

读文件和跑命令分开授权、OpenWorker 的两套开关别混用
workspace:
  file_read:
    enabled: true
    roots: ["/home/user/work"]
    follow_symlink: false
  file_write:
    enabled: false          # 与命令执行分开,别合并成一个总开关
    roots: ["/home/user/work/build"]
  command_exec:
    enabled: true
    mode: allowlist         # allowlist | confirm | any | off
    allowlist_file: ./cmd-allow.txt
    timeout_sec: 20
    shell: false            # 关掉 shell,减少管道和重定向绕过

命令白名单先只放只读命令

白名单要小,宁可从三条命令开始逐个加,也不要把 * 或 bash、sh、python 放进去——这些等于把整个执行权限重新打开。先覆盖任务真正需要的那几步:看目录、看路径、看文件内容、看版本状态。

只读命令清单示例,实际按任务裁剪:

  • 目录与路径:ls、ls -la、pwd、find -maxdepth(不带 -delete、-exec)、stat、file
  • 内容查看:cat、head -n、tail -n、wc -l、grep -R(不带 `--include` 配合写入类参数)
  • 空间与状态:du -sh、df -h、which、git status、git log `--oneline`、git diff

不要进白名单的:tee、sed -i、sort -o、curl -o、cp、mv、rm、mkdir、find -delete、find -exec、git checkout、git clean、xargs。这些命令名义上没有“写”,实际都能落盘或改仓库状态。

白名单文件的格式骨架,一行一条,按前缀匹配:

读文件和跑命令分开授权、OpenWorker 的两套开关别混用
# cmd-allow.txt  一行一条,前缀匹配,不加通配符
ls
ls -la
pwd
cat
head -n
tail -n
wc -l
grep -R
find -maxdepth
stat
git status
git log `--oneline`
git diff

两条关键约束:第一,匹配方式要在日志里能看出来是前缀还是整行精确,前缀匹配下 cat 会放行 cat anything;第二,如果 shell 没关,cat a.txt > b.txt 依然会写文件,所以白名单和禁用 shell 要一起改。

跑一次任务验证放行范围是否符合预期

验证不要只看“任务有没有成功”,要看单条命令的反馈。给 agent 一个明确的任务,让它依次执行:pwd、ls -la(应在白名单内),再执行 touch ./probe.txt、rm ./probe.txt(应被拦),最后执行一条带元字符的 ls; touch ./probe2.txt 作为绕过测试。

  1. 放行命令:界面应显示命令原文和 stdout,退出码为 0。
  2. 未放行命令:界面应显示被拒绝或未执行,没有 stdout;工作目录里不应出现 probe.txt。
  3. 绕过测试:如果 probe2.txt 被创建出来,说明 shell 没关或匹配太松,需要回去收紧。

用 ls -la 前后各看一次目录,比对是否有计划外的新文件,这是最直接的一致性检查。

被拦下的命令在日志里是什么样

拦截记录和“命令执行失败”是两类东西。失败意味着命令启动了、跑了、返回了非 0 退出码,会有 stderr;拦截意味着命令没启动,错误来自策略层,通常没有 stderr,字段里会带判定结果和命中规则。

读文件和跑命令分开授权、OpenWorker 的两套开关别混用

需要能被检索的关键字:deny、denied、blocked、not allowed、allowlist、reject、policy。日志样本形如:

{"ts":"<时间>","task_id":"<任务id>","tool":"command_exec",
 "cmd":"rm -rf ./build","cwd":"/home/user/work",
 "decision":"deny","reason":"not_in_allowlist",
 "rule":"cmd-allow.txt","exit_code":null,"stderr":""}

区分方式看三点:decision 是 deny 还是 allow;exit_code 是 null(未执行)还是非 0(执行失败);stderr 有没有内容。三个都对不上时,再看 reason 里写的是白名单未命中、超时还是工作目录越界——不同原因对应不同的收紧方向。

任务确实要写文件时怎么临时放开再收回

写操作要单独放开,不要用“把命令执行模式从 allowlist 改成 any”来图省事,那等于把只读验证全部作废。完整流程分五步:

  1. 确认范围:列出这次任务要写的具体目录和文件,比如只写 ./build/。
  2. 最小放开:优先只开 file_write.enabled 并限定 roots;如果必须跑写命令,就在白名单里加参数级条目,例如 mkdir build、cp src/a.txt,不要加裸 mkdir、cp。
  3. 记录改动:写下改前值、改后值、时间、任务 id,这是回滚依据。
  4. 限制执行条件:一次性任务、短超时、限定工作目录,任务结束立即处理。
  5. 回收并复验:把 file_write.enabled 改回 false、删除临时加入的白名单条目,然后重跑同一条写命令,预期被拦;再用 ls -la 和 git status 检查目录里没有超出预期的残留文件;最后翻一次日志,确认这段时间内没有其他未预期的命令被执行。

回滚后再跑一遍第 3 节的验证任务,确认只读命令仍然放行、写命令重新被拦,才算这次临时放开真正收回了。