docs: trim generated prose

This commit is contained in:
Tianyi Cui
2026-07-12 03:36:43 +08:00
parent 3dca90261c
commit 75838e10b5
323 changed files with 2857 additions and 11833 deletions

View File

@@ -2,7 +2,7 @@
Local implementation of the [`@deepseek-ai/dsh-sandbox`](../sandbox/) seam: wraps a caller's argv in a platform confinement runner. Selection is BY PLATFORM, resolved once and cached: each platform names its runner chain, a chain of one is selected directly (probing arbitrates between candidates — a sole candidate leaves nothing to arbitrate), and a chain of several is probed functionally in preference order. Linux: [`bwrap`](https://github.com/containers/bubblewrap) when its probe passes, else the [`landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run) Landlock launcher (kernel confinement that needs no userns/mount privileges — see the [sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md) for the prebuilt-binary decision and profile-parity notes); darwin: `sandbox-exec` speaking a Seatbelt (SBPL) profile, unprobed. A platform with no chain means `confine()` FAILS CLOSED with the seam's structured `SANDBOX_UNAVAILABLE` error (win32 today: a reserved, deliberately empty chain awaiting an AppContainer-family runner); an unprobed runner that turns out unusable fails closed at EXECUTION instead — it refuses to run the command, and every wrap's `runnerFailureSignatures` let the consumer classify that as a sandbox failure rather than a task failure. Never a silent unconfined passthrough on any path.
Policy is per call (`SandboxPolicy`: mode + workspace root); the provider holds only the mechanism and the cached ladder verdict. Every wrap reports the selected runner's `enforcement` (`full`, or `partial` on an older Landlock ABI that governs only a subset of accesses — read from the launcher's `--probe` report line) and its `denialSignatures` — the stderr dialect that rung's kernel speaks on a denied file effect (EROFS text under bwrap, EACCES under Landlock, EPERM under Seatbelt), which stderr-inferring consumers match instead of a cross-runner union. A non-empty `runnerCommand` config is the operator's assertion of a runner that fully enforces the bwrap-shaped profile: the ladder and probes are skipped (the wrap carries both Linux denial dialects, the mechanism being unknown) — also the deterministic fake-runner seam for keyless test tiers. Its runner-failure dialect is the OUTER shell's argv0-scoped failure shapes (`exec: <argv0>: not found`, `<argv0>: No such file or directory`, `<argv0>: Permission denied`) — the consumer re-joins the wrap through `bash -c 'exec …'`, so a missing or unexecutable configured runner classifies as a sandbox failure (fail closed at execution), never as a failing command or a denial. `probeTimeoutMs` (default 5000) bounds each functional probe, the escape hatch for hosts slow enough that a timed-out probe would otherwise misread as `SANDBOX_UNAVAILABLE`.
Policy is per call; the provider stores only the mechanism and cached runner verdict. Each wrap reports enforcement completeness plus backend-specific denial and runner-failure signatures. `runnerCommand` is an operator assertion of a bwrap-shaped runner and skips probes, but missing or unexecutable commands still fail closed at execution. Because its mechanism is unknown, it carries both Linux denial dialects. `probeTimeoutMs` bounds functional probes. The [sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md) owns selection and failure semantics.
The Seatbelt profile is allow-default with `(deny file-write*)` plus write allow-lists, so exactly the mode's promised file effects are governed: `read-only` grants the `/dev/null` literal alone; `workspace-write` adds the workspace root, `/tmp`, and the per-user darwin temp dir (`os.tmpdir()` — the platform's real temp area for mkstemp-family tools), every root canonicalized because Seatbelt matches resolved paths (`/tmp` IS `/private/tmp`). Apple marks the `sandbox-exec` CLI deprecated but ships it on every macOS; the functional probe is what fails closed if that ever changes.

View File

