Skip to content

fix: 安全审计加固 / Harden the permission gate and runner - #1

Open
hezhongtang wants to merge 4 commits into
KyoMio:mainfrom
hezhongtang:fix/security-audit-20261008
Open

hezhongtang wants to merge 4 commits into
KyoMio:mainfrom
hezhongtang:fix/security-audit-20261008

Conversation

@hezhongtang

Copy link
Copy Markdown

中文

符号链接后跟 .. 时,原来的路径检查先做词法折叠,可能把实际落在执行副本之外的操作判成界内。模型审批又会读取执行端可修改的任务单原文件,使审批依据能在投递后改变。本 PR 按安全审计加固这些路径,并保护闸门的状态和应答通道。

改动

  • 路径判界与闸门自保:逐段解析符号链接和 ..,同时检查原路径与词法归一路径;写类工具没有可判路径或解析失败时转人工。直接触碰本工具管控面、调用本工具 CLI 的 Bash/js 操作也转人工,保留默认 worktrees/ 与本会话执行副本的例外。
  • 固定审批依据:入队时保存任务单快照,记录 SHA-256,审批只读并核验快照。缺失或被修改的快照不再作为任务单授权依据。
  • 凭据与落盘保护:ZCode 子进程剔除已知宿主代理凭据环境变量;runs/ 树使用 0700、文件使用 0600;挂起内容按已知密钥值脱敏;原子写使用随机临时名和排他创建。
  • 审批探针与解析:关闭 git 探针的 fsmonitor、钩子、外部 diff、textconv 和已知 filter 驱动;快筛只接受独立成词、没有相反标记的肯定结果,含糊结果继续慢判。
  • 协议与终端输出:反向请求应答缓存在下一回合开始时清理;无 requestId 的请求按方法及参数全文哈希去重;终端控制字符转义,减少挂起内容被改写的风险。
  • 文档与回归用例:同步行为合同、术语、D23 取舍、实测记录与用例数量;核对 App 3.14.4 的工具注册与 --deny,明确禁用工具的联网边界。

模型审批仍只产出“放行”或“转人工”;人工拒绝路径与仅允许一次的合同保持不变。

验证

  • macOS / Node v26.5.0:npm test,765/765 通过,包括文档版本同步检查。使用 mock,不消耗 ZCode 额度。
  • git diff --check 通过。
  • 对照本机 ZCode App 3.14.4 源码核实工具注册、禁止列表与工作流开关;未为此发模型回合。
  • 与上游 main(5c728fe)的 git merge-tree 检查无冲突。

已知边界

执行端与 runner 仍是同一个系统用户。自保检查只识别直白写法,不提供系统级隔离。--deny "WebFetch WebSearch" 只移除指定联网工具,Bash、js 和 MCP 工具仍可能联网。硬链接越界与子模块自身的 git filter 配置等限制已记录在 D23,未在本 PR 扩大处理范围。

联合参与

本次工作由 Opus5.5、GPT-Sol-6.1、GLM5.3 联合参与修复。


English

When a symbolic link is followed by .., the previous path check normalized the path before resolving links and could classify an operation outside the worktree as inside it. Model review also read the mutable task file, allowing its authorization basis to change after a send. This PR hardens these paths following the security audit and protects the gate's state and answer channel.

Changes

  • Path boundaries and gate protection: resolve symbolic links and .. component by component, checking both the original and normalized paths. Missing write targets and resolution failures escalate to a human. Direct Bash/js references to the control plane or executor CLI also escalate, with exceptions for the default worktrees/ root and the current session's worktree.
  • Frozen authorization basis: snapshot task files when queued, record SHA-256, and review only verified snapshots. Missing or modified snapshots are excluded from task-file authorization evidence.
  • Credentials and persisted state: strip known host-agent credential variables from ZCode's environment; use 0700 directories and 0600 files for the run tree; redact known secret values from pending state; use randomized, exclusively created temporary files for atomic writes.
  • Review probes and parsing: disable fsmonitor, hooks, external diff, textconv and known filter drivers in git probes. Fast screening accepts only standalone affirmative markers without contradictory markers; ambiguous output proceeds to slow review.
  • Protocol and terminal output: clear settled reverse-request responses at the next turn boundary, deduplicate requests without requestId by method and a hash of the complete parameters, and escape terminal control characters.
  • Documentation and regressions: update behavioral contracts, terminology, D23 decisions, verification notes and test counts. Check App 3.14.4 tool registration and --deny, documenting the network-access boundary.

Model review continues to return only allow or ask. Human denial and the allow-once contract remain unchanged.

Validation

  • macOS / Node v26.5.0: npm test, 765/765 passing, including the documentation-version check. All tests use mocks and spend no ZCode tokens.
  • git diff --check passed.
  • Inspected the local ZCode App 3.14.4 source for tool registration, deny lists and workflow switches; no model turn was sent for this verification.
  • git merge-tree found no conflicts with upstream main at 5c728fe.

Known boundaries

The executor and runner still share an OS user. Gate protection recognizes plain spellings and does not provide OS isolation. --deny "WebFetch WebSearch" removes the named tools; Bash, js and MCP tools may still access the network. Hard-link escapes and git filter configuration inside submodules remain documented limitations in D23.

Joint participation

This work was jointly carried out by Opus5.5, GPT-Sol-6.1, and GLM5.3.

