Skip to content

docs(verification): #1144 打包 shell 工具整链由 agent 回合驱动实跑 + Developer ID/hardened runtime 复跑 - #1148

Merged
jinjunnn merged 2 commits into
alphafrom
ac-1144
Aug 26, 2026
Merged

jinjunnn merged 2 commits into
alphafrom
ac-1144

Conversation

@jinjunnn

Copy link
Copy Markdown
Owner

Refs #1144

#1076 交付时自己诚实标出的两块残余,本轮各推进一步。未改任何生产代码 —— 本 PR = 一个取证
目录(runner + 结果 JSON)+ 基线 §5/§7 的回写 + docs/README.md 的一行索引。

取证:docs/verification/2026-08-26-req138-1144-packaged-shell-tool-chain/

结果

AC 断言 结论
AC1(整链) 打包产物里,一次 agent 回合 → shell 工具 → wrapper → sandbox-exec,越界写入不落盘 PASS —— 7 条语料 × 4 轮 = 28/28 不落盘,全部 processStarted=true
AC1(反向对照) 同一套语料,围栏移除的打包副本上落盘 PASS —— 28/28 全部落盘
AC1(「真模型」这四个字) 决定去调 shell 工具的是真模型 未闭合 —— 是本地 OpenAI-compatible 桩,不是真模型
AC2 hardened runtime + 签名产物上,§2.5/§2.6 正反语料结论与未开时一致 实跑完成,逐格一致;与出厂件仍有三处具名差异,是否据此关闭由 owner 裁

八轮原始输出(fenced / unfenced / hardened / hardened-unfenced,各 2 轮),合计 172 pass / 0 fail。
按驱动面拆:

