Repository navigation
[ac#1149] 围栏内 set-ID 二进制不可 exec:登记为已知代价 + 真 sandbox-exec 双向判据 - #1152
Merged
Merged
Conversation
REQ-138 的围栏开着时,`ps` / `top` 在围栏内拿到 `Operation not permitted`。 这是 macOS seatbelt 对沙箱进程 exec set-ID 程序的固有拒绝(profile 只有 `(allow default)` + `(deny file-write*)`),`#1144` 量到过,但只存在于一份 dated 证据文件里 —— 没有任何东西会因为这条代价被改掉或被扩大而变红。 单一权威 = packages/ext/src/alpha-sandbox-setid-exec.test.ts:经生产的 wrapEngineShell 起真 sandbox-exec 跑真二进制,成员「该被拒还是该通过」由盘上的 真实 mode 派生(S_ISUID|S_ISGID),不写死清单。判据双向,两个方向各有正样本。 勘破更正三条(实测,2026-08-26): - 这一类不止于 setuid —— /usr/bin/write(0o2555,setgid-only)同样被拒, 判据取 0o6000;只看 setuid 位会把它归进「该通过」那一类而假红; - zsh 的拒绝签名是 rc=127,不是证据文件里的 126(那一跑用的是 /bin/sh); - /usr/bin/su 不进语料:有控制终端时它 `Password:` 阻塞读 /dev/tty, 无围栏对照臂会真的 exec 它,把 bun test 挂死。 Refs #1149
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1149
围栏开着时
ps/top在围栏内拿到Operation not permitted。这是 macOS seatbelt 对沙箱进程 exec set-ID 程序的固有拒绝(本仓 profile 只有(allow default)+(deny file-write*)),#1144量到过 —— 但它只存在于一份 dated 证据文件里,没有任何东西会因为这条代价被改掉或被扩大而变红。本 PR 补的就是那个东西。按裁决取 (a) 只登记为已知代价,不改用户可见行为;
tool/shell.ts的错误路径一个字没动。1. 单一权威在哪,新增 set-ID 成员时它怎么自己长出来
权威 =
packages/ext/src/alpha-sandbox-setid-exec.test.ts(packages/ext/src/shell-sandbox.ts的抬头新增一段 K1 指向它,不在那里枚举成员)。它经生产的
wrapEngineShell把 profile + wrapper 落到临时 root,再用真/usr/bin/sandbox-exec跑真二进制 —— 判的是行为,不是 profile 的文本(断言源码文本的闸门,在守卫被整段注释掉时照样绿)。自己长出来的机制:语料只登记「候选路径 + 一组无害参数」,类别不写在语料里,由
从盘上的真实 mode 派生。于是:
期望值来自文件系统、观察值来自跑围栏,两者不同源 —— 不是自指等价链。
2. 勘破更正三条(动笔前实测,票面/证据文件与之不符)
/usr/bin/write=0o2555setgid-only(setuid 位为 0),围栏内同样zsh:1: operation not permitted: /usr/bin/write,裸臂write: … is not logged inrc=1。⇒ 判据取0o6000而不是0o4000;只看 setuid 位会把write归进「该通过」那一类,制造一条假红/bin/sh。生产 wrapper exec 的是真 shell(zsh)⇒ rc=127 +zsh:1: operation not permitted: <path>。判据两者都认,且要求「rc ∈ {126,127}」与「stderr 含 operation not permitted 且点名该路径」同时成立susu打印Password:并阻塞读/dev/tty(stdin 给 pipe 拦不住);无围栏对照臂会真的 exec 它,把bun test挂死。实测script -q /dev/null /usr/bin/su <不存在的用户>5s 未返回只能 SIGKILL,而同一条命令无 tty 时 15ms 返回su: Sorry——「我这儿跑着没事」不是证据。set-ID 这一类由ps/top/quota/write覆盖顺带实测掉的一条死路:合成 setuid 探针不可行 ——
cp /bin/echo x && chmod 4755 x之后 mode 实测仍是0o755(set-ID 位没落上),且该副本两臂都 rc=137(SIGKILL)。语料只能用真系统二进制。3. AC2 双向变异实测(真实输出)
基线(未变异):
4 pass / 0 fail / Ran 4 tests across 1 file [1.53s]。方向一 —— wrapper 改动意外恢复 set-ID exec ⇒ 必须红
变异:
WRAPPER_SCRIPT去掉 sandbox-exec,exec /usr/bin/sandbox-exec -f … "$ALPHA_REAL_SHELL" "$@"→exec "$ALPHA_REAL_SHELL" "$@"。还原
git checkout -- packages/ext/src/shell-sandbox.ts⇒4 pass / 0 fail。方向二 —— 把非 set-ID 的
ls/git也拦了 ⇒ 必须红变异:
SEATBELT_PROFILE追加(deny process-exec (literal "/bin/ls") (literal "/usr/bin/git"))(先单独起sandbox-exec验过这条 profile 能解析、进程真的启动,不是靠空输出判断)。只有被拦的那两行翻,
wc与四条 set-ID 行不动 ⇒ 判据的粒度落在单个二进制上,不是「围栏还在不在」。还原 ⇒4 pass / 0 fail。方向三(观测手段自检)—— 空输出不许被读成「拦住了」
变异:profile 里塞一条解析不了的谓词(
(deny network* (remote host "github.com")),sandbox-exec对它unbound variable: host)⇒ 进程根本不启动,输出全空,长得像「全都被拦住了」。关键是第二段:set-ID 那四行报
unmeasured而不是exec-denied—— 若判据只看「命令没跑成」,这一格会假绿。还原 ⇒4 pass / 0 fail。4. 本地门(逐条真实输出,
set -o pipefail,退出码单独取)north-star-guard.sh✓ zero upstream package edits (baseline origin/alpha…)assert-frontend-patch-roundtrip.shassert-no-nul-bytes.py✓ 7738 个版本控制文件零字面 NUL 字节typecheck×4(contracts-consumer / ext / ui-mac / opencode)tsgo零条check:vendorbun-test-floor 15 alpha-contracts-consumer60 pass / 0 fail / 6 filesbun-test-floor 100 ext161 pass / 0 fail / 15 filesbun-test-floor 3000 ui-mac src4506 pass / 0 fail / 313 filesbun-test-app.sh624 条+ 5 个判据文件逐个精确匹配assert-gate-files.sh✓ 162 个闸门文件全部在位且真的跑过assert-seed-assets.sh✓ seed/vendored resources presentcheck-doc-links.py(改了 md)✓ 5 relative link(s) resolve across 1 file(s)与 base fail-set 的差:空。 base =
a4f750bcd(ext 157 pass / 14 files、ui-mac 4506 pass / 313 files / 0 fail)⇒ 本 PR:ext 161 / 15(+4 条 +1 文件 = 本 PR 的增量,逐条对得上)、ui-mac 4506 / 313 / 0 fail(逐字不变)。git push --no-verify:pre-push 会exec scripts/alpha-check.sh,而它第 [8/9] 步真建探针 worktree 并写全 worktree 共享的core.hooksPath(并行 lane 期间互相制造假红,且每跑一次泄漏一棵探针树,ac#928)。派发书明确要求不跑它;上表 13 道门是逐条单跑的结果。5. 为什么不进
scripts/gate-files.tsv登记 = 精确条数,而 CI 是
runs-on: ubuntu-latest,本文件在非 darwin 上整块describe.skip。这个前提我跑了一遍再写:临时造一个describe.skip的 4 条文件,bash scripts/bun-test-floor.sh =4 …实测⇒ 登记它会让 CI 恒红,而「恒红的门训练人忽略它」。与
alpha-sandbox-escape.test.ts(真 sandbox-exec 正反语料,同样 darwin-only、同样未登记)是同一支,gate-files.tsv里alpha-sandbox-seam.test.ts那行的 guarantee 已经写明这件事。文件名不命中GATE_NAME_TOKENS,gate-file-registry.test.ts不要求它二选一(实测 162 个闸门文件全绿)。6. 我主动没做的事(留给主 session 裁,不顺手改)
packages/opencode/src/tool/shell.ts的错误路径 —— 候选 (b)「给用户一句可读解释」按裁决 out of scope。今天用户看到的仍是裸Operation not permitted。要做需另开票。docs/verification/2026-08-26-req138-1144-packaged-shell-tool-chain/—— dated 证据文件是那一天的快照(它自己写着「由 owner 裁」),裁决落在活基线docs/architecture/2026-08-23-shell-sandbox-seam.md§5 上。alpha-sandbox-escape.test.ts与本文件都没有任何东西钉住它们的存在 —— 两者都 darwin-only 故不在gate-files.tsv,而ext整包地板是 100、实测 161 ⇒ 把这两个文件整个删掉,三层闸门全绿。这是gate-files.tsv抬头自己写的「单点消失」那一类,只是对 darwin-only 文件今天没有出口(登记会恒红)。本票 AC 不含它,不顺手扩。SHELL=/bin/zsh。Intel 机器、真 shell 是 bash 的用户没跑过(与#1076/#1144同一条残余)。