DSH / Atlas
2026-09-11implementedarchitecture

Sandboxed Node execution for PTC

PTC 的沙箱 Node 执行

A Node worker isolates JavaScript state but does not apply the calling Session's OS sandbox policy. Model code can import filesystem and subprocess APIs directly, bypassing the tool-policy path even when nested `tools.*` calls receive the correct checks. Terminating the worker also does not establish that its child processes have stopped. The [PTC foundation](../feature/2026-06-15-ptc.md) remains responsible for regi

English

Problem

A Node worker isolates JavaScript state but does not apply the calling Session's OS sandbox policy. Model code can import filesystem and subprocess APIs directly, bypassing the tool-policy path even when nested tools.* calls receive the correct checks. Terminating the worker also does not establish that its child processes have stopped.

The PTC foundation remains responsible for registry presentation, generated bindings, dispatch logging and one-shot settlement. This decision supersedes its worker-based execution, trust and budget realization while preserving those consumer rules.

Decision

dsh-ptc-runtime-node in its default per-call mode runs each program in one fresh Node process. The host resolves execution choices, confines the launch through the same ctx.sandbox provider as Bash, and gives process lifetime to ctx.subprocess. The child evaluates erasable TypeScript with direct Node APIs, an empty model environment and host-provided asynchronous bindings. No worker or persistent kernel remains after a per-call execution.

The host preserves ELECTRON_RUN_AS_NODE for child startup; the bootstrap removes it from the native environment before evaluation, and model-visible process.env stays empty. Nested Electron launches require their own explicit Node-mode selection. Desktop uses Electron as its Node executable; removing this selector launches Electron's application path instead of the PTC bootstrap. Sandbox permission changes cannot repair that launch mismatch. The macOS Desktop regression uses real Electron to verify binding writes, direct workspace writes, and rejection of writes outside the workspace under restricted policy. It requires an installed Electron binary; ordinary runtime tests cover environment filtering without that dependency.

Resolved inputs and policy

PtcRuntime.resolve(request) validates supported options and supplies a complete PtcRunSpec; run(spec) does not introduce defaults. PTC passes the calling Session's cwd and resolved standing policy. Direct runtime callers receive deployment defaults through the same resolver. The filesystem and subprocess providers share one execution world, and bootstrap paths cross through the filesystem's explicit host-file mapping or a configured preinstalled bootstrap.

File mode, observed denial and enforcement completeness travel in PtcRunResult.sandbox separately from the program outcome. Restricted execution fails if the required sandbox backend cannot launch. Full access is an explicit policy mode. Program success does not prove full enforcement, and neither the process descriptor nor the extra control pipe claims multi-tenant isolation.

The experimental Python provider shares the resolved file policy and sandbox diagnostics while retaining its local Unix execution and configured wall deadline. Per-call timeout overrides remain unsupported.

Control and lifetime

The subprocess owner supplies a dedicated inherited binary control channel, separate from program stdout/stderr and launcher lifecycle IPC. The host bounds frames, queued writes, pending calls and outstanding argument bytes, then validates call identity and the binding allowlist before dispatch. Model code can write to that channel, so its bytes remain untrusted.

Program completion, timeout, cancellation and protocol failure all close execution through the managed process owner. Result selection stops the execution timer; cleanup then waits for the direct outcome and managed-range quiescence. The PTC bridge separately aborts and drains nested tool dispatches before its outer tool result settles. A denial or transport failure never automatically replays a program whose effects may already have occurred.

Resource limits

The default elapsed deadline is 120 seconds, capped at 600 seconds by default. Trusted service consumers may request timeoutMs: null to disable this timer; workflow sandbox reuse owns that caller-controlled lifetime. Omitted and numeric requests, including model-facing run_code, retain the numeric defaults and caps. An enabled deadline includes runtime setup and nested tool or approval waits. V8 old-generation memory, serialized outer output and control traffic have separate configured bounds. The heap limit excludes native allocations and descendant memory, and elapsed time is not a process-tree CPU budget.