驱动面 围栏 ON 围栏 OFF
ac1.toolChain/*(shell 工具,argv ["-c", cmd]) 28/28 不落盘 28/28 落盘
baseline25.promptShell/*(prompt !command,= 基线 §2.5/§2.6 原驱动面) 28/28 不落盘 28/28 落盘

同一条工具链上的正样本(工作区内 echo ok > inside.txt)两臂都落盘 —— 缺它,「没落盘」
分不清是「被拦住」还是「这条链根本没跑」。

AC1 怎么做到的

链条:消息 → tool_call → 工具注册表 → tool/shell.ts → ask() → Shell.acceptable(cfg.shell)
→ ["-c", cmd] spawn → wrapper → sandbox-exec。runner 自己不 spawn 任何 shell。

  • ask() 用产品自带的入口解决:config.ts:550 读 Flag.OPENCODE_PERMISSION,
    sidecar-env.ts:100 的 OPENCODE_ 前缀放行它 ⇒ 启动带
    OPENCODE_PERMISSION={"bash":"allow","external_directory":"allow"},没有为此改生产代码。
  • 工具那一格按 callID 从 transcript 认领,不是「取最后一个工具格」。
  • 判据:文件是否落盘,不是 exit code;每条语料先 echo AC1144-STARTED,空输出不算拦住。

桩不是真模型。 桩之后每一格都是打包产品自己的代码;没被验证的只有「模型自己会不会选这个
工具」。runner 已留好入口:--provider <真 providerID> --model <modelID>,两轮即可补齐。

AC2 —— 票面前置为假

#1144 / #1076 都写着「本机无 Developer ID」。实读 security find-identity -v -p codesigning:
有一张 Developer ID Application: … (RQX6X6A635),正是 electron-builder.config.ts:157 要找的那种。
#1076 记的两条死路都是这个假前提的推论。

用它 + --options runtime + 出厂三键 entitlements(不含 disable-library-validation)重签:
flags=0x10000(runtime) / TeamIdentifier=RQX6X6A635 / codesign --verify --deep --strict 通过,
app 正常启动,正反语料结论与未开时逐格一致。

与出厂件仍然不同的三处(不要读成"等于出厂签名"):无安全时间戳(--timestamp=none)、
未公证未 staple、签名由 codesign 自底向上逐个 Mach-O 完成而非 electron-builder。

静态确认 entitlements.plist 无 com.apple.security.app-sandbox 不构成 AC2 的证据 ——
它只说明「不构成嵌套沙箱冲突」这个前提为真。已在证据 §5.5 写明。

顺带留痕的四条

  1. ad-hoc 重签副本会卡在钥匙串 ACL,症状是「没有 CDP page」,看起来像变异把 app 改坏了。
    主线程停在 SecItemCopyMatching → SecKeychainItemCopyContent → ClientSession::decrypt → mach_msg
    (sample 43 帧原文在 results/keychain-acl-hang.json)。这与 #1076 归因给 hardened runtime
    的那条是同一个东西。
    解法:四臂一律加 Chromium 自带的 --use-mock-keychain。
  2. codesign --force --deep 漏签 Electron Framework.framework/Versions/A/Libraries/*.dylib,
    hardened 下 dyld 以 "different Team IDs" 拒。报错字面与 #1076 那条几乎一样但成因不同。
  3. #1076 §8 第 2 条(打包弄脏 43 个被追踪的图标)已不成立 —— ac#1115 合入后
    git ls-files packages/ui-mac/resources/icons = 0,prod 打包跑完 git status --porcelain = 0 行。
  4. 新增残余:沙箱进程不能 exec setuid 二进制。 /bin/ps / /usr/bin/top / /usr/bin/su
    在围栏内全部 Operation not permitted,非 setuid 的 ls/wc/git 正常
    (results/setuid-exec-observation.json)。用户可观察:agent 在工具里跑 ps 会失败。
    §2.8 误伤集没覆盖这一类;是否登记为已知代价尚未裁决。已回写基线 §5。

本地门

门 命令 结果
north-star bash scripts/north-star-guard.sh exit 0 —— zero upstream package edits
NUL python3 scripts/assert-no-nul-bytes.py exit 0 —— 7714 个版本控制文件(= 7701 + 本 PR 的 13 个)零字面 NUL
typecheck (ext) bun run --cwd packages/ext typecheck exit 0 / 0 条 error TS
docs gate python3 scripts/check-doc-links.py <3 个改动的 md> exit 0 —— 48 relative link(s) resolve across 3 file(s)(3 == 枚举数)
分类步 scripts/detect-changed-scope.sh(base = origin/alpha) code=true(证据目录里有 run.ts)、md= 上面那 3 个

没跑的:packages/{ui-mac,opencode,alpha-contracts-consumer} 的 typecheck 与全部 bun test
floor —— 本 PR 零生产代码改动,这些门的结果按构造与 base 相同。分类步判 code=true 只是因为
证据目录里有一个 .ts 取证脚本(#1076 同形),CI 会照跑。

未闭合

  • AC1 的「真模型」一步(需要一个可用凭据,owner 侧资源);
  • AC2 与出厂件的三处差异,以及公证后的产物没跑过;
  • 单机单配置(macOS 26.3.1 / arm64 / SHELL=/bin/zsh),bash/fish 用户与 Intel 未覆盖。

AC2 未闭合,所以本 PR 用 Refs 不用 Fixes。

🤖 Generated with Claude Code

https://claude.ai/code/session_01QHDS2BFKuzm9uNH1svU2oj

jinjunnn added 2 commits August 26, 2026 10:46
…hardened runtime 复跑

`#1076` 交付时诚实标出的两块残余,本轮各推进一步。未改任何生产代码。

AC1(整链)—— 在打包产物里由一次 agent 回合触发 shell **工具**调用:
消息 → tool_call → 工具注册表 → tool/shell.ts → ask() → cfg.shell → wrapper →
sandbox-exec。runner 自己不 spawn 任何 shell。`ask()` 走产品自带的
`OPENCODE_PERMISSION`(config.ts:550 读它,sidecar-env.ts 的 OPENCODE_ 前缀放行),
没有为它改生产代码。判据仍是「文件是否落盘」,每条语料先 `echo AC1144-STARTED`
证明进程真的起来过。四轮:围栏 ON 28/28 不落盘,围栏移除的打包副本 28/28 落盘;
同一条工具链上的正样本(工作区内写)两臂都落盘。

AC1 的「真模型」四个字**未闭合**:决定去调工具的是一个本地 OpenAI-compatible 桩
(与 #1108 同法,经产品自带的 providers.add 注册),不是真模型。桩之后每一格都是
产品代码;没被验证的是模型自己会不会选这个工具。runner 已留好真 provider 入口。

AC2 —— 票面前置「本机无 Developer ID」经实读为假(security find-identity 有一张
Developer ID Application)。用它 + --options runtime + 出厂三键 entitlements 重签
(flags=0x10000(runtime) / TeamIdentifier=RQX6X6A635 / verify --deep --strict 通过),
两个驱动面各 7 条 × 2 轮:围栏 ON 28/28 不落盘、围栏 OFF 28/28 落盘,与未开时逐格一致。
`#1076` 记的两条死路都是那个假前提的推论。与出厂件仍有三处具名差异(无安全时间戳、
未公证未 staple、签名由 codesign 自底向上而非 electron-builder),是否据此关闭 AC2 由 owner 裁。
静态确认 entitlements 无 app-sandbox **不构成** AC2 的证据,已写明。

顺带三条留痕:
- ad-hoc 重签副本会卡在钥匙串 ACL(SecItemCopyMatching → mach_msg),症状是「没有 CDP page」,
  与 #1076 归因给 hardened runtime 的那条是同一个东西;四臂一律加 --use-mock-keychain。
- codesign --force --deep 漏签 Electron Framework 的 Libraries/*.dylib,hardened 下 dyld 拒。
- #1076 §8 第 2 条(打包弄脏 43 个图标)已不成立:ac#1115 合入后该目录零追踪、打包后 git status 0 行。
- 新增残余:沙箱进程不能 exec setuid 二进制(ps/top/su 在围栏内 Operation not permitted),
  已回写基线 §5。

Refs #1144
@jinjunnn
jinjunnn merged commit 8a43800 into alpha Aug 26, 2026
5 of 6 checks passed
@jinjunnn
jinjunnn deleted the ac-1144 branch August 26, 2026 14:54
jinjunnn added a commit that referenced this pull request Aug 26, 2026
…/14 · 负向控制 8/8) (#1150)

上一轮 #1144 的取证跑在 c3c5830;#1147(ac#1129)在那之后合进 alpha,换掉了
session/tools.ts 里执行咽喉那一格(permission.ask → AlphaToolPolicyGate.gateToolExecution),
于是「这条链是否仍到得了 bash 工具」变成零证据。本轮只补这一格。

被测 sha 8a43800(= 构建时的 origin/alpha,含 #1147/#1148),四份结果 JSON 自报一致。
构建走仓内离线快照(MODELS_DEV_API_JSON,退出码单独取、built in 行数核为 3),
避开上一轮那次「build 退出 1 而 package:mac 照跑上一份 out/」的假成功。

- 围栏臂 ×2 轮:shell 工具 14/14 不落盘、prompt !command 14/14 不落盘,正样本落盘;
- 反向臂 ×2 轮(只改打包副本里 wrapper 那一行,ext sha 9cc40e89… 与前两轮逐字相同):
  两个驱动面各 14/14 落盘;
- 四轮各 21 pass / 0 fail;56 个 escape 格全部 processStarted=true,
  28 次工具调用 tool=bash / completed / commandIntact / 按 callID 认领;
- 负向控制 8/8:只加一个配置键 builtin::bash=deny,引擎下发的工具从 16 个降到 15 个
  (少的正是 bash)、什么都没落盘;allow 臂在同一脚本同一产物上跑通 —— 证明「链是通的」
  是真实测量而非空转。README §4 同时写清它**不能**区分新旧闸,那由产物出处回答;
- 产物出处:五个在 c3c5830 零命中、在 8a43800 命中的标记全部出现在 app.asar 里
  (grep -a;该文件有 2,689,313 个字面 NUL),并附手段自证的正/负样本。

未闭合项不变:AC1 字面的「真模型回合」仍缺模型凭据;本轮不跑 hardened 臂(AC2 结论仍以
上一轮为准,围栏与签名配方本区间未动)。

未改任何生产代码。取证 runner 沿用上一轮那一份,只修了它的 finally 里
spawnSync("git",…).stdout.trim() 在 cwd 消失时抛错、导致整轮结果写不出来的缺陷
(改成 ?.trim() ?? "";该值只进结果对象的 gitSha 字段,不参与任何 probe 判定)。

Refs #1144


Claude-Session: https://claude.ai/code/session_01QHDS2BFKuzm9uNH1svU2oj

Co-authored-by: jinjunnn <slmbaovanetti99@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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