@@ -1,24 +1,5 @@
/**
* `LocalSandboxProvider`: the local implementation of the
* `@deepseek-ai/dsh-sandbox` seam. Wraps a caller's argv in a platform
* confinement runner selected BY PLATFORM: each platform names its runner
* chain ({@link PLATFORM_CHAINS}), a chain of one is selected directly (no
* probe — there is nothing to arbitrate), and a chain of several is probed
* FUNCTIONALLY in preference order (build and enforce a real profile once,
* not `--version`), the verdict cached for the provider's lifetime. Linux:
* `bwrap`, else the `landlock-run` Landlock launcher (kernel confinement
* that needs no userns/mount privileges; distributed as the npm package
* family `node-addon-landlock-run` — the decision recorded in
* docs/rfc/implemented/feature/2026-07-06-sandbox.md); darwin: macOS
* `sandbox-exec` speaking a Seatbelt (SBPL) profile, unprobed.
* When the platform has no chain or no candidate passes,
* {@link LocalSandboxProvider.confine} FAILS CLOSED with the seam's
* structured `SANDBOX_UNAVAILABLE` error instead of passing the argv
* through unconfined; an unusable runner selected WITHOUT a probe fails
* closed at execution time instead (it refuses to run the command), which
* the wrap's `runnerFailureSignatures` let consumers classify as a sandbox
* failure rather than a task failure.
*
* `LocalSandboxProvider`: the local implementation of the `@deepseek-ai/dsh-sandbox` seam.
* @module @deepseek-ai/dsh-sandbox-local
*/
@@ -35,20 +16,7 @@ import type { ConfinedArgv, ConfinedSandboxMode, SandboxEnforcement, SandboxPoli
/** Plugin config. All optional — `static Config` supplies the defaults. */
export interface Config {
/**
* Override the sandbox runner argv (the bwrap-shaped profile arguments are
* appended). A NON-EMPTY argv is the operator's assertion that this runner
* exists and FULLY enforces the profile (confinement reports
* `enforcement: 'full'`, and — the runner's kernel mechanism being unknown
* — carries both Linux file-denial dialects as its denial signatures) —
* the runner chain and its probes are skipped,
* and a broken runner fails loudly at execution time. The operator also
* supplies {@link runnerFailureSignatures}, which distinguish the runner
* refusing its profile from the wrapped command failing normally.
* Absent (or empty — the schema normalizes an omitted array to `[]`): the
* built-in platform chains — Linux `bwrap` then the Landlock launcher
* (probed in that order), darwin `sandbox-exec` (the sole candidate,
* selected without a probe). Used for custom/alternative runners and
* for deterministic fake runners in keyless test tiers.
* Override the sandbox runner argv (the bwrap-shaped profile arguments are appended).
*/
runnerCommand?: string[]
/**
@@ -74,14 +42,8 @@ export interface Config {
}
/**
* The `bwrap` profile arguments for one policy. The whole host tree is bound
* read-only; a fresh `/dev` keeps `>/dev/null` redirects working and a fresh
* `/proc` keeps process-inspecting tools working. `workspace-write`
* additionally mounts an ephemeral writable `/tmp` and rebinds the workspace
* root read-write (bind order matters: later binds overlay earlier ones).
* Deliberately NO `--unshare-pid` (it would break the process-group kill
* semantics shell consumers rely on) and NO network unsharing (the seam's
* mode vocabulary promises file effects only).
* The `bwrap` profile arguments for one policy.
*
* @param policy - the file-effect policy to express as bwrap arguments.
* @returns the bwrap profile arguments (before the trailing `--` + argv).
*/
@@ -95,19 +57,10 @@ export function bwrapProfileArgs(policy: SandboxPolicy): string[] {
}
/**
* The `landlock-run` grant arguments for one policy — the bwrap
* profile's file-effect semantics expressed as a Landlock allow-list
* (Landlock cannot mount, so there are no fresh/ephemeral filesystems). The
* whole tree is readable and executable; of `/dev`, ONLY `/dev/null` is
* writable — a whole-`/dev` grant would expose real host paths beneath it
* (`/dev/shm`, a shared tmpfs) to persistent writes, which `read-only`
* promises never happen. bwrap can hand out a fresh ephemeral `/dev`; on the
* host's own `/dev` the write grant must be node-by-node, and `>/dev/null`
* is the one redirects need. `workspace-write` adds the HOST `/tmp` (shared
* and persistent, where bwrap's is ephemeral — the honest difference,
* recorded in the sandbox RFC's runner notes) plus the workspace
* root read-write. The flag spelling belongs to `node-addon-landlock-run`'s
* `grantArgs`; this function owns only the policy → grants mapping.
* The `landlock-run` grant arguments for one policy — the bwrap profile's file-effect
* semantics expressed as a Landlock allow-list (Landlock cannot mount, so there are no
* fresh/ephemeral filesystems).
*
* @param policy - the file-effect policy to express as launcher grants.
* @returns the launcher grant arguments (before `--` + argv).
*/
@@ -131,9 +84,6 @@ function canonicalPath(path: string): string {
return realpathSync(path)
} catch {
// realpathSync failed: the path (or a prefix) is missing or unreadable.
// Grant the spelling as-is — an unresolvable root matches nothing until
// it exists, which is the conservative outcome, and inventing a fallback
// resolution here would grant a path the caller never named.
return path
}
}
@@ -144,20 +94,12 @@ function sbplString(path: string): string {
}
/**
* The `sandbox-exec` arguments for one policy: `-p` plus a Seatbelt (SBPL)
* profile with the same file-effect semantics as the other dialects, built
* as allow-default → `(deny file-write*)` → write allow-list (later rules
* win), so exactly the mode's promised file effects are governed — network
* and process visibility stay unrestricted, which is all the seam's mode
* vocabulary claims. Of `/dev`, ONLY the `/dev/null` literal is writable
* (the same node-not-directory reasoning as the Landlock grant).
* `workspace-write` adds the workspace root, the host `/tmp`, and the
* per-user darwin temp dir (`os.tmpdir()`, launchd's `TMPDIR`, inherited by
* the confined child) — on darwin that directory IS the platform's `/tmp`
* for every mkstemp-family tool, so omitting it would deny the mode's
* promised temp area. All granted roots are canonicalized because Seatbelt
* matches resolved paths ({@link canonicalPath}); duplicates after
* resolution collapse.
* The `sandbox-exec` arguments for one policy: `-p` plus a Seatbelt (SBPL) profile with the
* same file-effect semantics as the other dialects, built as allow-default → `(deny
* file-write*)` → write allow-list (later rules win), so exactly the mode's promised file
* effects are governed — network and process visibility stay unrestricted, which is all the
* seam's mode vocabulary claims.
*
* @param policy - the file-effect policy to express as an SBPL profile.
* @returns the `sandbox-exec` arguments (`-p` + profile, before `--` + argv).
*/
@@ -239,13 +181,10 @@ type SelectedRunner = { runner: 'bwrap' | 'landlock' | 'seatbelt'; enforcement:
const PLATFORM_CHAINS: Record<string, readonly SelectedRunner['runner'][]> = {
linux: ['bwrap', 'landlock'],
darwin: ['seatbelt'],
// Reserved slot, deliberately empty: Windows support fills it with a
// confinement runner (AppContainer / restricted-token family, shipped from
// its own repository on the landlock-run template) plus a
// SelectedRunner['runner'] union member — the switches' assertNever guards
// then walk the implementer to every site. An empty chain fails closed at
// confine(), identical to an unlisted platform: reserving the slot never
// weakens the fail-closed end.
// Reserved slot, deliberately empty: Windows support fills it with a confinement runner
// (AppContainer / restricted-token family, shipped from its own repository on the
// landlock-run template) plus a SelectedRunner['runner'] union member — the switches'
// assertNever guards then walk the implementer to every site.
win32: [],
}
@@ -276,16 +215,9 @@ function assertPositiveFinite(name: string, value: number): void {
}
/**
* The denial dialect each runner's kernel speaks — the case-insensitive
* stderr substrings a denied file effect produces under it, carried on every
* wrap (the seam's `ConfinedArgv.denialSignatures`). Kernel facts, not
* tunables: bwrap denies through its read-only bind mounts (EROFS), Landlock
* refuses with EACCES, Seatbelt with EPERM — whose text is also what
* non-file EPERM boundaries print, the residual imprecision the consumer's
* conservative classifier documents. An operator-configured `runnerCommand`
* has an unknown kernel mechanism, so its wraps carry both Linux file-denial
* dialects; bare EPERM stays excluded there (it names non-file boundaries
* the mode vocabulary does not govern).
* The denial dialect each runner's kernel speaks — the case-insensitive stderr substrings a
* denied file effect produces under it, carried on every wrap (the seam's
* `ConfinedArgv.denialSignatures`).
*/
const DENIAL_SIGNATURES = {
bwrap: ['read-only file system'],
@@ -295,15 +227,11 @@ const DENIAL_SIGNATURES = {
} as const satisfies Record<SelectedRunner['runner'] | 'runnerCommand', readonly string[]>
/**
* How each runner's OWN failure identifies itself on stderr (the seam's
* `ConfinedArgv.runnerFailureSignatures`): every runner prefixes its error
* lines with its program name, and the shell's runner-not-found message
* carries the same `name: ` shape (`bash: bwrap: command not found`,
* `bash: …/bin/landlock-run: No such file or directory`) — so one substring
* per runner covers both "runner broke" and "runner missing". Consumers
* match these BEFORE the denial dialect: a runner's error text can contain
* denial words (an unopenable grant root reports `Permission denied`), and
* a runner failure means the command never ran at all.
* How each runner's own failure identifies itself on stderr (the seam's
* `ConfinedArgv.runnerFailureSignatures`): every runner prefixes its error lines with its
* program name, and the shell's runner-not-found message carries the same `name: ` shape
* (`bash: bwrap: command not found`, `bash: …/bin/landlock-run: No such file or directory`) —
* so one substring per runner covers both "runner broke" and "runner missing".
*/
const RUNNER_FAILURE_SIGNATURES = {
bwrap: ['bwrap: '],
@@ -356,17 +284,15 @@ export class LocalSandboxProvider extends SandboxProvider {
}
/**
* Wrap `argv` in the selected runner's invocation for `policy` — the
* configured `runnerCommand` when present (the operator's assertion, no
* probe), else the platform chain's runner speaking its own profile
* dialect. Every wrap carries the runner's enforcement completeness, its
* denial dialect, and its runner-failure signatures.
* Wrap `argv` in the selected runner's invocation for `policy` — the configured
* `runnerCommand` when present (the operator's assertion, no probe), else the platform
* chain's runner speaking its own profile dialect.
*
* @param argv - the exact argv the caller is about to spawn.
* @param policy - the file-effect policy this execution runs under.
* @returns the wrapped argv plus the selected backend's enforcement
* completeness, denial signatures, and runner-failure signatures;
* throws the fail-closed `SANDBOX_UNAVAILABLE` error when the platform
* has no usable runner.
* @returns the wrapped argv plus the selected backend's enforcement completeness, denial
* signatures, and runner-failure signatures; throws the fail-closed
* `SANDBOX_UNAVAILABLE` error when the platform has no usable runner.
*/
confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv {
if (this.runnerCommand !== undefined) {
@@ -375,14 +301,9 @@ export class LocalSandboxProvider extends SandboxProvider {
argv: [...this.runnerCommand, ...bwrapProfileArgs(policy), '--', ...argv],
enforcement: 'full',
denialSignatures: DENIAL_SIGNATURES.runnerCommand,
// The operator names the configured runner's OWN pre-exec refusal
// dialect; the consumer additionally re-joins the wrap through an
// outer `bash -c 'exec …'`, so we can add the missing/unexecutable
// outer-shell shapes ourselves. Scoping every automatic shape to
// argv0 keeps in-command errors out (a bare `exec:`/`Permission
// denied` prefix would claim tool output; `exec: <argv0>: not found`
// cannot). The residual text-collision trade is documented by the
// seam's conservative classifier contract.
// The operator names the configured runner's own pre-exec refusal dialect; the consumer
// additionally re-joins the wrap through an outer `bash -c 'exec …'`, so we can add the
// missing/unexecutable outer-shell shapes ourselves.
runnerFailureSignatures: [
...this.configuredRunnerFailureSignatures,
`exec: ${argv0}: not found`,
@@ -428,11 +349,7 @@ export class LocalSandboxProvider extends SandboxProvider {
const chain = this.internals.chain ?? PLATFORM_CHAINS[this.internals.platform ?? process.platform] ?? []
const [first, ...rest] = chain
if (first === undefined) return 'unavailable'
// One candidate = nothing to arbitrate: select it without probing. Its
// runner fails closed at EXECUTION time if unusable (refuses to run the
// command), and the wrap's runnerFailureSignatures let the consumer
// classify that as a sandbox failure — never a silent unconfined run,
// never a plain task failure.
// One candidate = nothing to arbitrate: select it without probing.
if (rest.length === 0) return { runner: first, enforcement: STATIC_ENFORCEMENT[first] }
for (const runner of chain) {
const enforcement = this.probeRunner(runner)

View File

@@ -9,22 +9,8 @@ import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
import { bwrapProfileArgs, LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
/**
* KEYLESS bwrap integration proof for the BACKEND: the REAL `bwrap` confining
* REAL processes through `confine()` + a direct spawn of the returned argv.
* Nothing is forced off: bwrap is the ladder's FIRST rung, so a passing probe
* selects it naturally — the wrap shape assertion pins that. Verifies the
* WORLD (files exist or don't) and that the kernel's denial text matches the
* dialect the wrap advertises; the through-`ctx.bash` consumer proof lives
* with `@deepseek-ai/dsh-bash-sandbox`.
*
* Self-skips wherever the functional probe fails — no `bwrap` on PATH, or a
* host that denies unprivileged user namespaces (the probe is the same
* profile the provider enforces, so skip conditions match runtime exactly).
*
* Workspaces for the workspace-write tests live under the HOME directory on
* purpose: bwrap's `/tmp` is an EPHEMERAL mount (the documented
* bwrap-profile difference — pinned by its own test below), so only a
* workspace OUTSIDE `/tmp` proves the workspace-root rebind itself.
* Keyless bwrap integration proof for the backend: the real `bwrap` confining real processes
* through `confine()` + a direct spawn of the returned argv.
*/
const probe = spawnSync('bwrap', [...bwrapProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' })

View File

@@ -10,22 +10,10 @@ import { launcherPath } from 'node-addon-landlock-run'
import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
/**
* KEYLESS Landlock integration proof for the BACKEND: the REAL npm-distributed
* `landlock-run` launcher (`node-addon-landlock-run`) confining REAL processes through `confine()` + a direct
* spawn of the returned argv, with the bwrap rung forced off so the ladder
* lands on the launcher. Verifies the WORLD (files exist or don't), not the
* wrapper argv alone; the through-`ctx.bash` consumer proof lives with
* `@deepseek-ai/dsh-bash-sandbox`.
*
* Self-skips when the running kernel does not enforce Landlock (or this
* platform has no launcher package — the probe cannot pass then). The
* binary itself arrives with `pnpm install`, so absence is not a checkout
* state.
*
* Workspaces live under the HOME directory on purpose: `workspace-write`
* grants the host `/tmp` wholesale (the documented Landlock-profile
* difference), so only a workspace OUTSIDE `/tmp` proves the workspace-root
* grant itself.
* Keyless Landlock integration proof for the backend: the real npm-distributed `landlock-run`
* launcher (`node-addon-landlock-run`) confining real processes through `confine()` + a direct
* spawn of the returned argv, with the bwrap rung forced off so the ladder lands on the
* launcher.
*/
const probe = spawnSync(launcherPath(), ['--probe'], { timeout: 5_000, encoding: 'utf8' })

View File

@@ -252,19 +252,14 @@ describe('the platform chains', () => {
})
it('a rogue chain entry throws via the probe walk\'s exhaustiveness guard (closed union)', async () => {
// Same convention as the wrap switch below: the union is closed, so a
// runner added later fails to compile at the probe switch instead of
// silently selecting without a probe. Only a cast can reach the guard.
// Same convention as the wrap switch below: the union is closed, so a runner added later
// fails to compile at the probe switch instead of silently selecting without a probe.
const { sandbox } = await setup({}, { chain: ['chroot', 'bwrap'] as unknown as readonly ['bwrap'] })
expect(() => sandbox.confine(['true'], RO)).toThrow('unreachable variant')
})
it('a rogue cached runner tag throws via the exhaustiveness guard (closed union)', async () => {
// The wrap switches on the chain verdict's runner tag and ends with
// assertNever: a rogue tag (only reachable by a cast — the union is
// closed and chainVerdict writes only its own literals) must throw, so a
// runner added later fails to compile at the switch instead of silently
// wrapping with another runner's dialect.
// A rogue runner tag must hit assertNever.
const { sandbox } = await setup()
;(sandbox as unknown as { selectedRunner: unknown }).selectedRunner = { runner: 'chroot', enforcement: 'full' }
expect(() => sandbox.confine(['true'], RO)).toThrow('unreachable variant')

View File

@@ -6,32 +6,7 @@ import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
/**
* KEYLESS publish-path rehearsal for this package's own distribution: the
* provider must work from its PACKED tarball plus its REGISTRY launcher
* dependency, not the git checkout. `pnpm pack` produces the EXACT bytes
* `pnpm publish` would upload; this suite packs the workspace closure
* (`dsh-sandbox-local` + its `@deepseek-ai` peers), installs the tarballs
* into a throwaway consumer OUTSIDE the repo — npm resolving the
* `node-addon-landlock-run` dependency (and its os/cpu-selected platform
* package) from the public registry, the real consumer path — and drives
* the INSTALLED packages under plain `node`: no tsx, no tsconfig paths, no
* workspace resolution, so a `files`-list omission, a broken launcher
* dependency, or a mode-stripped binary fails here instead of at the first
* real install.
*
* World-proofs: the registry-installed launcher carries this host's ELF
* architecture and IS executable (a tarball that loses the mode bit would
* otherwise masquerade as a non-enforcing kernel — the fail-closed branch
* below must never absorb that), and the installed provider confines a real
* process THROUGH it (bwrap forced off) — or fails closed when the running
* kernel does not enforce Landlock, which is itself the installed
* fail-closed contract. Byte provenance of the launcher is the
* `node-addon-landlock-run` repository's own release-pipeline concern.
*
* Self-skips off Linux or when the built `lib/` is absent (run
* `pnpm run build` first — CI's landlock legs do).
*/
/** Keyless packed-tarball smoke in an external plain-Node consumer. */
const packageDir = fileURLToPath(new URL('..', import.meta.url))
const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url))
@@ -84,11 +59,7 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish-
tarballs.push(lines[lines.length - 1] as string)
}
// A real consumer: plain ESM project, tarballs installed by npm — the
// peer ranges (^0.0.1) resolve to the tarball versions, cordis pins to
// the peer range's rc, and `node-addon-landlock-run` (with its
// os/cpu-selected platform package, an OPTIONAL dependency of the entry
// — so no `--omit=optional` here) comes from the public registry.
// Install packed tarballs in a plain ESM consumer, including optional platform dependencies.
writeFileSync(join(consumerDir, 'package.json'), JSON.stringify({ name: 'dsh-packed-consumer', private: true, type: 'module' }))
const install = spawnSync('npm', ['install', '--no-audit', '--no-fund', ...tarballs, 'cordis@4.0.0-rc.6'], {
cwd: consumerDir,

View File

@@ -9,20 +9,9 @@ import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
import { LocalSandboxProvider, seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local'
/**
* KEYLESS Seatbelt integration proof for the BACKEND: the REAL macOS
* `sandbox-exec` confining REAL processes through `confine()` + a direct
* spawn of the returned argv, with the Linux rungs forced off so the ladder
* lands on Seatbelt. Verifies the WORLD (files exist or don't) and that the
* kernel's denial text matches the dialect the wrap advertises; the
* through-`ctx.bash` consumer proof lives with `@deepseek-ai/dsh-bash-sandbox`.
*
* Self-skips wherever the functional probe fails — every non-macOS host, or
* a macOS whose `sandbox-exec` refuses the profile.
*
* Workspaces for the workspace-write tests live under the HOME directory on
* purpose: `workspace-write` grants `/tmp` and the per-user temp dir
* wholesale (the documented Seatbelt-profile temp areas), so only a
* workspace OUTSIDE both proves the workspace-root grant itself.
* Keyless Seatbelt integration proof for the backend: the real macOS `sandbox-exec` confining
* real processes through `confine()` + a direct spawn of the returned argv, with the Linux
* rungs forced off so the ladder lands on Seatbelt.
*/
const probe = spawnSync('sandbox-exec', [...seatbeltProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' })

View File

@@ -1,24 +1,7 @@
/**
* The process-sandbox seam (`ctx.sandbox`): an abstract service defining WHAT
* platform confinement does — wrap a subprocess argv so it executes under a
* file-effect policy — without saying HOW. Implementations subclass
* {@link SandboxProvider} and register as the `sandbox` service;
* `@deepseek-ai/dsh-sandbox-local` (per-platform chains: Linux `bwrap` then the
* npm-distributed `landlock-run` launcher, macOS `sandbox-exec`/Seatbelt) is
* the first.
* Consumers hand over the exact argv they are about to spawn
* (`@deepseek-ai/dsh-bash-sandbox` wraps `['bash', '-c', command]`; a
* subagent backend wraps its child-agent argv) and spawn the returned argv
* instead.
*
* The seam confines SAME-WORLD subprocesses only: a backend shares the
* host's filesystem and kernel, and the policy's `workspaceRoot` names a
* real host path. Containers, microVMs, and remote executors are NOT
* backends of this seam — they are sibling implementations of whole
* capability seams (`ctx.bash`, `ctx.fs`), deployed as environment-coherent
* groups; the boundary is recorded in
* docs/rfc/implemented/feature/2026-07-06-sandbox.md.
*
* The process-sandbox seam (`ctx.sandbox`): an abstract service defining what platform
* confinement does — wrap a subprocess argv so it executes under a file-effect policy —
* without saying how.
* @module @deepseek-ai/dsh-sandbox
*/
@@ -27,24 +10,6 @@ import { HarnessError } from '@deepseek-ai/dsh-llm'
/**
* File-effect policy a sandbox backend enforces on confined processes.
*
* - `read-only` — the process cannot write the filesystem anywhere; a
* write-shaped `/dev/null` sink stays available so `>/dev/null` redirects
* keep working (HOW is the backend's choice: bwrap mounts a fresh `/dev`,
* the Landlock launcher and Seatbelt grant the single `/dev/null` node).
* - `workspace-write` — writes are allowed only under the policy's
* workspace root and `/tmp`; everything else stays read-only. Which `/tmp`
* is backend-specific — an ephemeral mount under bwrap, the HOST `/tmp`
* under the Landlock launcher, the host `/private/tmp` plus the per-user
* darwin temp dir under Seatbelt: the seam promises the write boundary,
* not the mount's nature.
* - `danger-full-access` — no confinement; a consumer configured with it
* spawns its argv unwrapped and never calls the provider.
*
* The mode governs FILE effects only: network and process visibility are not
* restricted (a backend that cannot honestly enforce them must not pretend
* to). How completely the file effects themselves are enforced is likewise a
* reported fact, not an assumption — see {@link SandboxEnforcement}.
*/
export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
@@ -52,18 +17,7 @@ export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
export type ConfinedSandboxMode = Exclude<SandboxMode, 'danger-full-access'>
/**
* How completely the selected backend enforces a confined mode's file
* effects.
*
* - `full` — every file effect the mode promises to block is governed: the
* `bwrap` mount profile, a Landlock kernel enforcing the launcher's whole
* ruleset, or an operator-configured runner (configuring one asserts full
* enforcement along with existence).
* - `partial` — the backend is active but the kernel governs only the subset
* of accesses its ABI knows (an older Landlock ABI: path-based truncate is
* ungoverned before ABI v3), so a file effect the mode promises to block
* may still land. A caller that needs the mode's promise to be absolute
* must treat `partial` as outside that promise.
* How completely the selected backend enforces a confined mode's file effects.
*/
export type SandboxEnforcement = 'full' | 'partial'
@@ -103,17 +57,10 @@ export interface ConfinedArgv {
*/
denialSignatures: readonly string[]
/**
* How the RUNNER ITSELF failing identifies itself: case-insensitive stderr
* substrings produced when the sandbox binary is missing, refuses its
* profile, or fails closed before exec'ing the command (`bwrap: `,
* `landlock-run: `, `sandbox-exec: ` — each covers both the runner's own
* error prefix and the shell's runner-not-found message). ORTHOGONAL to
* {@link denialSignatures}: a denial is the confined COMMAND being blocked
* (the sandbox working as designed); a runner failure means the command
* NEVER RAN and must surface as a sandbox failure, not a task failure —
* consumers check these signatures FIRST (a runner's own error text may
* contain denial words, e.g. an unopenable grant root reporting
* `Permission denied`).
* How the RUNNER ITSELF failing identifies itself: case-insensitive stderr substrings
* produced when the sandbox binary is missing, refuses its profile, or fails closed before
* exec'ing the command (`bwrap: `, `landlock-run: `, `sandbox-exec: ` — each covers both the
* runner's own error prefix and the shell's runner-not-found message).
*/
runnerFailureSignatures: readonly string[]
}
@@ -155,27 +102,9 @@ declare module 'cordis' {
}
/**
* Abstract process-sandbox service. Subclass, implement {@link confine}, and
* load the subclass as a plugin — it registers as `ctx.sandbox` (one
* implementation per context; loading a second throws, cordis' standard
* duplicate-service behavior).
*
* Semantics every implementation must honor:
* - {@link confine} either returns an argv whose runner ENFORCES the policy
* or fails closed — at `confine` time with {@link SandboxUnavailableError}
* (no backend for this host), or at EXECUTION time by the runner itself
* refusing to run the command (exiting without exec'ing it, identified by
* {@link ConfinedArgv.runnerFailureSignatures}). A silent unconfined
* passthrough is never a legal outcome on either path.
* - Probing exists to ARBITRATE between multiple candidate backends and may
* be skipped when a platform has exactly one: the sole candidate is
* selected directly and the runner's exec-time fail-closed refusal carries
* the safety property. When probing does run, it is functional (actually
* enforcing a profile, not a version check), at most once per provider
* lifetime; `confine` itself spawns nothing beyond that one-time probing.
* - The returned {@link ConfinedArgv.enforcement} states the backend's
* actual completeness for THIS host; `partial` is reported, never silently
* upgraded to `full`.
* Abstract process-sandbox service. Subclass, implement {@link confine}, and load the subclass
* as a plugin — it registers as `ctx.sandbox` (one implementation per context; loading a
* second throws, cordis' standard duplicate-service behavior).
*/
export abstract class SandboxProvider extends Service {
constructor(ctx: Context) {