ctx.sandbox (dsh-sandbox): confine(argv, policy) returns the argv to spawn instead — wrapped so the process and its children run confined — plus the enforcement completeness and the backend denial/runner-failure dialects; no usable backend throws the fail-closed SANDBOX_UNAVAILABLE. Policy rides per call. dsh-sandbox-local selects by platform and caches the verdict: multi-candidate chains probe FUNCTIONALLY in preference order (Linux: bwrap → the registry-installed node-addon-landlock-run launcher), a sole candidate is selected unprobed (darwin: sandbox-exec/Seatbelt) and fails closed at execution via runnerFailureSignatures; win32 is a reserved empty chain. Profile parity is honest per backend (documented temp-area and ABI differences; enforcement full|partial is a structured result fact). CI: the sandbox-e2e matrix proves real-kernel confinement per rung (bwrap, Landlock per architecture through the registry-installed launcher, Seatbelt), failing on a silent all-skip; the packed-install rehearsal installs the launcher family from the registry and asserts the binary executable apart from kernel enforcement.
121 lines
5.7 KiB
TypeScript
121 lines
5.7 KiB
TypeScript
import { spawnSync } from 'node:child_process'
|
|
import { existsSync, readFileSync } from 'node:fs'
|
|
import { mkdtemp, rm } from 'node:fs/promises'
|
|
import { homedir, tmpdir } from 'node:os'
|
|
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'
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
const probe = spawnSync('sandbox-exec', [...seatbeltProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' })
|
|
const seatbeltUsable = probe.status === 0
|
|
|
|
let ctx: Context | undefined
|
|
const tempDirs: string[] = []
|
|
|
|
afterEach(async () => {
|
|
await ctx?.fiber.dispose()
|
|
ctx = undefined
|
|
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
|
|
})
|
|
|
|
async function tempDir(base: string): Promise<string> {
|
|
const dir = await mkdtemp(join(base, 'dsh-seatbelt-e2e-'))
|
|
tempDirs.push(dir)
|
|
return dir
|
|
}
|
|
|
|
async function provider(): Promise<LocalSandboxProvider> {
|
|
ctx = new Context()
|
|
await ctx.plugin(LocalSandboxProvider, {})
|
|
const sandbox = ctx.sandbox as LocalSandboxProvider
|
|
sandbox.internals = { probeBwrap: () => false, probeLandlock: () => 'unusable' }
|
|
return sandbox
|
|
}
|
|
|
|
/** Confine a shell command under `policy` and run it for real; returns the spawn result and the wrap's facts. */
|
|
function runConfined(sandbox: LocalSandboxProvider, command: string, policy: SandboxPolicy) {
|
|
const confined = sandbox.confine(['bash', '-c', command], policy)
|
|
const result = spawnSync(confined.argv[0] as string, confined.argv.slice(1), { timeout: 30_000, encoding: 'utf8' })
|
|
return { result, confined }
|
|
}
|
|
|
|
describe.skipIf(!seatbeltUsable)('sandbox-local: real Seatbelt confinement through sandbox-exec', () => {
|
|
it('read-only denies a write — the file must NOT exist, and the kernel speaks the advertised dialect', async () => {
|
|
const workdir = await tempDir(tmpdir())
|
|
const sandbox = await provider()
|
|
const { result, confined } = runConfined(sandbox, `echo hi > ${workdir}/denied.txt`, { mode: 'read-only', workspaceRoot: workdir })
|
|
expect(result.status).not.toBe(0)
|
|
expect(confined.enforcement).toBe('full')
|
|
// The wrap's denialSignatures must be what the kernel actually prints.
|
|
expect(result.stderr.toLowerCase()).toContain('operation not permitted')
|
|
expect(existsSync(join(workdir, 'denied.txt'))).toBe(false)
|
|
})
|
|
|
|
it('read-only keeps the tree readable/executable and /dev/null writable', async () => {
|
|
const workdir = await tempDir(tmpdir())
|
|
const sandbox = await provider()
|
|
const { result } = runConfined(sandbox, 'ls / > /dev/null && echo dev-ok', { mode: 'read-only', workspaceRoot: workdir })
|
|
expect(result.status).toBe(0)
|
|
expect(result.stdout).toBe('dev-ok\n')
|
|
})
|
|
|
|
it('read-only grants no temp area: a write under the user temp dir is denied too', async () => {
|
|
// The per-user darwin temp dir is a workspace-write grant, not a
|
|
// read-only one — under read-only the only write-shaped path is /dev/null.
|
|
const workdir = await tempDir(tmpdir())
|
|
const sandbox = await provider()
|
|
const target = join(workdir, 'tmp-denied.txt')
|
|
const { result } = runConfined(sandbox, `echo hi > ${target}`, { mode: 'read-only', workspaceRoot: await tempDir(homedir()) })
|
|
expect(result.status).not.toBe(0)
|
|
expect(existsSync(target)).toBe(false)
|
|
})
|
|
|
|
it('workspace-write lands a write inside the workspace root and still denies one beside it', async () => {
|
|
const workdir = await tempDir(homedir())
|
|
const outside = await tempDir(homedir())
|
|
const sandbox = await provider()
|
|
|
|
const inside = runConfined(sandbox, `printf seatbelt-ok > ${workdir}/allowed.txt`, { mode: 'workspace-write', workspaceRoot: workdir })
|
|
expect(inside.result.status).toBe(0)
|
|
expect(readFileSync(join(workdir, 'allowed.txt'), 'utf8')).toBe('seatbelt-ok')
|
|
|
|
const denied = runConfined(sandbox, `echo hi > ${outside}/denied.txt`, { mode: 'workspace-write', workspaceRoot: workdir })
|
|
expect(denied.result.status).not.toBe(0)
|
|
expect(existsSync(join(outside, 'denied.txt'))).toBe(false)
|
|
})
|
|
|
|
it('workspace-write grants /tmp and the user temp dir (the documented Seatbelt-profile temp areas)', async () => {
|
|
const workdir = await tempDir(homedir())
|
|
const hostTmp = await tempDir('/tmp')
|
|
const userTmp = await tempDir(tmpdir())
|
|
const sandbox = await provider()
|
|
const { result } = runConfined(
|
|
sandbox,
|
|
`printf tmp-ok > ${hostTmp}/scratch.txt && printf user-tmp-ok > ${userTmp}/scratch.txt`,
|
|
{ mode: 'workspace-write', workspaceRoot: workdir },
|
|
)
|
|
expect(result.status).toBe(0)
|
|
expect(readFileSync(join(hostTmp, 'scratch.txt'), 'utf8')).toBe('tmp-ok')
|
|
expect(readFileSync(join(userTmp, 'scratch.txt'), 'utf8')).toBe('user-tmp-ok')
|
|
})
|
|
})
|