这页分三层:我们现在怎么做(宿主原生边界、两个平台、持久开关、authority与25个执行面的当前分类)、
别人怎么做(references/下八个框架逐一,包括四个明确不做沙箱的)、
我们采用了什么(设计取舍、实施顺序、验收结果与已知限制)。
设计文档:docs/reference/design/runtime/sandbox.md
系统调用级实现位于openprogram/sandbox/__init__.py,统一入口是backend/local.py::_invocation。
bash、process、execute_code、cron direct、memory写入器的MCP shell和one-shot MCP均复用该入口;文件工具使用同一份写入根与hard-floor校验。
下面四节记录四个方向的边界、两个平台各自的机制、开关为什么现在传得到,以及固定权限档常量表如何参与25个执行面的判定。
执行面计数是审计清单,不代表U01—U23都应采用同一种隔离策略。
workspace-write。
authority与pairing决定外部请求是否具有执行能力,沙箱限制已获准agent对宿主产生的副作用,并保留宿主真实开发环境。
Docker不是当前沙箱实现,也不是后端不可用时的自动回退;以后只有出现明确的独立Linux环境需求时,才设计为用户显式选择的第二后端。四个方向不对称:读允许访问大部分宿主文件但拒绝凭证清单,写限制到工作目录,执行不限制可执行文件但子进程继承沙箱,网络在两个平台都禁用。 凭证清单与环境变量白名单已经预置,但Linux不能实现中段glob匹配。
同一个wrap_command在两个平台走完全不同的机制,各自额外放行了一些东西,也各自有一处只在实跑里才会暴露的细节。
/dev/null等五个字符设备用require-all加vnode-type CHARACTER-DEVICE放行读写。sysctl-read只允许hw.*和四个uname所需的kern.*名字;不再允许通用mach-lookup,剪贴板和Apple Events服务不可达。写范围只包含当前TMPDIR。(?:…),使用非捕获分组会导致deny规则不生效且没有错误;引擎匹配解析符号链接后的真实路径,所以静态前缀是符号链接时要同时生成原路径和真实路径规则。--perms 0000 --tmpfs遮蔽,文件用--ro-bind /dev/null替代。--cap-drop ALL移除DAC_OVERRIDE,所以容器内子进程是root时权限位仍然生效,实测返回Permission denied。--unshare-pid之后实测:沙箱内只看得到4个PID,读宿主进程的/proc/<pid>/environ报文件不存在,kill -9报进程不存在,宿主进程照常活着。没有它的话,洗干净子进程环境也没用。bwrap可执行文件首次使用前,以正式策略所需的mount、PID、IPC、UTS、network和capability限制执行/bin/true;已安装但被宿主禁止创建非特权user namespace时,按后端不可用处理。--tmpfs /tmp必须位于cwd bind之前,否则/tmp下的工作目录会被tmpfs遮蔽。拒绝访问的挂载还必须跳过宿主上不存在的路径:根是只读绑定,bubblewrap无法创建挂载点,强制挂载会使调用失败。两项均由运行测试确认。| 维度 | macOS | Linux |
|---|---|---|
| 网络 | 断(deny default兜住) | 断(--unshare-net) |
| 整盘可读 | 是,减去屏蔽清单 | 是,减去屏蔽清单 |
| 屏蔽清单表达力 | 正则,中间带通配符的glob也能表达 | 只能盖路径,**/.env这类无对应实现,被丢弃 |
| 写范围 | cwd + 当前TMPDIR + /tmp + /private/tmp | cwd + 隔离tmpfs /tmp |
| /tmp语义 | 真实/tmp,与宿主共享、可持久 | 空tmpfs,用完即弃 |
| exec限制 | 不限制,子进程继承profile | 不限制 |
| /dev/null可写 | 是 | 是 |
| 看得到宿主进程 | 否 | 否,PID命名空间已隔离 |
| 环境变量 | 白名单 | 白名单 |
| 工作目录被遮蔽 | 不会 | 不会,tmpfs先挂 |
| ps / top | 不能执行,setuid平台限制 | 正常 |
| 用的shell | /bin/bash(不开沙箱时是/bin/sh) | /bin/bash |
开关原来是一个ContextVar:设它的地方和真正执行命令的地方不在同一个上下文里,值就丢了,三条路径里两条在丢,其中一条是用户最常用的Web。
下图是改之前的传递,留在这里是因为它说明了为什么载体必须换。
copy_context()并不等价:
spawn出来的子agent确实能靠它把开关带进worker线程,但在followup线程上照样掉回默认值,这是实测的;嵌套CLI根本不是线程,补不了。
一个要跨进程存活的用户级设置,本来就不该挂在上下文变量上。
现在的载体是文件:策略在包装命令的那一刻从~/.openprogram/config.json的sandbox.*读出来。
文件不属于任何上下文,所以三个边界都跨得过去。
| 路径 | 改之前 | 改之后(实测) |
|---|---|---|
| Web:asyncio任务里设,普通线程里执行 | 读回false,命令无沙箱执行 | 线程内写工作目录之外被拒,$OPENAI_API_KEY长度为0 |
| CLI:REPL同线程 | 有效 | 有效,且跨会话保持 |
spawn:@agentic_function的子解释器 | 读回false,全部无沙箱执行 | 子进程内写工作目录之外被拒,$OPENAI_API_KEY长度为0 |
审批bypass:permission_mode="bypass" | 短路到工具自己的execute | 照样被包住:策略在权限层之下解析,实测写越界和读SSH私钥都失败 |
| 平台后端不可用 | 静默改为普通执行 | 工具缺失或隔离机制探测失败时默认refuse并说明原因;warn可选 |
sandbox.mode、writable_roots、deny_read、deny_write、network、pass_env、unavailable_policy,
都在config_schema.py的SETTINGS里,所以openprogram config、setup向导、TUI设置页和Web设置页一起有。
新安装默认mode=workspace-write;已有用户的显式danger-full-access保持不变。平台工具不可用时默认拒绝,不退回宿主执行。
authority_tier是请求边界上的owner/paired枚举;检查点用该枚举查询进程内写死的capability常量表。请求不能携带、创建或扩展capability列表。principal标识请求使用谁的授权以及谁能作出owner审批,不等于权限档。
permission rule或精确owner approval表达对本次操作的同意,SandboxPolicy限制操作实际可访问的宿主资源,不可取消的hard constraints独立于以上输入始终执行。字段定义与传播规则同时记录在speaker identity设计中。
allow = hard_constraints ∧ tier_capabilities[authority_tier].contains(capability) ∧ permission_or_exact_owner_approval ∧ enforcement_boundary。
请求只携带权限档枚举;capability集合只存在于进程内常量表。模型正文、speaker和工具参数都不能创建或扩展它。| 能力 | 允许的操作 | 默认归属 |
|---|---|---|
reply | 向当前会话返回文本,不产生其他宿主副作用 | owner与paired |
memory.source.append | 追加带speaker、authority_tier和信任状态的来源记录 | owner与paired;未配对消息不进入agent |
memory.trusted.promote | 把已审阅记录提升为owner轮次可自动召回的可信记忆 | 仅认证本地owner interactive |
schedule.createschedule.manage | 创建映射到schedule.create;修改、重排或删除映射到schedule.manage。创建或修改获批后保存新的不可变job capability | 仅认证本地owner interactive;属于宿主副作用 |
fs.read / fs.writeprocess.exec / network.send | 读取或修改文件、启动进程、向外部系统发送 | 仅owner;仍需规则、审批和执行边界 |
| 请求来源 | principal | authority tier | 缺失字段处理 |
|---|---|---|---|
| 认证本地Web / CLI / TUI | principal_id=ownerinteraction=interactive | owner | 入口必须主动构造;认证或owner配置无效时拒绝请求 |
| 已配对渠道 | 实例owner;稳定账号ID另存为speaker identity | paired | 只按稳定账号ID匹配;显示名不参与身份判断 |
| continuation / subagent | 显式继承caller principal | 显式继承同一档位;没有交互升级 | 序列化或恢复缺字段即状态错误并deny,不能依赖unknown fallback |
| cron触发 | 创建时批准的owner | 不可变job authority中的owner | 触发时不得重算为interactive;字段或hash不完整即deny |
| 未配对或无稳定ID的渠道消息 | 无agent principal | 无 | 不进入agent;返回配对码。群组消息另存为pending证据,私聊不归档 |
memory.source.append;paired档没有文件、进程、网络、调度、审批或runtime control能力。
未配对群组发言不进入agent,只归档为pending证据。pending证据当前仍可显式检索;hold队列准入和记忆读取过滤属于第二批,不能写成已实现。
只有认证本地owner能把已审阅记录执行memory.trusted.promote。由记忆内容引出的操作仍须通过当前轮次的权限档、权限规则、审批与沙箱检查,Writer和模型输出不能改变权限档或信任状态。实施前审计把范围固定为25个执行面:当时2个已受管、23个未统一受管。当前U01—U23已经逐项采用强制策略或记录受信边界与保留理由;一个执行面按共同的命令来源与安全边界计数,不按每次subprocess调用计数。
bash/process经local backend进入backend/local.py::_invocation;M02是memory写入器的MCP shell使用同一份SandboxPolicy。下面记录U01—U23的闭合方式。| 编号 | 实施前未统一受管的执行面 | 来源类 | 已实施边界 / 明确保留理由 |
|---|---|---|---|
| U01 | backend/docker.py | 模型命令 / 外部容器 | 声明容器是执行边界;命令仍经过工具规则、审批与审计,不宣称使用宿主sandbox |
| U02 | backend/ssh.py | 模型命令 / 远端主机 | 声明远端主机是执行边界;目标由owner配置,命令仍经过工具规则、审批与审计 |
| U03 | functions/tools/execute_code | 模型代码 | local路径通过LocalBackend.run复用_invocation;解释器、cwd与临时脚本均进入同一策略测试 |
| U04 | cron/worker.py direct command | 模型请求保存 / 无人值守 | 创建先检查schedule.create,修改、重排或删除先检查schedule.manage;paired档必须在任何job持久化变更前deny。认证本地owner批准创建或修改后保存新的不可变job authority,固化principal、authority_tier=owner、command、cwd、policy与hash;触发时强制沙箱,ask与升级均deny |
| U05 | cron/worker.py prompt job | 模型prompt / 无人值守 | 同样要求owner批准创建;重建请求时显式source=cron并加载不可变job authority,内部所有副作用继续按owner档常量表检查 |
| U06 | memory/agent_runtime/claude_code.py | 模型驱动的嵌套agent | 协议层关闭内置命令与文件工具,注入OpenProgram受管MCP工具 |
| U07 | functions/watcher.py → _registry.exec_module | 模型可写Python | 任何模型可写目录禁止自动导入;导入目录改为owner控制并记录来源 |
| U08 | webui/_functions.py动态重载 | 模型可写Python | 与U07共享同一条目录所有权约束;不能在宿主进程重载模型写入的模块 |
| U09 | agent/process_runner.py | 已注册agentic function | spawn请求显式序列化SandboxPolicy;子进程内的命令、文件与嵌套工具保持同一策略 |
| U10 | plugins/loader.py | owner安装的plugin | plugin清单与代码目录不可由模型修改;加载事件记录来源。trust默认untrusted且拒绝加载,只有owner显式提升信任的plugin才加载,并在宿主进程内执行(plugins/sandbox.py的subprocess隔离未实现,不宣称已进程隔离) |
| U11 | mcp/client.py stdio server | owner配置的server | MCP配置不可由模型静默改写;启动argv与环境显式保存并审计 |
| U12 | webui/routes/mcp.py one-shot | HTTP请求体 | 要求已认证owner请求并进入与MCP stdio一致的启动策略;删除“已沙箱”但实现未隔离的声明 |
| U13 | events/shell_hooks.py gate command | owner配置;模型事件作stdin | 命令本身按受信配置处理;配置目录受保护,超时或启动失败不能成为安全强制规则 |
| U14 | events/shell_hooks.py notifier command | owner配置;模型事件作stdin | 保持通知用途并审计;不得把notifier结果用于允许高风险操作 |
| U15 | providers/_shared/cli_backend/runner.py | 模型prompt / 外部CLI | 若接入生产,先声明CLI内置工具策略;保持未引用时不得计为现有保护 |
| U16 | functions/tools/grep | 模型参数 / 固定argv | 保持shell=False与--分隔;增加路径范围测试,不需要命令sandbox |
| U17 | functions/tools/worktree | 模型参数 / 固定git argv | 仅允许受管仓库与固定子命令;保持argv调用并测试路径规范化 |
| U18 | functions/tools/agent_browser | 模型参数 / 浏览器进程 | 浏览器profile与下载路径使用独立策略;启动参数保持argv并进入工具审批 |
| U19 | session/project/shadow store的git plumbing | 第一方确定性argv | 保留宿主执行;不接受模型shell字符串,测试参数分隔和仓库范围 |
| U20 | _cli_cmds/programs.py | owner主动安装 / 更新 | 保留管理员CLI权限;与agent工具不可互调,记录安装来源 |
| U21 | _cli_cmds/browser.py | owner主动安装 | 保留管理员CLI权限;安装命令不暴露给模型 |
| U22 | _cli_cmds/upgrade.py与updater | owner主动升级 | 保留管理员CLI权限;下载、替换和重启必须来自显式升级操作 |
| U23 | plugins/installer.py | owner主动安装 | 保留管理员CLI权限;安装入口不暴露给模型,安装后由U10约束加载 |
下面保留修复前的自动导入流程。它说明为什么只修改wrap_command不够:直接文件工具和进程内导入需要各自的强制边界。当前实现已经在文件工具写入检查和owner登记来源两处阻止该流程。
permission_mode="bypass"在自动危险工具检查之前返回,影响
bash、execute_code、process以及write/edit/apply_patch。
当前hard constraints与capability检查位于bypass之前;命令使用宿主原生沙箱,文件工具执行统一路径检查,subagent没有交互升级能力。| 实施前P0缺口 | 实施前源码行为 | 影响 | 当前闭合方式 |
|---|---|---|---|
| “Always allow”过宽 | 审批持久化时只保存工具名;一次bash批准会得到allow bash | 后续任意bash都可跳过审批,范围大于原命令 | 已完成:只保存规范化后的精确操作;复杂shell只允许单次批准 |
| 文件工具与watcher组合 | 文件工具可写工作区外;watcher对functions/agentics/变化调用exec_module | 模型可写Python并由宿主进程自动导入 | 已完成:文件工具统一写入根检查;自动导入只接受owner登记来源 |
| cron无人值守执行 | direct job使用Popen(..., shell=True);prompt job启动分离CLI | 触发时没有交互审批路径,也没有统一沙箱 | 已完成:签名执行spec、固化owner档、强制沙箱、ask与升级均deny |
| execute_code本地执行 | 模型给代码和解释器路径,随后subprocess.run | 不经过Backend._invocation | 已完成:复用local backend及同一份SandboxPolicy |
| 命令规则按原字符串前缀匹配 | 未先处理ANSI、NUL、Unicode NFKC或env X=1包装 | 保存规则与实际argv不一致;复杂shell文本也不能靠前缀安全解析 | 已完成:规范化、shell解析、透明env包装与精确规则 |
| runtime sandbox参数被忽略 | create_runtime(sandbox="read-only")的provider参数接受但不执行 | 调用方看到的配置不构成隔离 | 已完成:进程边界显式序列化并安装SandboxPolicy snapshot |
references/下八个框架逐一:claude-code、codex-cli、openclaw、opencode、
hermes-agent、pi-mono、pi-ai、weclaw。
其中四个完全不做系统级沙箱,这本身是一种设计立场,下面写清它们各自靠什么替代。
计数上有两点:pi-ai是pi-mono同上游的只读子集、没有执行面;
claude-code和pi-mono的扩展调的是同一个外部包,所以这一组里独立的系统调用级实现只有两份。
按隔离强度分四档。左边是主动移除隔离的,右边是把执行推到另一个内核视图或另一台机器的。 同一个框架出现两次,表示它跨档:左边是默认路径,右边是可选配置。
sandbox.mode=off,关闭时审批默认放行;
启用且选择backend=docker时创建Docker容器,默认network=none、root filesystem只读、cap-drop ALL并设置no-new-privileges。
它也支持SSH与插件提供的OpenShell后端,因此Docker配置不能用于描述所有后端,更不能用于描述其出厂执行状态。同样四个方向,加上一列子进程环境变量,因为凭证从环境里漏出去和从磁盘上漏出去是两条独立的路。 绿=收住,橙=部分,红=敞开,灰=不适用。
| 维度 | claude-code | codex-cli | openclaw | pi-mono | hermes-agent | opencode | weclaw | OpenProgram |
|---|---|---|---|---|---|---|---|---|
| 系统级隔离 | 外部runtime包 | 仓库内自研 | Docker默认;SSH/OpenShell可选 | 只有示例扩展 | 默认无,后端可选 | 明确不做 | 主动移除 | Seatbelt / bubblewrap,模型影响面已分类 |
| 默认开 | 否 | 是,read-only | 否,mode=off | 核心无此概念 | 否,默认local | — | — | 是,workspace-write |
| 粒度 | 全局×单命令opt-out | per-command,工具可覆盖 | 全局×agent×tool×session | 整工具级allowlist | per-pattern加按上下文 | per-tool×per-resource | 无 | 全局配置,尚无per-tool覆盖 |
| 配置入口 | 分层settings.json | config.toml加CLI flag | zod schema生成JSON schema | settings.json,项目覆盖全局 | cli-config.yaml加环境变量 | opencode.json加frontmatter | 没有安全相关的键 | 七个sandbox.*设置键 |
| 沙箱与审批 | 沙箱内bash免审批 | 里面被拒→问→不带沙箱重跑 | off时审批默认放行;启用后由容器与tool policy处理 | 核心没有审批 | 守卫就是审批系统 | 只有审批 | 审批自动答allow | scope先判定;仅本地owner可精确单次升级 |
| 凭证屏蔽出厂状态 | 引擎有,为空 | 引擎有,为空 | 启用时挂载源加环境变量过滤 | 扩展预置denyRead | 环境变量从注册表推导 | *.env→ask | 无 | deny-read清单加环境白名单 |
| 后端不可用 | 继续执行但明确告警 | 静默none,Windows降级 | 强制拒绝加doctor预警 | 四条路径都静默继续 | 非交互fail-open | — | — | 默认refuse,可显式warn |
| 审计 | 内核deny行归因后返回模型 | 结构化违规事件加OTel | 专用tool-policy logger | 无 | 审批生命周期钩子 | 权限事件总线 | 只有log.Printf | 结构化sandbox.violation及审批事件 |
| 资源限额 | 无 | 仅RLIMIT_CORE=0 | pids/memory/cpus/ulimits,默认不设 | 无 | pids 256、tmpfs限额、600秒 | 无 | 无超时 | CPU/内存/进程数配额不在范围内 |
| 受管范围 | 只有Bash和PowerShell | 只有子进程 | 启用容器内的一切 | 只有bash工具 | terminal和code_execution | — | — | 模型可影响的进程、文件、cron、MCP与嵌套工具面 |
这一轮新看的五家里,有十一种机制在claude-code和codex-cli里都不存在。
下面八张卡记录逐维度可采用的机制,并标出OpenProgram的采用状态和对应步骤。
子进程无网不表示读取到的凭证不会离开子进程:工具输出会返回agent runtime,并由宿主网络进入下一次模型请求。 因此凭证读取限制、环境过滤和执行面覆盖都必须独立存在。
沙箱批次S先完成第04步subagent bypass与第05A步cron触发隔离和U01—U23分类;随后完成身份批次I、第05B步cron创建授权、第06步嵌套agent、第07步审批联动与共享记忆pending/trusted跨scope约束;第08步将新安装默认值改为workspace-write。下面保留各步的参考来源、依赖和验收条件。
左列取自层一的实测结果,中列是层二里已有的做法,右列是它落在修复路线的哪一步。
| 图外的OpenProgram执行面 | 不能直接采用的处理 | 采用的处理 | 步骤 |
|---|---|---|---|
| write/edit/apply_patch | 把所有文件操作改写成shell | 共享路径规范化与写入根检查;禁止模型可写目录被自动导入 | 04 |
| execute_code | 继续直接subprocess.run | 本地路径复用LocalBackend.run及其_invocation和SandboxPolicy | 05A |
| cron direct / prompt | 让paired渠道直接保存job,或触发时尝试弹交互审批 | 05A先完成触发隔离、固定执行spec与ask→deny;身份批次I完成后,05B再让创建检查schedule.create,修改、重排或删除检查schedule.manage,paired渠道在持久化变更前deny | 05A / 05B |
| U01—U23执行面清单 | 用“其他执行点”概括或全部机械包装 | 逐项记录来源类、强制策略、保留理由与测试;模型可影响项全部是默认开启阻断项 | 05A |
| 嵌套Claude Code CLI | 把需要API网络的整个CLI放入无网沙箱 | 协议层拒绝内置文件/命令工具,注入OpenProgram受管替代工具 | 06 |
下列路线已经按依赖顺序实施:第04步先修复subagent bypass;第05A步完成cron触发隔离和U01—U23执行面;authority批次显式传播owner/paired档位;第05B、06、07步分别完成cron授权、嵌套agent受管工具和审批联动;第08步完成默认值切换。保留各步的设计与验收文字,作为实现记录。
--tmpfs /tmp放在cwd bind之前。/usr/sbin、2>/dev/null全部正常;/tmp下的工作目录保持可见。ps和top因setuid平台限制不能执行。pass_env的强制限制。Linux增加--unshare-pid。文件工具对agentics目录和agentic源注册表的禁写在策略之前判定,无条件成立;沙箱内命令的同一禁写由策略提供,danger-full-access下的shell面按该模式定义不设防。~/.openprogram/auth/**、keychain等具体路径不可读且不可删除;macOS的**/.env规则有效,Linux不具备同等glob表达力。164字符的OPENAI_API_KEY在子进程里是空串;Linux上宿主进程的/proc/<pid>/environ不可读,且不能向宿主PID发送信号。git hook和git config保持opt-in,因为禁止后会导致git init和git clone失败。sandbox.*解析,不依赖执行线程或调用链。SETTINGS增加七个键。后端可用性按实际隔离能力探测;工具缺失或所需namespace无法创建时默认拒绝并给出明确原因。wrap_command接收显式策略,但目前还没有按工具或按调用点给出不同策略的地方。permission_mode="bypass"判断之前,或在已经位于审批包装器外的tool.before层安装不可卸载的生产策略。该步骤不等待principal或scope字段;字段完成后,subagent只能继承caller scope的子集。subagent没有交互审批通道,命中危险命令、工作区外写入或ask规则时直接deny。source=agent_spawn与permission_mode=bypass轮次,分别调用bash、execute_code、process、write、edit和apply_patch;危险命令与越界写入在工具执行前被拒绝,安全只读调用仍可执行。测试必须证明默认生产安装点存在,不能只测试可选gate API。source=cron和固定非交互策略,内部ask与升级均deny。该部分不判断创建者身份。随后按第04节清单逐项处理U03—U09与U12;execute_code本地路径复用LocalBackend.run及其_invocation,模型可写目录不允许watcher或Web重载器自动导入。allowed_tools/can_use_tool拒绝内置Read、Write、Edit、Grep、Glob与命令工具;注入OpenProgram MCP替代工具,由宿主执行文件与命令策略。schedule.create,修改、重排和删除映射到schedule.manage。paired档的固定常量表不含这两项,必须在任何job持久化变更前deny。只有认证本地owner的精确批准可以生成新的不可变job authority;其中固化principal_id、creator speaker审计值、authority_tier=owner、command或prompt、cwd、SandboxPolicy与hash。触发时只能读取该记录,不能重算为interactive或改变权限档。owner+interactive;只有通过稳定账号ID配对的渠道请求才能构造paired,陌生账号不进入agent。continuation、Task、inbox、subagent和cron显式序列化同一权限档。每个工具操作先映射到capability并查询写死的档位常量表;该档不含能力时先deny。只有带approval.request的认证本地owner轮次可申请一次性、精确到operation/cwd/env/policy的能力,paired channel、cron和subagent不得升级。审批表达owner同意,SandboxPolicy表达执行约束,hard constraints不可批准取消。workspace-write仍可能修改或删除仓库文件,不能因为命令在沙箱内就统一免审批。沙箱违规必须产生结构化事件;“Always allow”保存规范化后的精确操作或受限pattern,不能只保存工具名。env包装;只有简单argv可保存pattern,复杂shell文本只允许单次批准。一次bash批准不能授权后续任意bash。任何设置或批准都不能取消hard constraints。workspace-write。保留现有用户明确设置;迁移只提示,不覆盖danger-full-access。Windows没有宿主原生后端,沙箱启用时默认拒绝,不能显示为已启用后直接在宿主执行。| 默认开启真实命令矩阵 | 必须实际执行的代表命令 | 通过条件 |
|---|---|---|
| Git | git init、本地git clone、status、diff、add、commit | 仓库内产物正确;hook与config行为符合显式策略;无工作区外写入 |
| Python | python -m venv .venv、python -m pytest、脚本读写/tmp与cwd | 常规项目可运行;凭证路径、宿主进程环境和工作区外目标仍被拒绝 |
| npm | npm ci --offline、npm test、npm run build | 本地依赖和构建正常;需要公网的安装产生明确网络拒绝或精确审批,不允许静默宿主执行 |
| make | make及包含编译器、子shell和临时文件的fixture | 构建成功,子进程继续受同一profile约束 |
| conda | macOS执行conda info --json;Linux使用只读base cache执行conda create --clone ... --offline -p ./env和conda run | 工作区内环境可创建和使用;HOME、env和可写cache显式位于工作区,全局位置保持只读 |
| 路径与平台 | 普通cwd、带空格cwd、/tmp cwd;macOS Seatbelt与Linux bubblewrap各一轮 | 结果一致;已知ps/top与Linux中段glob限制单独报告,不以跳过表示通过 |
signal和process-info*;
Linux加--new-session --die-with-parent --unshare-pid --unshare-ipc --unshare-uts --cap-drop ALL;functions/agentics/禁写;两条沙箱路径统一用/bin/bash;
macOS的sysctl-read已收窄到硬件前缀和四个uname名字,通用mach-lookup已删除,/private/var/folders已限制到本进程TMPDIR。
--unshare-user不在清单里:非setuid的bubblewrap构建自己就会建用户命名空间,setuid的构建不接受这个参数。shell使用暂存目录和同一份SandboxPolicy;
Claude Code CLI继续在外部进程访问Anthropic API,但其内置Read/Write/Edit/Grep/Glob与命令工具已由协议配置禁用。
文件和命令操作改用OpenProgram提供的受管MCP工具,因此副作用回到宿主的路径、scope、审批和沙箱判定。记录日期:2026-08-10。第01—08步与身份批次I均已实现;新安装默认workspace-write,已有显式danger-full-access不被迁移覆盖。状态以当前代码、完整单元测试和双平台真实子进程矩阵为准。
| 步骤 | 状态 | 实现与验证结果 | 剩余限制 / 记录 |
|---|---|---|---|
| 01 可用性 | 已完成 | 常用编译、Git、Python与/tmp工作区已验证;Linux会执行namespace能力探测 | macOS ps/top限制保留 |
| 02 凭证屏蔽 | 已完成 | 默认deny-read、环境白名单、Linux pid隔离已验证 | Linux中段glob不能声明为已强制 |
| 03 开关语义 | 已完成 | Web线程、spawn与bypass bash都能读取有效配置;不可用默认refuse | 当前仍为全局策略,没有per-tool覆盖 |
| 04 subagent强制约束 | 已完成 | hard constraint和capability检查位于bypass之前;bash/process/execute_code及文件工具的越界操作在执行前拒绝 | subagent始终非交互,不能申请升级 |
| 05A cron触发与执行面 | 已完成 | cron保存签名不可变执行spec,worker强制sandbox并记录policy hash;execute_code、U03—U09和U12均已处理 | U01/U02使用配置的Docker或SSH执行后端自身边界,不计为宿主原生沙箱 |
| 06 嵌套agent | 已完成 | Claude Code内置副作用工具被禁用,文件与命令改用OpenProgram受管MCP工具 | CLI的API网络仍保留在外部进程 |
| I authority scope依赖 | 已完成 | owner principal、speaker、interaction和capability集合显式进入TurnRequest、Task、inbox、continuation、subagent与spawn snapshot | 恢复缺字段时对宿主副作用deny,不使用ContextVar补值 |
| 05B cron创建与管理 | 已完成 | 创建消费schedule.create,修改/重排/删除消费schedule.manage;job capability固化owner、scope、policy与内容hash | paired渠道在持久化改变前deny |
| 07 审批联动 | 已完成 | scope先判定;违规结构化;只有本地interactive owner可精确单次升级;Always allow精确到规范化operation | hard floor与凭证环境过滤不能被批准取消 |
| 08 默认开启 | 已完成 | sandbox.mode默认workspace-write;macOS与Linux的git/python/npm/make/conda及拒绝矩阵通过 | 保留显式danger-full-access;未知平台默认refuse |
ps和top不能在Seatbelt profile内执行。**/.env这类中段glob不能直接强制;该规则只在macOS正则profile中有效。/absolute/path/to/secrets/**这类具有确定前缀的目录级deny。不能依赖**/.env声明Linux已覆盖。| 其他已知限制 | 当前行为 | 范围决定 |
|---|---|---|
| Windows | 没有宿主原生沙箱后端;沙箱开启时默认拒绝执行。只有owner显式关闭沙箱或选择不安全的unavailable_policy=warn才会改变该行为 | 不自动选择Docker,不把普通宿主执行显示为已启用沙箱 |
| per-tool覆盖 | 沙箱策略当前以安装为粒度;工具仍分别执行authority、权限规则、审批与hard constraints | 本批次不增加per-tool SandboxPolicy覆盖 |
| CPU / 内存 / 进程数 | 有命令timeout和Linux namespace,但没有资源配额 | 明确不在范围内;PID namespace不能描述为进程数限制 |
LocalBackend + sandbox-exec下通过git clone/status/diff/add/commit、Python venv、npm ci offline/test/build、make/clang、conda info和uname;.env、工作区外写入、网络与剪贴板访问均被拒绝。
Linux在一次性Docker Linux内核环境中执行真实bubblewrap,通过git、Python venv、npm、make、使用只读base cache的工作区conda clone/run,以及凭证拒读、工作区外拒写和网络拒绝;容器随后删除。Docker只用于Linux分支验证,不是OpenProgram沙箱实现。