hezhongtang and others added 4 commits October 8, 2026 10:53
外部安全审计 D4、D9-③ 在代码与 mock 上核实属实后的修复。

D4:runner 是从宿主代理里起的,环境里常有 ANTHROPIC_API_KEY、GITHUB_TOKEN 一类
凭据;zcode 子进程原样继承,执行端的 Bash 一条 env 就全看见,配上联网工具就能外发。
拉起子进程时按固定名单剔掉(不做白名单:HOME、PATH 漏一个就坏功能),不论它来自
本进程环境还是调用方的 opts.env——doctor 与闲时自检是把 process.env 整份传进来的,
只剔继承来的那一份会漏。没带过去的名字在 stderr 说一行,只有名字。

D9-③:去重表从不清理,对端一旦复用 requestId,上一回合缓存的放行会原样答给新请求,
处理器(闸门)根本不跑。mock 复现:第二回合换了命令、同一个 requestId,旧实现直接
回 allow。现在 Session.send 开头清掉已出结果的条目。没有 requestId 的退路键从
「方法|会话」改成「方法 + 参数全文」:同会话里第二个无 requestId 的审批请求不再并进
第一个、拿到它的放行;重发的参数一字不差,仍落进同一键,处理器不会被重发反复叫起。

另:events.jsonl 与所在目录新建时只给当前用户(审计 D5 落在协议层的部分)。

mock 加三个剧本字段:reportEnv、serverRequests[].omitRequestId、permission.requestId。
用例 728 → 731。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
外部安全审计 D9-① 核实属实:挂起回显把工具名、理由原样打到终端,执行端在里面夹一段
ANSI/OSC 转义就能清行、回车改写人正在看的那行审批、改窗口标题。回复原文、流式行、
文件名摘要、zcode 报错原文同样是执行端可控的文本,打印点有几十处。

逐处过滤迟早漏一处,所以在入口把 stdout / stderr 的写出口包一层:C0(换行、制表除外)、
DEL、C1(8 位的 CSI/OSC,JSON.stringify 不转义它们)、双向排版控制符,一律换成字面的
\uXXXX——内容不丢,人能看出这里被塞了东西。\r\n 收成 \n,落单的 \r 照样转义。
--json 的含义不变:这些字符只会出现在 JSON 字符串里,\uXXXX 在那里是合法转义,
解析回来还是原字符。runner.log 走的是同一个入口,一并中和。

拦不了的:模型回复里自己写一行「send: done」。回复原文按原样分行打印是定下来的做法。

用例 731 → 736(纯函数与写出口包装;走真实 CLI 的两条随下一个提交进 run.test)。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
外部安全审计 D1、D2、D3 及 D5、D6、D8、D9-②、D9-④ 逐条在代码与本机上核实后的修复。
每条先补失败用例再改;新加的检查失败方向都是转人工,没有哪条 ask 路径被改成自动放行。

D1(越界红线可绕):原先 path.resolve 之后再 realpathSync,<执行副本>/link/../x 先被
词法折叠成看着在界内的路径,实际写入落在链接目标的上一级——Node 的 realpathSync 自己
也是先折叠。改成从根逐段解析(符号链接就地展开,.. 退到真实父目录,末段的悬空链接也
跟),原串与词法归一后的串各算一遍,任一越界即命中。成环、把文件当目录穿这类判不了
落点的,以及写类工具(含 3.14.4 的保留名 ApplyPatch)拿不出可判路径的,不再当界内放过。

D2(执行端能自己批自己):执行端与 runner 是同一个用户,写 answer.json、跑
zcode-executor approve,文件权限与属主校验都挡不住。新增机械红线 self-modify-approval:
操作指向本工具的数据目录或调本工具的 CLI,直接转人工,不交给模型判。审计说「匹配数据
目录前缀不会误伤」在这里不成立——默认的执行副本就在 <数据目录>/worktrees 下——所以放行
worktrees/ 与本会话的 cwd,CLI 只认名字后面跟子命令。只认直白写法;拼变量、先写脚本
再跑认不出来,同一个用户之下没有根治(decisions D23)。

D3(任务单可事后改写):审批把任务单当「用户说过的话」,却在每次审批时现读执行副本里
那份执行端改得动的文件。入队时留快照(runs/<id>/tasks/<sha256>.md),审批只按事件里的
taskSha256 读快照并核对哈希;没有快照的旧事件不再作依据(只会更常转人工)。闲时投递
先取号后入队,所以在取号之前先留一份,任务单读不了不白用一次号。

其余:runs/ 树 0700、落盘 0600,pending.json 按值抹掉 runner 手里的密钥(D5;不做
启发式脱敏,人要靠原文决定放不放);原子写的临时名加随机段并排他创建(D9-②);
审批探针的 git 关掉 fsmonitor、钩子、外部 diff、textconv,并把仓库配置里的 filter
驱动逐个置空(D6;filter 这条审计没提,是核实时在 git 2.54.0 上实测出来的);
快筛只认独立成词且唯一的 Y,「They are…」里的 y 不再算放行(D8)。

没改的与理由见 decisions D23:慢判提示词不套启发式脱敏(D7)、Write 正文仍进快筛
(D8 后半)。

用例 736 → 765。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
同步已落地的安全加固行为、术语与验收数量。按本机 ZCode 3.14.4 的源码核对联网工具的注册与屏蔽,明确工具禁用和系统断网的边界,避免把局部加固写成完整隔离。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant