docs: rebalance prose cleanup and add trimming skill
This commit is contained in:
@@ -10,6 +10,8 @@ The Seatbelt profile is allow-default with `(deny file-write*)` plus write allow
|
||||
|
||||
[`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run) supplies the platform launcher, functional probe, and CLI argument vocabulary. This provider owns only mode-to-grant mapping and runner selection. Keeping path resolution and probe parsing with the versioned binary prevents contract drift.
|
||||
|
||||
Each rung has a self-skipping keyless world-effect test; CI runs platform legs against real kernels and rejects a silent all-skip. The packed-install test exercises the registry launcher and executable mode through a plain-Node consumer.
|
||||
|
||||
```yaml
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* `LocalSandboxProvider`: the local implementation of the `@deepseek-ai/dsh-sandbox` seam.
|
||||
* Local sandbox backend. It selects the platform runner chain (Linux bwrap then
|
||||
* Landlock; macOS Seatbelt), functionally probes competing candidates once, and
|
||||
* reports each wrap's enforcement and stderr dialects. Missing or unusable
|
||||
* confinement fails closed rather than returning the original argv.
|
||||
* @module @deepseek-ai/dsh-sandbox-local
|
||||
*/
|
||||
|
||||
@@ -16,7 +19,10 @@ 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).
|
||||
* Override the runner argv; bwrap-shaped profile arguments are appended. A
|
||||
* non-empty override asserts full enforcement and skips built-in selection and
|
||||
* probing; a broken runner then fails at execution and must be identifiable by
|
||||
* {@link runnerFailureSignatures}.
|
||||
*/
|
||||
runnerCommand?: string[]
|
||||
/**
|
||||
@@ -33,7 +39,9 @@ export interface Config {
|
||||
}
|
||||
|
||||
/**
|
||||
* The `bwrap` profile arguments for one policy.
|
||||
* Build a bwrap profile: the host is read-only with fresh `/dev` and `/proc`;
|
||||
* workspace-write overlays writable temp and workspace mounts. PID and network
|
||||
* isolation are intentionally outside the file-effect policy.
|
||||
*
|
||||
* @param policy - the file-effect policy to express as bwrap arguments.
|
||||
* @returns the bwrap profile arguments (before the trailing `--` + argv).
|
||||
@@ -48,9 +56,9 @@ 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).
|
||||
* Build Landlock grants for the same file policy without synthetic mounts.
|
||||
* Read-only grants only `/dev/null` for writes; workspace-write also grants the
|
||||
* host temp root and workspace.
|
||||
*
|
||||
* @param policy - the file-effect policy to express as launcher grants.
|
||||
* @returns the launcher grant arguments (before `--` + argv).
|
||||
@@ -74,7 +82,7 @@ function canonicalPath(path: string): string {
|
||||
try {
|
||||
return realpathSync(path)
|
||||
} catch {
|
||||
// realpathSync failed: the path (or a prefix) is missing or unreadable.
|
||||
// An unresolved grant matches nothing until the named path exists; keep its spelling.
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -85,11 +93,9 @@ 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.
|
||||
* Build a Seatbelt profile that denies file writes then allows `/dev/null` and,
|
||||
* for workspace-write, the canonical workspace, host temp, and per-user macOS
|
||||
* temp roots. Network and process visibility remain unrestricted.
|
||||
*
|
||||
* @param policy - the file-effect policy to express as an SBPL profile.
|
||||
* @returns the `sandbox-exec` arguments (`-p` + profile, before `--` + argv).
|
||||
@@ -208,11 +214,9 @@ 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".
|
||||
* Runner-owned stderr prefixes cover both internal refusal and shell-level
|
||||
* not-found errors. Consumers match these before denial text because the
|
||||
* command never ran on this path.
|
||||
*/
|
||||
const RUNNER_FAILURE_SIGNATURES = {
|
||||
bwrap: ['bwrap: '],
|
||||
@@ -330,7 +334,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.
|
||||
// A sole candidate needs no arbitration; its execution-time refusal still fails closed.
|
||||
if (rest.length === 0) return { runner: first, enforcement: STATIC_ENFORCEMENT[first] }
|
||||
for (const runner of chain) {
|
||||
const enforcement = this.probeRunner(runner)
|
||||
|
||||
@@ -9,8 +9,11 @@ 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.
|
||||
* Keyless backend integration through `confine()` and a real bwrap process. With no rung forced,
|
||||
* a passing probe must select the first rung. Tests assert world effects, wrap shape, and that the
|
||||
* kernel denial matches the advertised dialect; consumer coverage lives in dsh-bash-sandbox.
|
||||
* Skips when bwrap or user namespaces are unavailable. HOME-based workspaces avoid bwrap's
|
||||
* ephemeral `/tmp`, so workspace-write actually proves the workspace-root rebind.
|
||||
*/
|
||||
|
||||
const probe = spawnSync('bwrap', [...bwrapProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' })
|
||||
|
||||
@@ -10,10 +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.
|
||||
* Keyless backend integration through `confine()` and the registry `landlock-run` launcher, with
|
||||
* bwrap forced off. Tests assert real world effects; consumer coverage lives in dsh-bash-sandbox.
|
||||
* Skips when the platform package or enforcing kernel is unavailable. HOME-based workspaces avoid
|
||||
* Landlock's wholesale `/tmp` grant, so workspace-write proves the workspace-root grant itself.
|
||||
*/
|
||||
|
||||
const probe = spawnSync(launcherPath(), ['--probe'], { timeout: 5_000, encoding: 'utf8' })
|
||||
|
||||
@@ -259,7 +259,8 @@ describe('the platform chains', () => {
|
||||
})
|
||||
|
||||
it('a rogue cached runner tag throws via the exhaustiveness guard (closed union)', async () => {
|
||||
// A rogue runner tag must hit assertNever.
|
||||
// Only a cast can create this rogue closed-union tag. It must hit `assertNever`, ensuring a new
|
||||
// runner cannot silently use another runner's wrap or denial dialect.
|
||||
const { sandbox } = await setup()
|
||||
;(sandbox as unknown as { selectedRunner: unknown }).selectedRunner = { runner: 'chroot', enforcement: 'full' }
|
||||
expect(() => sandbox.confine(['true'], RO)).toThrow('unreachable variant')
|
||||
|
||||
@@ -6,7 +6,16 @@ import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
/** Keyless packed-tarball smoke in an external plain-Node consumer. */
|
||||
/**
|
||||
* Keyless publish-path rehearsal. It packs the package and workspace peers, installs those exact
|
||||
* tarballs in an external plain-Node consumer, and lets npm resolve the registry Landlock launcher
|
||||
* plus its platform package. No tsx, path mapping, or workspace resolution can hide missing files,
|
||||
* dependency errors, or lost executable modes.
|
||||
*
|
||||
* The installed launcher must match the host architecture, remain executable, and either confine a
|
||||
* real process with bwrap disabled or fail closed on a non-enforcing kernel. Skips off Linux or
|
||||
* before `pnpm run build`; launcher byte provenance belongs to its upstream release pipeline.
|
||||
*/
|
||||
|
||||
const packageDir = fileURLToPath(new URL('..', import.meta.url))
|
||||
const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url))
|
||||
@@ -59,7 +68,8 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish-
|
||||
tarballs.push(lines[lines.length - 1] as string)
|
||||
}
|
||||
|
||||
// Install packed tarballs in a plain ESM consumer, including optional platform dependencies.
|
||||
// Peer ranges resolve to the tarballs; Cordis is pinned to their peer range. Do not omit optional
|
||||
// dependencies because the launcher selects its OS/CPU package through one.
|
||||
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,
|
||||
|
||||
@@ -9,9 +9,11 @@ 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.
|
||||
* Keyless backend integration through `confine()` and a real macOS Seatbelt process, with Linux
|
||||
* rungs forced off. Tests assert world effects and that the kernel denial matches the advertised
|
||||
* dialect; consumer coverage lives in dsh-bash-sandbox. Skips off macOS or when the profile probe
|
||||
* fails. HOME-based workspaces avoid Seatbelt's wholesale temp-directory grants, so
|
||||
* workspace-write proves the workspace-root grant itself.
|
||||
*/
|
||||
|
||||
const probe = spawnSync('sandbox-exec', [...seatbeltProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' })
|
||||
|
||||
@@ -1,7 +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.
|
||||
* Same-world process-confinement seam: wrap exact subprocess argv under a
|
||||
* host-path file policy. Containers, microVMs, and remote execution replace the
|
||||
* surrounding capability seam instead; this service shares the host kernel and filesystem.
|
||||
* @module @deepseek-ai/dsh-sandbox
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,10 @@ import { Context, Service } from 'cordis'
|
||||
import { HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/**
|
||||
* File-effect policy a sandbox backend enforces on confined processes.
|
||||
* File-effect policy for confined processes. `read-only` permits only required
|
||||
* sinks such as `/dev/null`; `workspace-write` also permits the workspace and a
|
||||
* backend-defined temp area; `danger-full-access` bypasses confinement. Network
|
||||
* and process visibility are outside this vocabulary.
|
||||
*/
|
||||
export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
|
||||
|
||||
@@ -17,7 +20,9 @@ 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.
|
||||
* Enforcement completeness for this host. `partial` means an active backend or
|
||||
* older kernel ABI cannot govern every promised file effect; callers requiring
|
||||
* an absolute boundary must not treat it as `full`.
|
||||
*/
|
||||
export type SandboxEnforcement = 'full' | 'partial'
|
||||
|
||||
@@ -57,10 +62,9 @@ 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).
|
||||
* Case-insensitive signatures for runner failure before command execution.
|
||||
* Consumers check these before denial signatures: runner failure means the
|
||||
* command never ran, while denial means confinement worked and blocked it.
|
||||
*/
|
||||
runnerFailureSignatures: readonly string[]
|
||||
}
|
||||
@@ -102,9 +106,10 @@ 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).
|
||||
* Abstract process-sandbox service. {@link confine} must return enforcing argv
|
||||
* or fail closed at wrap or runner-execution time; silent unconfined passthrough
|
||||
* is forbidden. Functional probes arbitrate multi-runner chains and may be
|
||||
* skipped for a sole candidate, whose own refusal remains the fail-closed end.
|
||||
*/
|
||||
export abstract class SandboxProvider extends Service {
|
||||
constructor(ctx: Context) {
|
||||
|
||||
Reference in New Issue
Block a user