2530bf8aa3
- fs-sandbox: delegate the mutation with the freshly re-canonicalized target (not the stale one), so the checked identity is the mutated identity — a symlink swapped in after resolve() can no longer escape workspace-write. - tool-fs: map a denial to an FsError carrying FS_SANDBOX_DENIED (not a plain Error), so ToolRegistry keeps the structured code on result.error for retry/observers while the message stays the shared marker. - sandbox-local: derive the Seatbelt writable set from the shared writableRoots() helper, so the profile and the fs fence cannot drift. - gen-doc-graphs: ctx.sandboxPolicy is owned by dsh-sandbox-policy and read only by the sandboxed executor/provider (the tool layers use the pure fold). - docs: bash-sandbox/bash/permission READMEs and bash.md reflect the relocated policy home and the sandbox/mode rename; drop the stale stdout.golden.jsonl.
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
/**
|
|
* Internal platform-profile builders for the local sandbox provider.
|
|
*
|
|
* @module @deepseek-ai/dsh-sandbox-local/profiles
|
|
*/
|
|
|
|
import { grantArgs as landlockGrantArgs } from 'node-addon-landlock-run'
|
|
import { writableRoots } from '@deepseek-ai/dsh-sandbox'
|
|
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 })
|
|
}
|
|
|
|
/** 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. The
|
|
* writable roots come from the shared {@link writableRoots} helper (canonical,
|
|
* deduplicated) so the Seatbelt grant and the in-process fs fence
|
|
* (`@deepseek-ai/dsh-fs-sandbox`) can never drift apart.
|
|
* @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')}))`]
|
|
const roots = writableRoots(policy)
|
|
if (roots.length > 0) {
|
|
forms.push(`(allow file-write* ${roots.map(root => `(subpath ${sbplString(root)})`).join(' ')})`)
|
|
}
|
|
return ['-p', forms.join(' ')]
|
|
}
|