feat(sandbox): 统一 OS 沙箱与 Windows workspace-write 后端 - #530
Conversation
新增命令执行方式(commandSafetyMode),在聊天输入栏工具条切换,单一互斥维度: - 逐条审批(ask):非只读工具每次执行前都请求批准 - 自动执行(auto,默认):按设置中的工具权限策略直接执行 - 沙箱(sandbox):Bash/ManagedProcess 在 OS 级沙箱内执行 - 沙箱·断网(sandboxOffline):在沙箱基础上禁止命令联网 沙箱实现(runtime/sandbox.rs): - macOS:/usr/bin/sandbox-exec + 动态 Seatbelt profile(allow-default + 全盘写 deny + 工作区/临时目录写 allow + 敏感目录读 deny + 工作区 re-allow, 规则顺序有单测锁定) - Linux:bwrap(--ro-bind / / + 工作区 bind + tmpfs 掩蔽敏感目录 + 可选 --unshare-net),带真实可用性探测 - Windows:暂不支持(受限令牌 + Job Object + WFP 路线待实现),选择器中 沙箱两项禁用并提示,执行层 fail-closed 兜底 - 掩蔽范围:~/.ssh、~/.aws、~/.gnupg、~/.config/gh、~/.liveagent - 拦截点:spawn_platform_shell_command 唯一漏斗(Bash + ManagedProcess 共用), 沙箱包裹失败直接报错,绝不静默降级 - Hook/Cron 脚本为用户显式配置的自动化,不套沙箱 配套: - ShellRunResponse 新增 sandbox 字段,Bash 结果头部显示 sandbox: seatbelt - Bash 工具描述在沙箱开启时告知模型围栏规则 - system_sandbox_capability 命令 + adapters 探测(WebUI 返回 null 透传) - 设置持久化(含保存白名单)、GUI/WebUI settings 双份同步、i18n 四 block 同加 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…besandbox # Conflicts: # crates/agent-gateway/web/src/app/GatewayApp.tsx # crates/agent-gateway/web/src/i18n/config.ts # crates/agent-gateway/web/src/lib/settings/index.ts # crates/agent-gui/src-tauri/src/lib.rs # crates/agent-gui/src/i18n/config.ts # crates/agent-gui/src/lib/settings/index.ts # crates/agent-ui/src/pages/chat/ChatComposerBar.tsx
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ss lifecycle Address two P1 review blockers on the sandbox write fence: P1#2 — Workspace root could re-expose sensitive dirs. The write fence re-allows write_root last (macOS re-allow rule, Linux --bind after --tmpfs), so a workspace of $HOME or / would re-open ~/.ssh, ~/.liveagent, etc. Add a fail-closed validate_workspace() in the shared wrap_command choke point: reject any workspace that encloses/equals a sensitive dir, and any workspace nested inside a credential dir (~/.ssh/.aws/.gnupg/.config/gh). The app-config dir (~/.liveagent) is exempt in the descendant direction so the default workspace ~/.liveagent/default-project still runs. P1#3 — isolated ManagedProcess lifecycle was broken on Linux. bwrap always added --die-with-parent, killing isolated long-lived processes when LiveAgent exits. Thread an `isolated` flag through SandboxSpec (default false for the Bash tool path, true for isolated managed processes) and omit --die-with-parent when set, mirroring the existing conditional --unshare-net. Adds cross-platform tests for workspace validation and the bwrap flag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
远端 WebUI / 队列 / 编辑重发的请求经网关下发时,useSendChatTurn 硬编码使用 本地 settings.system.commandSafetyMode,忽略了请求自带的安全模式,导致远端 选定的沙箱模式无法生效。 - proto: ChatRequest 新增 command_safety_mode 字段(regen Go/TS/Rust) - Go 网关: NormalizeCommandSafetyMode 白名单归一(非法/空→"")并全链路透传 - WebUI: buildGatewaySystemSettings / payload / adapters 补齐字段 - 桌面端: 网关事件 → bridge override → useSendChatTurn 优先级链 (override ?? 网关直带 ?? 本地设置),fail-closed 回落本地而非默认 auto - 队列: QueuedChatTurn 顶层携带 commandSafetyMode,保留入队时的安全模式 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…besandbox # Conflicts: # crates/agent-gui/src-tauri/src/commands/runtime/shell.rs # crates/agent-gui/src/lib/tools/shellTools.ts # crates/agent-gui/src/pages/ChatPage.tsx # crates/agent-ui/src/pages/chat/ChatComposerBar.tsx
在基础 OS 沙箱之上实现 Windows 端写围栏,替换原先的 unsupported 桩。 免管理员、零配置、fail-closed,语义为 workspace-write。 机制(Option A,自我再执行 launcher): - sandbox.rs windows `wrap_command` 返回 (current_exe, ["__sandbox_exec","--write-root",root,"--",program,args...]) - windows_sandbox.rs `run_sandbox_launcher_if_requested()` 作为 run() 首行(Tauri 初始化前)识别 marker:盖 (OI)(CI) 继承写 ACE(合成 workspace SID)+ 建 WRITE_RESTRICTED 受限令牌(合成 SID 作 restricting SID ⇒ 广读、写限工作区)+ CreateProcessAsUserW 拉真实 shell, WaitForSingleObject(INFINITE) 后透传退出码;任何失败退 127,绝不直跑。 - Job Object KILL_ON_JOB_CLOSE 兜底;launcher 阻塞等待使 taskkill /T 沿完整 PPID 链级联,进程树 kill 不受多一跳影响。 能力/门控: - capability() 返回 supported:true, mechanism:"restricted-token", network_control:false。sandbox 模式在 Windows 放开;sandboxOffline (断网 + 读掩蔽免管理员做不到)保持 fail-closed 禁用,执行层对 !allow_network 直接报错兜底(设置可能自 macOS 同步)。 - 前端 CommandSafetyModeSelector 改为按 network_control 单独禁用 sandboxOffline,sandbox 仍可用;i18n 增 sandboxOfflineUnavailable。 - SandboxCapability 增 network_control:boolean,Rust→双端 TS adapter 贯通。 安全加固(采纳两轮 review): - resolve_program_in_path:lpApplicationName 用 PATH 内绝对路径解析, 工作区 cwd 绝不被搜索(挡 planted-binary),argv[0] 保留原名。 - ensure_write_ace 跳过 null-DACL(避免 deny-all)、setup_fenced_temp 拒绝 reparse point、INVALID_HANDLE_VALUE 守卫。 验证:probe crate cargo check --target x86_64-pc-windows-msvc 零错误; host cargo test --lib sandbox 12/12;GUI 715 / WebUI 578 前端测试 + 双端 tsc + biome 全绿。⚠️ token/ACE/CreateProcessAsUserW 的 unsafe FFI 仅编译 级验证,从未在真 Windows 上功能测试,合入前需真机验证边界。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…518 沙箱可选并被选中(✓),沙箱·断网因 Windows 免管理员方案无法断网而 fail-closed 禁用。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…besandbox Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # crates/agent-gateway/web/src/lib/proto/gen/proto/v2/gateway_pb.ts
…h DLL-init failure Two Windows sandbox changes, both admin-free: 1. Fix Git Bash dying with 0xC0000142 (STATUS_DLL_INIT_FAILED) under the restricted-token sandbox: append a logon-SID GENERIC_ALL ACE to the restricted token's default DACL (Chromium AddSidToDefaultDacl pattern) so msys/cygwin named kernel objects pass the write-restricted second access check; set lpDesktop explicitly; replace bInheritHandles=TRUE with STARTUPINFOEXW + PROC_THREAD_ATTRIBUTE_HANDLE_LIST (3 std handles only); translate loader NTSTATUS exit codes into readable stderr hints. shell_runner additionally probes each candidate once per (shell, mechanism) under sandbox mode and falls through to pwsh/powershell/cmd on loader-level incompatibility instead of returning a dead shell. 2. sandboxOffline now works on Windows: zero-capability AppContainer (CreateProcessW + SECURITY_CAPABILITIES) gives kernel-enforced network denial including loopback via WFP, no elevation needed; write fence reuses the inheritable-ACE stamping with the AC SID as trustee, and AC default-deny read masks sensitive dirs as a side benefit. Offline env overlay (blackhole proxy, CARGO_NET_OFFLINE, PIP_NO_INDEX, NPM_CONFIG_OFFLINE) added as defense in depth. Launcher protocol gains --net on|off and --isolated; isolated processes now skip the KILL_ON_JOB_CLOSE job object across the re-exec boundary (previously the flag was dropped, matching bwrap --die-with-parent semantics on Linux).
network_control is now true on Windows, so the capability-driven selector enables sandboxOffline there; drop the hardcoded "Windows can't sever network" from the unavailable hints (kept generic for platforms that still lack network control) and refresh the CommandSafetyMode comment.
|
对 一、沙箱围栏可被绕过1. 模型可经 MCP 工具拿到无围栏的进程 spawn 原语 —
更关键的是 2. cron bash 任务完全不受沙箱约束,且重启后仍在 —
用户为了约束一个不可信任务而选择 3. 沙箱开关完全由渲染进程的两个 bool 决定,后端无独立下限 —
仓库里已有正确范式可循: 二、fail-open:失效路径未按 fail-closed 收敛4. Windows 其余平台都做真实探测:macOS 在 理解注释的论证是两个后端免依赖免提权、恒可用。但若 5.
于是当 6. 未识别的安全模式静默降级为
任何未来新增的模式值、任何回退到旧版本、任何手改配置的笔误,都会解析成宽松的 三、UI 安全承诺与 Windows 联网后端实现不符7. 两种语言的文案都承诺"~/.ssh 等敏感目录不可读",但 Windows 联网后端(restricted-token)没有实现任何读掩蔽 — macOS 用 问题在于文案把一个已知缺口转成了肯定的安全承诺:Windows 用户读到该描述,据此认为凭据受保护,于是在 sandbox 模式下运行不可信的模型生成代码。这段代码可以随意读取 四、Windows 后端实现细节8.
另外 五、设计问题(非缺陷,供讨论)9. 安全模式优先级链缺少 max-strictness 钳制 —
但链路上没有任何一处与本地设置做严格度比较。WebUI 经 审查覆盖沙箱边界相关改动;未逐行审查 UI 文案与文档的其余部分。若某条判断有误请指出。 |
Address PR review findings on the unified sandbox work. - MCP: McpManager no longer doubles as an unfenced process spawn primitive. test/tools/restart/diagnose now refuse stdio runtime probes while the OS sandbox is active (both inline and persisted servers), before any IPC. http/sse probes and the Settings UI path are unaffected. - Cron: bash tasks used to run with sandbox_options=None, i.e. a persistent, restart-surviving bypass. They now share the same backend floor as Bash and ManagedProcess. - Backend floor: sandbox flags from the renderer can only tighten, never relax. The backend re-reads the persisted commandSafetyMode at the command boundary and takes the stricter side; failure to read it is fail-closed. Applied to shell_run, shell_session_start, managed_process_start and cron. - Windows capability is now probed at runtime instead of hardcoding supported: true. Both backends build a real security context (restricted token + default DACL, AppContainer SID derivation), so the fail-closed guard in wrap_command is actually reachable on Windows. - validate_workspace normalizes verbatim prefixes (\\?\, \\?\UNC\) and folds case on Windows before the lexical comparison; mismatched prefix forms used to make the fence check silently pass. - Unrecognized commandSafetyMode values now fail closed to "ask" instead of silently downgrading to "auto" (Rust and frontend, same semantics). - The Windows networked backend fences writes only, so the mode copy no longer promises credential read masking on that platform. - The self-reexec launcher calls validate_workspace too, keeping both entry points symmetric. Documented why write ACEs are grant-only. - Command safety mode from remote/queued snapshots is clamped to the stricter of itself and the local setting, so a stale snapshot cannot widen it.
|
PR governance checks passed. Awaiting human review. |
Two conflicts, both from files that main also touched: - crates/agent-gui/src-tauri/Cargo.toml: our windows-sys feature list is a superset of main's, so keep ours and add main's new [dev-dependencies]. - crates/agent-gateway/web/src/lib/proto/gen/proto/v2/gateway_pb.ts: generated base64 descriptor, not hand-mergeable. gateway.proto itself merged cleanly (our ChatRequest.command_safety_mode plus main's TrajectoryFetch messages), so the generated Go and TS output was regenerated from the merged schema with the repo-pinned buf 1.71.0 / protoc-gen-go 1.36.11 instead of resolving by hand. Verified: cargo check --tests, cargo test --lib (921 passed), go build for the proto/protocol/session packages, tsc --noEmit for both frontends, test:gui (2112 passed) and test:webui (619 passed).
Keep Darwin temp parent promotion on /var/folders/*/*/T only, refuse McpManager stdio create/update/enable while the OS sandbox is on, and resolve bwrap to a system absolute path outside the workspace. Treat unknown capability as lacking read masking and stop telling the model that Windows networked sandbox hides ~/.ssh. Co-authored-by: Cursor <cursoragent@cursor.com>
|
跟进审查结论,已在 P1
P2
P3
验证
Windows 受限令牌 / AppContainer / |
The 0xE0434352 / BCrypt.dll crash is a write-fence denial on the user certificate store, not a broken pwsh install. Stamp a narrow CNG ACE surface, treat CLR crypto-init exits as probe failures, and refuse WindowsApps binaries. Co-authored-by: Cursor <cursoragent@cursor.com>
…exe on Windows pwsh dies with 0xE0434352 under the Windows sandbox. That code is a CLR unhandled exception, not a loader failure: the PE loader and the native CLR came up fine, then managed init failed reaching BCrypt/CNG, which a WRITE_RESTRICTED token cannot open for write. So the pwsh install is fine; the sandbox is the cause, and there is no warmup window to work around it (Chromium's trick needs the process to start with a normal token and drop privileges itself, whereas we build the restricted token in the parent and CreateProcessAsUserW a fresh shell). - Classify 0xE0434352 as sandbox-incompatible. The probe only matched the three loader NTSTATUS values, so it judged a guaranteed-dead pwsh "usable": the fallback chain never advanced, every command failed with the same code, and the wrong verdict was cached process-wide until restart. Kept the loader set as its own predicate so diagnostics can still tell the two failure layers apart. - Order cmd.exe first when sandboxed. It is the only fully native candidate; Git Bash needs the MSYS runtime, pwsh is CoreCLR and powershell.exe is .NET Framework, so the old comment claiming the non-Git-Bash candidates were "native PE, always usable" was wrong. This also drops the ~6s worst case of probing three doomed candidates on the first command. Unsandboxed order is unchanged. All three spawn paths (Bash, shell sessions, ManagedProcess) share spawn_platform_shell_command, so they all pick this up. - Translate 0xE0434352 into an actionable bilingual hint, alongside the existing loader ones, and document the limitation on the module. - This covers both backends: the probe verdict is mechanism-agnostic and its cache is keyed by (program, mechanism), so pwsh is judged independently under restricted-token and AppContainer. Verified: cargo test --lib (922 passed), cargo clippy --tests clean for both files. The Windows-gated code is parse-checked via rustfmt only - cross-checking x86_64-pc-windows-msvc needs MSVC headers that this host lacks. AppContainer's actual CNG behaviour is still unconfirmed on real hardware; cmd.exe-first is the conservative choice there and can be narrowed to the restricted-token backend if pwsh turns out to work under AppContainer.
|
跟进 Windows 沙箱里 PowerShell 启动即死(日志写 这不是本机 改动
验证
|
su-fen's 5fd878d lands the same 0xE0434352 bug with a better diagnosis, so both conflicting files are resolved wholesale in their favour and the merged tree is byte-identical to origin. Why mine stepped aside: I had placed the denial on the \Device\CNG device object and concluded it was unfixable, so a53b3f1 only reordered cmd.exe to the front. The real denial is on the user certificate store (HKCU\...\SystemCertificates and %APPDATA%\Microsoft\Crypto), which is fixable - they stamp a narrow write ACE there and keep pwsh working, with the probe left as a safety net. Forcing cmd.exe first would now be a regression: it permanently downgrades the shell even though pwsh is usable again. Their version is also strictly more complete (it catches NTE_PROVIDER_DLL_FAIL 0x8009001D, which I missed, and refuses WindowsApps binaries) and carries equivalent tests. a53b3f1 is kept on the scratch/cmd-first-reorder branch for reference only; it is not intended to land. Verified after the merge: cargo test --lib (928 passed), test:gui (2115 passed), rustfmt parses both Windows-gated files. Cross-compiling to x86_64-pc-windows-msvc still needs MSVC headers this host lacks, so the Win32 paths remain unverified here.
…e list 5fd878d's new doc comment ends a bullet list with an unindented follow-up line, so rustdoc folds it into the last item and clippy::doc_lazy_continuation fires. Blank doc line only; no wording or logic change.
|
已推送修复提交 根因断网 AppContainer 之前直接向工作区写入 package SID ACE。该 ACL 会与普通联网 Low-IL 沙箱的访问模型冲突,导致后续 Bash / ManagedProcess 甚至无法列目录,而不只是“禁止写入”。 修复
验证
|
…ectors Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Closes #506
Closes #520
Summary
将基础 OS 沙箱与 Windows workspace-write 后端合并为一个面向
main的 PR,并修复模型驱动 shell 的可恢复 Bash 绕过问题。根因是 chat 的 Bash 在启用 resumable session 时走
shell_session_start,旧链路没有传递 sandbox policy,Rust 最终以sandbox_spec=None启动普通 shell。RANDOM、&&、Git Bash 和D:/...路径本身都不是绕过技巧;缺失的策略参数才是漏洞。现在一次性 Bash、resumable Bash 和 ManagedProcess 共用同一个平台 spawn funnel。resumable session 初始启动和后续
ProcessWait都保持同一隔离进程,响应返回实际生效的机制,便于审计。本 PR supersedes #505 和 #518;两个旧 PR 已关闭。
Coverage Matrix
useSendChatTurn->runAgentConversationTurn-> builtin registrysandbox/sandboxOfflinereaches every shell toolcommand_safety_mode-> desktop listener -> turnExplicit user-authored cron scripts and hooks remain outside this model-driven Bash contract and retain their existing explicit-script semantics.
Change Scope
sandboxPolicy.ts.shell_run,shell_session_start, andmanaged_process_startalways send explicitsandboxandsandbox_allow_networkbooleans.sandbox-exec); Linux uses bubblewrap (bwrap).bwrapis resolved to an absolute system path; workspace PATH entries are never used as the wrapper.Screenshots / Preview
Default command mode
Command mode menu
Sandbox mode selected
Windows composer with workspace-write sandbox
Windows 下拉截图已撤下:旧图仍显示
sandboxOffline不可用,且文案承诺~/.ssh不可读。AppContainer 落地后这两点都过时了,需在真 Windows 主机上重拍(含sandboxDescNoReadMask)。Verification
cargo test --lib sandbox— 29 passed, including Darwin temp-parent layout, bwrap PATH pinning, and Unix workspace-write E2Egit diff --check— passedWindows FFI is compile-checked and covered by pure-logic tests, but a real Windows host/Tauri launcher E2E is still required before release.
Pre-submit Checklist
feature/sandbox-unified