| 项 | 内容 |
|---|---|
| 状态 | 已采纳(Accepted) |
| 日期 | 2026-10-21 |
| 背景 | 隔离档位这套机制要成立,前提是「被执行的程序都在某个已登记的后端里跑」;档位是后端自报的,只活在代码里则 review 时看不见、改了没人响、标高了没人管。 |
| 决策 | 两条 error 级规则:harness-sandbox-execution-single-entry(os/exec 只允许出现在 sandbox 包内)与 harness-sandbox-provider-declared(后端档位必须在 policy 的 sandbox_providers 段登记且不与代码事实漂移)。 |
| 后果 | 绕过登记簿直接起外部进程的路径被封死(那种绕过没有任何信号:构建全绿、审计日志只有一条普通进程记录);档位声明可见、不漂移、有签字(人 review 时看得见「它凭什么说自己有这一档」)。 |
| 依据 | architecture-policy.yaml 的 sandbox_providers / execution_paths 段 + archcheck/sandbox.go / archcheck/execution.go;两条均已反向验证(注入违规 → 报错 + 退出码 1)。 |
> 一句话结论:执行只允许经 sandbox 登记簿,档位声明必须有人签字——否则「宿主声明的强度」与「实际执行的强度」之间没有信号。
> 本文件正文原样搬自 README「执行沙箱:入口唯一 + 档位登记(2026-10-21 补齐)」一节。
执行沙箱:入口唯一 + 档位登记(2026-10-21 补齐)
| 规则 | 级别 | 拦什么 |
|---|---|---|
harness-sandbox-execution-single-entry | error | 生产代码在 sandbox 包之外直接 import "os/exec"——外部程序执行只允许经 sandbox 包登记的后端 |
harness-sandbox-provider-declared | error | 执行沙箱后端的隔离档位没登记在 sandbox_providers 段,或登记的档位与代码事实不一致 |
为什么需要它:隔离档位这套机制要成立,前提是「被执行的程序都在某个已登记 的后端里跑」。一旦某个包绕过登记簿直接起一个外部进程,那次执行就没有档位—— 宿主按「它在受控后端里」做的风险判断,对那次执行全部落空,而没有任何信号: 构建全绿、测试全绿、审计日志里只有一条普通的进程记录。
这类绕过最常见的形态是 os/exec(Go 里起外部进程的标准入口),加进某个包 只要一行。于是本规则把「起外部进程」收口到 sandbox 包,其余生产代码一律不得 直接 import。判定面是 import 路径(走 go/ast),单行/块/别名/空白导入 一视同仁。
已知边界(写明,而不是假装覆盖):本规则只拦 os/exec 这一个标准入口。 直接用 syscall 起进程的形态拦不住——那需要拦整个 syscall 面,代价远大于 收益,且当前本仓生产代码零 os/exec。规则拦的是常见形态,不是全部形态。
已做反向验证(注入违规 → 报错 + 退出码 1;同一形态放进 sandbox 包不报)。
第二条(档位登记)为什么需要:档位是后端自报的。若那份声明只活在代码里, 它有三重失效:review 时看不见(档位是实现里的一个常量,混在方法之间)、改了没人 会响(漂移)、标高了没人管(后端自称「内核级」其实只是进程内解释器,宿主据此做的 风险判断全部落空)。
于是 policy 的 sandbox_providers 段成了档位声明的签字处:每个后端一条登记 (标识 / 档位 / 所在包),「它凭什么说自己有这一档」写在该条的 notes 里—— 那是人 review 时会看到的地方。门禁核对两件事(走 go/ast):登记 ↔ 代码事实 一致(档位漂移 = 代码改了登记没改)、实现 ↔ 登记齐全(未登记的后端, 它的档位声明不在被复核的清单里)。
这条门禁判不了什么:「后端实际提供的隔离强度是否兑现其声明」——那要审实现, 机械判不了。它保证的是三件事:声明可见(在 policy 里)、不漂移(门禁核对)、 有签字(人 review 时看到那一行)。
已做反向验证:改代码里的档位 → 红在 drift 上;删掉登记 → 红在 unregistered 上;撤销后归零。