The opt-in in-memory execution mode retains disposable memory under the same file policy; retained-process permissions govern policy changes and temporary escalation.

Alternatives considered

Keep the worker and add tool checks. Tool checks cannot intercept direct Node imports or establish OS confinement. Keeping a worker inside a confined supervisor process preserves worker metering but adds another execution lifetime without supplying a process-tree CPU limit.

Use an in-process JavaScript realm. A language-level realm does not enforce the filesystem and process policy required for direct Node APIs. OS confinement and a host-owned process lifetime are the required protections.

Copy Codex Code Mode's execution model. Codex Code Mode exposes fresh raw-JavaScript isolates and host tool callbacks, with yield/wait observations separate from execution lifetime. Removing Node APIs or adding resumable cells would change PTC's programming and logging model. The retained design keeps direct Node access under OS policy and one-shot results.

Treat worker active time as a CPU limit. Event-loop utilization counts active wall time in one worker, not CPU consumed by Node and its descendants. The process provider uses a host-owned elapsed deadline and does not make that stronger claim.

Consequences

Each run_code pays for a Node process launch and OS sandbox setup. Long nested tools and approvals consume the same elapsed budget as direct program work. Cleanup can extend the caller's wait beyond that deadline. Stronger descendant containment remains dependent on the selected subprocess backend; its fallback limitations remain visible rather than being upgraded by the runtime label.

run_code requires the program and its description. Service callers can resolve supported execution choices; model-facing timeout and approval controls have a separate consumer owner. Every nested tool still passes through its normal policy and logging path.

<a id="deferred-timeout-design"></a>

Deferred timeout design

The elapsed defaults are a revisitable deployment choice. Further design must separate responsiveness from termination: yielding output to a model does not itself stop a program, and adding waitable cells introduces ownership, cancellation, partial-output logging, turn-end and resume obligations.

Open questions include whether approval waits consume the program budget, how sequential long-running tools compose, whether a separate total-lifetime backstop is needed, and which process-tree CPU/RSS limits can be enforced consistently. A persistent kernel additionally needs a Session-log representation of retained state. These questions do not silently pause or extend the shipped elapsed timer.

中文

问题

Node worker 隔离 JavaScript 状态,但不应用调用 Session 的 OS 沙箱策略。模型代码可以直接导入文件系统与子进程 API,绕过工具策略路径,即使嵌套 tools.* 调用受到正确检查。终止 worker 也不能证明其子进程已停止。

PTC 基础继续负责注册表呈现、生成绑定、分派日志和一次性结算。本决策取代其中基于 worker 的执行、信任与预算实现,同时保留消费方规则。

决策

dsh-ptc-runtime-node 的默认 per-call 模式在一个全新 Node 进程中运行每个程序。Host 解析执行选择,通过与 Bash 相同的 ctx.sandbox 提供方约束启动,并将进程生命周期交给 ctx.subprocess。子进程以直接 Node API、空模型环境和 Host 提供的异步绑定求值可擦除 TypeScript。逐次执行结束后不保留 worker 或持久内核。

Host 在子进程启动时保留 ELECTRON_RUN_AS_NODE;bootstrap 在求值前将其从原生环境中删除,模型可见的 process.env 仍为空。嵌套启动 Electron 需要自行显式选择 Node 模式。桌面端使用 Electron 作为 Node 可执行文件;删除此选择变量会启动 Electron 应用路径,而不是 PTC bootstrap。更改沙箱权限无法修复这一启动模式不匹配。macOS 桌面端回归测试使用真实 Electron 验证绑定写入、直接工作区写入以及受限策略对工作区外写入的拒绝。该测试需要已安装的 Electron 二进制文件;普通运行时测试无需此依赖即可覆盖环境过滤。

已解析输入与策略

PtcRuntime.resolve(request) 验证支持的选项并补全 PtcRunSpec;run(spec) 不引入默认值。PTC 传入调用 Session 的 cwd 与已解析常设策略。直接运行时调用方通过同一解析器取得部署默认值。文件系统与子进程提供方共享一个执行世界,bootstrap 路径通过文件系统的显式宿主文件映射或配置的预安装 bootstrap 传递。

