浏览代码

refactor: hide sandbox implementation helpers

Tianyi Cui 2 月之前
父节点
当前提交
b2025337b5

+ 2 - 2
docs/config-catalog.md

@@ -176,7 +176,7 @@ export interface Config extends LocalConfig {
 
 Depends on: [`LocalConfig`](#deepseek-aidsh-bash-local) · [`SandboxMode`](core-data-structures/sandbox.md)
 
-Source: [`packages/bash/bash-sandbox/src/index.ts:60`](../packages/bash/bash-sandbox/src/index.ts)
+Source: [`packages/bash/bash-sandbox/src/index.ts:61`](../packages/bash/bash-sandbox/src/index.ts)
 
 ## `@deepseek-ai/dsh-code-runtime-worker`
 
@@ -521,7 +521,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/sandbox/sandbox-local/src/index.ts:36`](../packages/sandbox/sandbox-local/src/index.ts)
+Source: [`packages/sandbox/sandbox-local/src/index.ts:35`](../packages/sandbox/sandbox-local/src/index.ts)
 
 ## `@deepseek-ai/dsh-session-persistence-jsonl`
 

+ 2 - 0
packages/bash/bash-sandbox/README.md

@@ -2,6 +2,8 @@
 
 Sandbox-consuming implementation of the [`@deepseek-ai/dsh-bash`](../bash/) executor seam. Load it **instead of** `@deepseek-ai/dsh-bash-local`, together with a [`ctx.sandbox`](../../sandbox/sandbox/) provider (e.g. [`@deepseek-ai/dsh-sandbox-local`](../../sandbox/sandbox-local/)) — the model-facing tool layer (`dsh-tool-bash`) is untouched; that swap is exactly what the seams exist for.
 
+The package root exports the default and named `SandboxBashExecutor` plugin plus its `Config`; quoting and result-classification helpers stay internal.
+
 Every command is confined by handing the provider the exact `['bash', '-c', command]` argv this executor is about to spawn and spawning the returned (wrapped) argv instead. WHICH platform runner confines it — and whether one is usable at all (fail closed with a structured `SANDBOX_UNAVAILABLE` error, never a silent unconfined run) — is the provider's concern; this package owns the bash side only.
 
 | Mode | File effects |

+ 49 - 0
packages/bash/bash-sandbox/src/helpers.ts

@@ -0,0 +1,49 @@
+/**
+ * Internal shell-quoting and sandbox-result classification helpers.
+ *
+ * @module @deepseek-ai/dsh-bash-sandbox/helpers
+ */
+
+import type { BashRunResult } from '@deepseek-ai/dsh-bash'
+
+/**
+ * Quote one string as a single-quoted POSIX shell word.
+ * @param text - raw argv element to preserve through the outer shell parse.
+ * @returns the quoted shell word.
+ */
+export function shellQuote(text: string): string {
+  return `'${text.replaceAll("'", String.raw`'\''`)}'`
+}
+
+/**
+ * Classify a failed run against the selected backend's denial dialect.
+ * @param result - settled foreground run.
+ * @param signatures - case-insensitive denial substrings from the active wrap.
+ * @returns whether the failed run matches that denial dialect.
+ */
+export function classifyDenial(result: BashRunResult, signatures: readonly string[]): boolean {
+  return matchesSignature(result.exitCode, result.stderr.text, signatures)
+}
+
+/**
+ * Classify a failed run against the selected backend's runner-failure dialect.
+ * @param result - settled foreground run.
+ * @param signatures - case-insensitive runner-failure substrings from the active wrap.
+ * @returns whether the failed run matches that runner-failure dialect.
+ */
+export function classifyRunnerFailure(result: BashRunResult, signatures: readonly string[]): boolean {
+  return matchesSignature(result.exitCode, result.stderr.text, signatures)
+}
+
+/**
+ * Match a non-zero exit against case-insensitive stderr signatures.
+ * @param exitCode - process exit code; null means signal termination.
+ * @param stderr - collected stderr text.
+ * @param signatures - substrings identifying the selected backend's dialect.
+ * @returns whether this is a non-zero exit whose stderr matches a signature.
+ */
+export function matchesSignature(exitCode: number | null, stderr: string, signatures: readonly string[]): boolean {
+  if (exitCode === null || exitCode === 0) return false
+  const lowered = stderr.toLowerCase()
+  return signatures.some(signature => lowered.includes(signature.toLowerCase()))
+}

+ 1 - 69
packages/bash/bash-sandbox/src/index.ts

@@ -49,6 +49,7 @@ import { SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import type { ConfinedSandboxMode, SandboxEnforcement, SandboxMode } from '@deepseek-ai/dsh-sandbox'
 import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
 import type { Config as LocalConfig } from '@deepseek-ai/dsh-bash-local'
+import { classifyDenial, classifyRunnerFailure, matchesSignature, shellQuote } from './helpers.ts'
 
 /**
  * Plugin config: the local executor's knobs plus the sandbox policy. All
@@ -67,75 +68,6 @@ export interface Config extends LocalConfig {
   workspaceRoot?: string
 }
 
-/**
- * Quote one string as a single-quoted POSIX shell word (embedded single
- * quotes become `'\''`), so a wrapped argv element survives the outer
- * `bash -c` re-parse byte-for-byte.
- * @param text - the raw argv element to quote.
- * @returns the single-quoted shell word.
- */
-export function shellQuote(text: string): string {
-  return `'${text.replaceAll("'", String.raw`'\''`)}'`
-}
-
-/**
- * Conservative sandbox-denial classifier: a run counts as denied only when it
- * FAILED (nonzero exit — a signal kill is not a denial) and its stderr
- * carries one of the SELECTED BACKEND's own denial signatures — the dialect
- * the provider stamps on every wrap (`ConfinedArgv.denialSignatures`:
- * `Read-only file system` under bwrap's EROFS mounts, `Permission denied`
- * under Landlock's EACCES, `Operation not permitted` under Seatbelt's
- * EPERM). Matching the backend's dialect rather than a cross-backend union
- * keeps the classifier from claiming denials the active backend never
- * produces (bare EPERM text under a Linux runner names non-file boundaries —
- * mount, kill, ptrace — that fail the same way unsandboxed). Text inference
- * is the fallback signal until a runner provides a structured one (which
- * wins once it exists); it errs toward NOT claiming a denial, and its known
- * residual imprecision is non-sandbox text in the active dialect (an ssh
- * auth failure reads as a denial under Landlock, a refused `kill` under
- * Seatbelt).
- * @param result - the settled foreground run to classify.
- * @param signatures - the active wrap's denial dialect, case-insensitive
- *   stderr substrings.
- * @returns whether the run's failure reads as a sandbox denial.
- */
-export function classifyDenial(result: BashRunResult, signatures: readonly string[]): boolean {
-  return matchesSignature(result.exitCode, result.stderr.text, signatures)
-}
-
-/**
- * Runner-failure classifier: a failed run whose stderr carries the SELECTED
- * BACKEND's own runner-failure signature (`ConfinedArgv.
- * runnerFailureSignatures`: the runner's error prefix, which also matches
- * the shell's runner-not-found message) means the SANDBOX itself failed and
- * the command never ran. Checked BEFORE {@link classifyDenial} — a runner's
- * error text can contain denial words (an unopenable grant root reports
- * `Permission denied`) — and surfaced as the fail-closed
- * `SANDBOX_UNAVAILABLE` error on the foreground path, `sandbox.runnerFailed`
- * on a settled background task. Same conservative-text-inference stance and
- * residual imprecision as the denial classifier (a failing task that itself
- * prints the runner's prefix reads as a runner failure).
- * @param result - the settled foreground run to classify.
- * @param signatures - the active wrap's runner-failure signatures,
- *   case-insensitive stderr substrings.
- * @returns whether the run's failure reads as the runner itself failing.
- */
-export function classifyRunnerFailure(result: BashRunResult, signatures: readonly string[]): boolean {
-  return matchesSignature(result.exitCode, result.stderr.text, signatures)
-}
-
-/**
- * The classifier core shared by foreground results and settled background
- * tasks: failed AND signature present. Lowercases BOTH sides — the seam
- * declares its signatures case-insensitive, and producers compose them from
- * runtime data of any case (an `argv0` path, `No such file or directory`).
- */
-function matchesSignature(exitCode: number | null, stderr: string, signatures: readonly string[]): boolean {
-  if (exitCode === null || exitCode === 0) return false
-  const lowered = stderr.toLowerCase()
-  return signatures.some(signature => lowered.includes(signature.toLowerCase()))
-}
-
 /**
  * Sandbox-consuming bash executor. Registers as `ctx.bash` (loading it
  * INSTEAD OF `dsh-bash-local`, together with a `ctx.sandbox` provider, is

+ 2 - 1
packages/bash/bash-sandbox/tests/bwrap.e2e.ts

@@ -5,7 +5,8 @@ import { homedir } from 'node:os'
 import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
-import { bwrapProfileArgs, LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { bwrapProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts'
 import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
 
 /**

+ 2 - 1
packages/bash/bash-sandbox/tests/sandbox.spec.ts

@@ -16,7 +16,8 @@ import { Context } from 'cordis'
 import type { BashRunResult, CollectedOutput } from '@deepseek-ai/dsh-bash'
 import { SANDBOX_UNAVAILABLE, SandboxProvider, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import type { ConfinedArgv, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
-import { classifyDenial, classifyRunnerFailure, SandboxBashExecutor, shellQuote } from '@deepseek-ai/dsh-bash-sandbox'
+import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
+import { classifyDenial, classifyRunnerFailure, shellQuote } from '../src/helpers.ts'
 import type { Config } from '@deepseek-ai/dsh-bash-sandbox'
 
 const spillDir = mkdtempSync(join(tmpdir(), 'dsh-bash-sandbox-spec-'))

+ 2 - 1
packages/bash/bash-sandbox/tests/seatbelt.e2e.ts

@@ -5,7 +5,8 @@ import { homedir } from 'node:os'
 import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
-import { LocalSandboxProvider, seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local'
+import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts'
 import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
 
 /**

+ 2 - 0
packages/sandbox/sandbox-local/README.md

@@ -2,6 +2,8 @@
 
 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.
 
+The package root exports the default and named `LocalSandboxProvider` plugin, `Config`, and its public test-injection seam; platform profile builders stay internal.
+
 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`.
 
 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.

+ 2 - 100
packages/sandbox/sandbox-local/src/index.ts

@@ -23,14 +23,13 @@
  */
 
 import { spawnSync } from 'node:child_process'
-import { realpathSync } from 'node:fs'
-import { tmpdir } from 'node:os'
-import { grantArgs as landlockGrantArgs, LAUNCHER_BIN, launcherPath as landlockLauncherPath, probe as defaultProbeLandlock } from 'node-addon-landlock-run'
+import { LAUNCHER_BIN, launcherPath as landlockLauncherPath, probe as defaultProbeLandlock } from 'node-addon-landlock-run'
 import { Context } from 'cordis'
 import z from 'schemastery'
 import { assertNever } from '@deepseek-ai/dsh-llm'
 import { SandboxProvider, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import type { ConfinedArgv, ConfinedSandboxMode, SandboxEnforcement, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
+import { bwrapProfileArgs, landlockProfileArgs, seatbeltProfileArgs } from './profiles.ts'
 
 /** Plugin config. All optional — `static Config` supplies the defaults. */
 export interface Config {
@@ -73,103 +72,6 @@ export interface Config {
   probeTimeoutMs?: number
 }
 
-/**
- * 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).
- * @param policy - the file-effect policy to express as bwrap arguments.
- * @returns the bwrap profile arguments (before the trailing `--` + argv).
- */
-export function bwrapProfileArgs(policy: SandboxPolicy): string[] {
-  const args = ['--ro-bind', '/', '/', '--dev', '/dev', '--proc', '/proc', '--die-with-parent']
-  if (policy.mode === 'workspace-write') {
-    args.push('--tmpfs', '/tmp')
-    args.push('--bind', policy.workspaceRoot, policy.workspaceRoot)
-  }
-  return args
-}
-
-/**
- * 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.
- * @param policy - the file-effect policy to express as launcher grants.
- * @returns the launcher grant arguments (before `--` + argv).
- */
-export function landlockProfileArgs(policy: SandboxPolicy): string[] {
-  const readWrite = ['/dev/null']
-  if (policy.mode === 'workspace-write') {
-    readWrite.push('/tmp', policy.workspaceRoot)
-  }
-  return landlockGrantArgs({ readOnly: ['/'], readWrite })
-}
-
-/**
- * Resolve a granted root to the path the kernel actually sees. Seatbelt path
- * filters match the CANONICAL path (symlinks resolved), and the roots this
- * profile grants are symlinked on every macOS: `/tmp` is `/private/tmp` and
- * the user temp dir lives under `/var` → `/private/var` — an as-spelled
- * grant would match nothing.
- */
-function canonicalPath(path: string): string {
-  try {
-    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
-  }
-}
-
-/** Quote one path as an SBPL string literal (backslashes and double quotes escaped). */
-function sbplString(path: string): string {
-  return `"${path.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`)}"`
-}
-
-/**
- * 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.
- * @param policy - the file-effect policy to express as an SBPL profile.
- * @returns the `sandbox-exec` arguments (`-p` + profile, before `--` + argv).
- */
-export function seatbeltProfileArgs(policy: SandboxPolicy): string[] {
-  const forms = ['(version 1)', '(allow default)', '(deny file-write*)', `(allow file-write* (literal ${sbplString('/dev/null')}))`]
-  if (policy.mode === 'workspace-write') {
-    const roots = [...new Set([policy.workspaceRoot, '/tmp', tmpdir()].map(canonicalPath))]
-    forms.push(`(allow file-write* ${roots.map(root => `(subpath ${sbplString(root)})`).join(' ')})`)
-  }
-  return ['-p', forms.join(' ')]
-}
-
 /**
  * Functional `bwrap` probe: can it actually build the read-only profile on
  * this host? (`--version` alone would miss a disabled unprivileged user

+ 67 - 0
packages/sandbox/sandbox-local/src/profiles.ts

@@ -0,0 +1,67 @@
+/**
+ * Internal platform-profile builders for the local sandbox provider.
+ *
+ * @module @deepseek-ai/dsh-sandbox-local/profiles
+ */
+
+import { realpathSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { grantArgs as landlockGrantArgs } from 'node-addon-landlock-run'
+import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
+
+/**
+ * Build the bwrap profile arguments for one file-effect policy.
+ * @param policy - file-effect policy to express as bwrap mounts.
+ * @returns profile arguments before the trailing separator and command argv.
+ */
+export function bwrapProfileArgs(policy: SandboxPolicy): string[] {
+  const args = ['--ro-bind', '/', '/', '--dev', '/dev', '--proc', '/proc', '--die-with-parent']
+  if (policy.mode === 'workspace-write') {
+    args.push('--tmpfs', '/tmp')
+    args.push('--bind', policy.workspaceRoot, policy.workspaceRoot)
+  }
+  return args
+}
+
+/**
+ * Build the Landlock launcher grants for one file-effect policy.
+ * @param policy - file-effect policy to express as Landlock allow-list grants.
+ * @returns launcher grant arguments before the trailing separator and command argv.
+ */
+export function landlockProfileArgs(policy: SandboxPolicy): string[] {
+  const readWrite = ['/dev/null']
+  if (policy.mode === 'workspace-write') {
+    readWrite.push('/tmp', policy.workspaceRoot)
+  }
+  return landlockGrantArgs({ readOnly: ['/'], readWrite })
+}
+
+/** Resolve a granted root to the canonical path the Seatbelt kernel sees. */
+function canonicalPath(path: string): string {
+  try {
+    return realpathSync(path)
+  } catch {
+    // Missing or unreadable roots stay as spelled; an unresolved root grants
+    // nothing until it exists, which is the conservative outcome.
+    return path
+  }
+}
+
+/** Quote one path as an SBPL string literal. */
+function sbplString(path: string): string {
+  return `"${path.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`)}"`
+}
+
+/**
+ * Build the sandbox-exec arguments and SBPL profile for one policy.
+ * @param policy - file-effect policy to express as an SBPL profile.
+ * @returns sandbox-exec arguments before the trailing separator and command argv.
+ */
+export function seatbeltProfileArgs(policy: SandboxPolicy): string[] {
+  const forms = ['(version 1)', '(allow default)', '(deny file-write*)', `(allow file-write* (literal ${sbplString('/dev/null')}))`]
+  if (policy.mode === 'workspace-write') {
+    const roots = [...new Set([policy.workspaceRoot, '/tmp', tmpdir()].map(canonicalPath))]
+    forms.push(`(allow file-write* ${roots.map(root => `(subpath ${sbplString(root)})`).join(' ')})`)
+  }
+  return ['-p', forms.join(' ')]
+}

+ 2 - 1
packages/sandbox/sandbox-local/tests/bwrap.e2e.ts

@@ -6,7 +6,8 @@ import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
 import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
-import { bwrapProfileArgs, LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { bwrapProfileArgs } from '../src/profiles.ts'
 
 /**
  * KEYLESS bwrap integration proof for the BACKEND: the REAL `bwrap` confining

+ 1 - 3
packages/sandbox/sandbox-local/tests/local.spec.ts

@@ -15,12 +15,10 @@ import { Context } from 'cordis'
 import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
 import {
-  bwrapProfileArgs,
-  landlockProfileArgs,
   LocalSandboxProvider,
-  seatbeltProfileArgs,
 } from '@deepseek-ai/dsh-sandbox-local'
 import type { Config } from '@deepseek-ai/dsh-sandbox-local'
+import { bwrapProfileArgs, landlockProfileArgs, seatbeltProfileArgs } from '../src/profiles.ts'
 
 const RO: SandboxPolicy = { mode: 'read-only', workspaceRoot: '/ws' }
 const WW: SandboxPolicy = { mode: 'workspace-write', workspaceRoot: '/ws' }

+ 2 - 1
packages/sandbox/sandbox-local/tests/seatbelt.e2e.ts

@@ -6,7 +6,8 @@ import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
 import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
-import { LocalSandboxProvider, seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local'
+import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
+import { seatbeltProfileArgs } from '../src/profiles.ts'
 
 /**
  * KEYLESS Seatbelt integration proof for the BACKEND: the REAL macOS