fix(sandbox): evidence-gate runner failures (round 1)
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/sandbox/sandbox/README.md
|
||||
README.md: 99f0641560937f66df6db76ae55c90595329792f
|
||||
README.zh.md: 5d7dfb303ea2405e725d162698c3ab3d39485636
|
||||
README.md: 1b522b2c72d00bfed89650aa7f22b65a72d26085
|
||||
README.zh.md: 310b6bd5f526678e61d72846f154e7226495fca6
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
Abstract process-sandbox seam. Owns the `ctx.sandbox` service contract ([`SandboxProvider`](src/index.ts)) and the confinement vocabulary the harness shares: `SandboxMode` (`read-only` / `workspace-write` / `danger-full-access`, file effects only), `SandboxEnforcement` (`full` / `partial`, per kernel ABI), `SandboxExecutionPolicy` (the complete per-call mode + workspace root), `SandboxPolicy` (its confined subset), and the fail-closed `SANDBOX_UNAVAILABLE` error. Interface package of the [capability-seam split](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): depends only on cordis (+ the harness error base), never on a backend.
|
||||
|
||||
The contract in one line: `ctx.sandbox.confine(argv, policy)` returns the argv to spawn INSTEAD of your own — wrapped so the process (and everything it spawns) runs confined — plus two facts about the selected backend: the enforcement completeness it achieves and its denial dialect (`denialSignatures`, the stderr substrings its kernel prints on a denied file effect — what stderr-inferring consumers match instead of a cross-backend union); when no backend is usable it throws rather than passing the argv through unconfined.
|
||||
The contract in one line: `ctx.sandbox.confine(argv, policy)` returns the argv to spawn INSTEAD of your own — wrapped so the process (and everything it spawns) runs confined — plus the selected backend's enforcement completeness, denial dialect (`denialSignatures`), and structured runner-failure evidence (`runnerFailureRules`); when no backend is usable it throws rather than passing the argv through unconfined. The [core type catalog](../../../docs/core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects) owns the exact classifier shape.
|
||||
|
||||
Policy rides the call, not the provider: two consumers may confine under different policies at the same instant (bash under `read-only` while a confined child agent keeps its state directory writable), and an approved escalated retry is just a new call with a wider policy.
|
||||
|
||||
@@ -39,4 +39,5 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
- **File effects are the whole policy vocabulary** — the seam expresses no network, process, syscall, device, or credential restrictions.
|
||||
- **Same-world confinement only** — containers, microVMs, and remote execution require replacing capability implementations rather than adding a provider here.
|
||||
- **Denial reporting is a stderr dialect** — the seam returns backend signatures instead of a typed runtime denial channel, so consumers that need classification must infer it from the child process's output.
|
||||
- **Runner diagnostics are in-band** — exit status plus stderr evidence cannot prove which process wrote a matching line, so a confined child that deliberately mimics its runner can cause an availability/diagnostic false attribution. This cannot bypass confinement; an out-of-band runner-status channel is deferred.
|
||||
- **One provider per context** — composing different sandbox mechanisms simultaneously requires a provider-level ladder or separate Cordis contexts; callers choose policy per call, not backend identity.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
抽象进程沙箱 seam。负责定义 `ctx.sandbox` 服务契约([`SandboxProvider`](src/index.ts))与 harness 共享的限制词汇:`SandboxMode`(`read-only`/`workspace-write`/`danger-full-access`,仅限文件操作)、`SandboxEnforcement`(`full`/`partial`,针对每种内核 ABI)、`SandboxExecutionPolicy`(每次调用的完整模式及工作区根目录)、`SandboxPolicy`(其中受限制的子集),以及故障时拒绝放行的 `SANDBOX_UNAVAILABLE` 错误。它是[能力 seam 拆分](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)的接口包(package):只依赖 cordis(及 harness 错误基类),绝不依赖后端。
|
||||
|
||||
用一句话概括契约:`ctx.sandbox.confine(argv, policy)` 返回用于 spawn、应当取代调用方原始 argv 的 argv。返回值经过包装,使进程及其派生的所有进程都在限制下运行;另附所选后端的两个事实:它达到的强制执行完整度,以及拒绝方言(`denialSignatures`,即内核在文件操作被拒绝时打印到 stderr 的子字符串;通过 stderr 推断的消费方会匹配这些字符串,而不是统一的跨后端签名集合)。没有可用后端时,它会抛出异常,绝不会原样传递 argv 使其不受限制地运行。
|
||||
用一句话概括契约:`ctx.sandbox.confine(argv, policy)` 返回用于 spawn、应当取代调用方原始 argv 的 argv。返回值经过包装,使进程及其派生的所有进程都在限制下运行;还会附带所选后端达到的强制执行完整度、拒绝方言(`denialSignatures`)和结构化 runner 失败证据(`runnerFailureRules`)。没有可用后端时,它会抛出异常,绝不会原样传递 argv 使其不受限制地运行。[核心类型目录](../../../docs/core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects)负责定义分类器的精确结构。
|
||||
|
||||
策略随调用传递,而不属于提供方:两个消费方可以同时按不同策略施加限制(bash 使用 `read-only`,而受限制的子 agent(智能体)保持其状态目录可写);获批的升权重试只是使用更宽策略发起的新调用。
|
||||
|
||||
@@ -39,4 +39,5 @@ sandbox mode "<mode>" is requested but no sandbox backend is usable on this host
|
||||
- **文件操作是完整的策略词汇**:该 seam 不表达网络、进程、系统调用、设备或凭据限制。
|
||||
- **只支持与宿主共享文件系统和内核的限制**:容器、microVM 与远程执行需要替换能力实现,而不是在此处增加提供方。
|
||||
- **拒绝报告是一种 stderr 方言**:该 seam 返回后端签名,而非类型化运行时拒绝通道,因此需要分类的消费方必须从子进程输出推断。
|
||||
- **Runner 诊断使用带内通道**:退出状态与 stderr 证据无法证明匹配行由哪个进程写入,因此受限子进程若故意模仿 runner,就可能造成可用性或诊断误归因。这无法绕过约束;带外 runner 状态通道暂缓实现。
|
||||
- **每个上下文只有一个提供方**:同时组合不同沙箱机制需要提供方级阶梯或独立 Cordis 上下文;调用方逐调用选择策略,而非后端标识。
|
||||
|
||||
@@ -62,6 +62,22 @@ export interface SandboxPolicy extends SandboxExecutionPolicy {
|
||||
mode: ConfinedSandboxMode
|
||||
}
|
||||
|
||||
/**
|
||||
* Evidence that identifies a sandbox runner failing before it executes the
|
||||
* wrapped command. A consumer first applies {@link allowedExitCodes} when
|
||||
* present, removes {@link informationalLines} by case-insensitive exact line
|
||||
* equality, then matches {@link fatalSignatures} case-insensitively within
|
||||
* each remaining stderr line. Exit status alone never proves runner failure.
|
||||
*/
|
||||
export interface RunnerFailureRule {
|
||||
/** Nonzero process exit codes on which this rule may match; omitted permits any nonzero exit. */
|
||||
allowedExitCodes?: readonly number[]
|
||||
/** Non-empty substrings identifying a fatal runner diagnostic on one stderr line. */
|
||||
fatalSignatures: readonly string[]
|
||||
/** Benign stderr lines excluded by exact full-line equality before fatal matching. */
|
||||
informationalLines?: readonly string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link SandboxProvider.confine} result: the argv to spawn in place of
|
||||
* the caller's own, plus the enforcement completeness the selected backend
|
||||
@@ -82,11 +98,12 @@ export interface ConfinedArgv {
|
||||
*/
|
||||
denialSignatures: readonly string[]
|
||||
/**
|
||||
* Case-insensitive signatures for runner failure before command execution.
|
||||
* Consumers check these before denial signatures: runner failure means the
|
||||
* Structured runner-failure evidence rules. Consumers require a matching
|
||||
* fatal stderr line (after informational exclusions) and any rule-specific
|
||||
* exit-code gate before checking denial signatures: runner failure means the
|
||||
* command never ran, while denial means confinement worked and blocked it.
|
||||
*/
|
||||
runnerFailureSignatures: readonly string[]
|
||||
runnerFailureRules: readonly RunnerFailureRule[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user