文件模式、观察到的拒绝与强制完整性通过 PtcRunResult.sandbox 独立于程序结果传递。所需沙箱后端无法启动时,受限执行失败。完整访问是显式策略模式。程序成功不证明完整强制能力,process 描述符与额外控制管道也不声明多租户隔离。

实验性 Python 提供方共享已解析文件策略与沙箱诊断,同时保留本地 Unix 执行和配置的经过时间截止。仍不支持单次 timeout 覆盖。

控制与生命周期

子进程所有者提供专用的继承式二进制控制通道,与程序 stdout/stderr 及 launcher 生命周期 IPC 分开。Host 限制帧、排队写入、待处理调用和未完成参数字节,然后在分派前验证调用身份与绑定允许列表。模型代码可以写入该通道,因此其中字节仍不可信。

程序完成、超时、取消与协议失败都通过受管进程所有者关闭执行。选择结果后停止执行计时器;清理随后等待直接结果与受管范围停稳。PTC 桥接在外层工具结果结算前,另行取消并排空嵌套工具分派。拒绝或传输失败绝不自动重放可能已经产生副作用的程序。

资源限制

默认经过时间截止为 120 秒,默认上限为 600 秒。可信服务消费方可以请求 timeoutMs: null 来禁用该定时器;工作流沙箱复用负责这种由调用方控制的生命周期。省略 timeout 或使用数值的请求(包括面向模型的 run_code)保留数值默认值与上限。启用的截止包括运行时准备以及嵌套工具或审批等待。V8 老生代内存、序列化外层输出与控制通信具有独立配置的上限。堆限制不包含原生分配和后代内存,经过时间也不是进程树 CPU 预算。

显式启用的内存保留执行模式在相同文件策略下保留可丢弃内存;常驻进程权限负责策略切换和临时提权。

考虑过的替代方案

保留 worker 并增加工具检查。 工具检查不能拦截直接 Node 导入,也不能建立 OS 约束。在受限监督进程中保留 worker 可以保留 worker 计量,但会增加一层执行生命周期,仍不能提供进程树 CPU 限制。

使用同进程 JavaScript realm。 语言级 realm 不强制直接 Node API 所需的文件系统与进程策略。所需保护是 OS 约束与 Host 拥有的进程生命周期。

复制 Codex Code Mode 的执行模型。 Codex Code Mode 提供全新原始 JavaScript isolate 与 Host 工具回调,yield/wait 观察与执行生命周期分开。移除 Node API 或增加可恢复单元会改变 PTC 的编程与日志模型。保留的设计在 OS 策略下提供直接 Node 访问,并返回一次性结果。

把 worker active time 当作 CPU 限制。 事件循环占用率计量单个 worker 的活跃经过时间,不是 Node 及其后代消耗的 CPU。进程提供方使用 Host 拥有的经过时间截止,不作更强声明。

后果

每次 run_code 都承担 Node 进程启动与 OS 沙箱准备成本。较长的嵌套工具和审批消耗与直接程序工作相同的经过时间预算。清理可能让调用方在截止后继续等待。更强的后代约束仍取决于所选子进程后端;其 fallback 限制保持可见,不会被运行时标签提升。

run_code 要求程序及其描述。服务调用方可以解析支持的执行选择;面向模型的 timeout 与审批控制由独立消费方负责。每个嵌套工具仍经过正常策略与日志路径。

<a id="deferred-timeout-design"></a>

延后评估 timeout 设计

经过时间默认值是可以重新评估的部署选择。后续设计必须将响应及时性与终止分开:向模型交出输出本身不会停止程序;增加可等待单元会引入所有权、取消、部分输出日志、轮次结束和恢复义务。

开放问题包括审批等待是否消耗程序预算、顺序执行的长任务工具如何组合、是否需要独立的总生命周期兜底,以及哪些进程树 CPU/RSS 上限可以一致强制执行。持久内核还需要在 Session 日志中表示保留状态。这些问题不会静默暂停或延长已发布的经过时间计时器。