@alvin0/ai-agent-sdk-sandbox 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/approval.ts","../src/exec.ts","../src/path.ts","../src/entries.ts","../src/roots.ts","../src/fence.ts","../src/mode.ts","../src/network.ts","../src/classify.ts","../src/policy.ts","../src/resources.ts","../src/violation.ts"],"sourcesContent":["/** Fail-closed error surface. Silent unconfined passthrough is never legal. */\n\n/** Thrown when no backend on this host can enforce a confining policy. */\nexport class SandboxUnavailableError extends Error {\n readonly code = 'SANDBOX_UNAVAILABLE' as const\n /** Runner ids that were considered for this platform, in chain order. */\n readonly attempted: readonly string[]\n\n constructor(readonly platform: string, attempted: readonly string[] = [], detail?: string) {\n const options = attempted.length === 0 ? 'no runner is available' : `tried ${attempted.join(', ')}`\n super(`No sandbox backend can confine this execution on ${platform}: ${options}${detail === undefined ? '' : ` (${detail})`}`)\n this.name = 'SandboxUnavailableError'\n this.attempted = Object.freeze([...attempted])\n }\n}\n\n/** Thrown by the in-process fence when a mutation leaves the permitted roots. */\nexport class SandboxDeniedError extends Error {\n readonly code = 'SANDBOX_DENIED' as const\n\n constructor(readonly path: string, readonly mode: string, readonly writableRoots: readonly string[]) {\n const permitted = writableRoots.length === 0 ? 'nothing is writable' : `writable roots: ${writableRoots.join(', ')}`\n super(`Sandbox mode '${mode}' denies writing ${path} (${permitted})`)\n this.name = 'SandboxDeniedError'\n }\n}\n\n/** Thrown when a policy is structurally unusable before any backend is asked. */\nexport class SandboxPolicyError extends Error {\n readonly code = 'SANDBOX_POLICY_INVALID' as const\n\n constructor(message: string) {\n super(message)\n this.name = 'SandboxPolicyError'\n }\n}\n","/**\n * The authorization boundary.\n *\n * Everything a tool sends arrives as JSON the model can write, so a policy\n * input that widens authority is one the model can grant itself. The demonstrated\n * attack is exactly that: a tool call carrying `mode: 'danger-full-access'`, or\n * an entry granting write to `.git`, and the resolver honouring it because\n * nothing distinguished \"what was asked for\" from \"what was approved\".\n *\n * An approval is therefore a capability, not data: it exists only if this module\n * minted it, and `JSON.parse` cannot produce one. A host mints it after whatever\n * out-of-band check it uses — a human prompt, a policy engine — and only a\n * minted approval may widen a policy.\n */\n\nimport type { FileSystemEntry } from './entries.ts'\nimport { SandboxPolicyError } from './errors.ts'\nimport type { SandboxMode } from './mode.ts'\nimport type { NetworkMode } from './network.ts'\n\n/** How long a minted approval remains usable. */\nexport type SandboxApprovalScope = 'single-call' | 'session'\n\n/** What an approval permits, beyond what the session already allows. */\nexport interface SandboxApprovalGrant {\n /**\n * Whether the approval survives the call it was minted for.\n *\n * `single-call` — the default — is consumed the first time a policy is\n * resolved with it, so a grant given for one operation cannot be replayed\n * for the next. A person approving \"read this file\" approved one read.\n */\n readonly scope?: SandboxApprovalScope\n /** Wall-clock deadline in epoch milliseconds, after which it is refused. */\n readonly expiresAt?: number\n /** Mode the approval raises this call to. */\n readonly mode?: SandboxMode\n /** Entries the approval may add, including widening ones. */\n readonly entries?: readonly FileSystemEntry[]\n /** Network reach the approval raises this call to. */\n readonly network?: NetworkMode\n /** Free-text reason, carried for audit; never interpreted. */\n readonly justification?: string\n}\n\n/**\n * An approval token. Structurally it is just its grant, but only a value minted\n * by {@link approveSandboxEscalation} is accepted — a look-alike object, however\n * carefully shaped, is refused.\n */\nexport interface SandboxApproval extends SandboxApprovalGrant {\n readonly approved: true\n}\n\n/**\n * The minted set. A `WeakSet` is the whole mechanism: membership cannot be\n * forged, serialized, or reached from inside a tool payload.\n */\nconst MINTED = new WeakSet<object>()\n\n/** Approvals already consumed. Separate from minting, so the refusal differs. */\nconst SPENT = new WeakSet<object>()\n\n/**\n * Mint an approval. Call this only after the host has actually authorized the\n * escalation; the SDK cannot tell an approved grant from a requested one, which\n * is precisely why this call has to be the place the distinction is made.\n */\nexport function approveSandboxEscalation(grant: SandboxApprovalGrant): SandboxApproval {\n const approval: SandboxApproval = Object.freeze({ ...grant, approved: true })\n MINTED.add(approval)\n return approval\n}\n\n/** Whether a value is an approval this module minted. */\nexport function isSandboxApproval(value: unknown): value is SandboxApproval {\n return typeof value === 'object' && value !== null && MINTED.has(value)\n}\n\n/** Whether a minted approval has already been used up. */\nexport function isSandboxApprovalSpent(approval: SandboxApproval): boolean {\n return SPENT.has(approval)\n}\n\n/**\n * Accept an approval, or refuse it loudly.\n * @throws SandboxPolicyError when the value was not minted here — which is what\n * a forged approval arriving through a tool payload looks like.\n */\nexport function requireSandboxApproval(value: unknown, now: number = Date.now()): SandboxApproval {\n if (!isSandboxApproval(value)) {\n throw new SandboxPolicyError(\n 'Sandbox escalation requires an approval minted by approveSandboxEscalation(); '\n + 'a plain object cannot widen a policy, because a tool payload can contain one',\n )\n }\n if (value.expiresAt !== undefined && now > value.expiresAt) {\n throw new SandboxPolicyError(\n 'This sandbox approval has expired; an escalation outliving the moment it was '\n + 'granted is an escalation nobody is still watching',\n )\n }\n if (SPENT.has(value)) {\n throw new SandboxPolicyError(\n 'This sandbox approval was already used; a grant given for one operation cannot '\n + 'be replayed for the next',\n )\n }\n if ((value.scope ?? 'single-call') === 'single-call') SPENT.add(value)\n return value\n}\n","/**\n * Reading a command for what it *does*, not just what it touches.\n *\n * The file seam cannot tell `systemctl status nginx` from `systemctl restart\n * nginx`: both are argv, neither writes a file the policy cares about, and one\n * observes while the other changes the machine. Deciding between them needs the\n * command read semantically, before any of it runs.\n *\n * This is a classifier, and a classifier is a guess. Two rules keep the guess\n * from becoming a hazard: a command it does not recognise is never allowed, and\n * a command that hides other commands — a shell string, a pipeline, a chain —\n * is classified by the riskiest thing inside it rather than by its wrapper.\n */\n\n/** What a command does to the machine, in increasing order of consequence. */\nexport type ExecCapability =\n | 'observe'\n | 'use'\n | 'modify'\n | 'service-control'\n | 'package-install'\n | 'privilege'\n | 'credential'\n | 'critical'\n | 'unknown'\n\n/** What a harness should do with a command carrying that capability. */\nexport type ExecOutcome = 'allow' | 'allow-scoped' | 'ask-approval' | 'deny'\n\n/** One classified command, and why it was classified that way. */\nexport interface ExecClassification {\n readonly capability: ExecCapability\n readonly outcome: ExecOutcome\n /** The program the decision was made about, after unwrapping. */\n readonly program: string\n /** Why, in terms a person approving it can check. */\n readonly reason: string\n /** Every command found inside a shell string or chain, already classified. */\n readonly parts: readonly ExecClassification[]\n}\n\n/** The outcome each capability maps to, before a deployment adjusts it. */\nexport const DEFAULT_EXEC_OUTCOMES: Readonly<Record<ExecCapability, ExecOutcome>> = Object.freeze({\n observe: 'allow',\n use: 'allow-scoped',\n modify: 'ask-approval',\n 'service-control': 'ask-approval',\n 'package-install': 'ask-approval',\n privilege: 'ask-approval',\n credential: 'deny',\n critical: 'deny',\n // A command nobody recognised is not a safe command; it is an unread one.\n unknown: 'ask-approval',\n})\n\n/** Commands that only report state. */\nconst OBSERVE = new Set([\n 'uname', 'uptime', 'hostname', 'date', 'whoami', 'id', 'df', 'du', 'free', 'vmstat',\n 'top', 'ps', 'pgrep', 'lsof', 'ss', 'netstat', 'ifconfig', 'ip', 'arch', 'sw_vers',\n 'sysctl', 'lscpu', 'lsblk', 'nproc', 'env', 'printenv', 'pwd', 'which', 'whereis',\n 'cat', 'head', 'tail', 'less', 'more', 'wc', 'stat', 'file', 'ls', 'find', 'grep',\n 'rg', 'awk', 'sed', 'sort', 'uniq', 'cut', 'diff', 'md5sum', 'sha256sum', 'journalctl',\n 'dmesg', 'log', 'tree', 'readlink', 'realpath', 'basename', 'dirname', 'echo',\n // Added after running real model output through this classifier: these are\n // what a model reaches for when asked how a machine is doing, and leaving\n // them unrecognised turned \"check CPU and RAM\" into an approval prompt.\n 'vm_stat', 'iostat', 'mpstat', 'sar', 'pmap', 'swapon', 'getconf', 'sysctl',\n 'launchctl', 'pgrep', 'pidof', 'w', 'last', 'lsattr', 'mount', 'blkid',\n])\n\n/** Project tooling: runs inside a workspace and is scoped by the file policy. */\nconst USE = new Set([\n 'node', 'npm', 'npx', 'pnpm', 'yarn', 'bun', 'deno', 'tsc', 'vitest', 'jest',\n 'python', 'python3', 'pip', 'pip3', 'pytest', 'ruby', 'bundle', 'go', 'cargo',\n 'rustc', 'java', 'mvn', 'gradle', 'make', 'cmake', 'git', 'docker', 'kubectl',\n 'terraform', 'gh', 'curl', 'wget', 'jq',\n])\n\n/** Commands that change files. */\nconst MODIFY = new Set([\n 'cp', 'mv', 'rm', 'mkdir', 'rmdir', 'touch', 'ln', 'chmod', 'chown', 'chgrp',\n 'truncate', 'dd', 'tee', 'install', 'patch', 'tar', 'unzip', 'zip',\n])\n\n/** Commands that start, stop or signal running services. */\nconst SERVICE_CONTROL = new Set([\n 'systemctl', 'service', 'launchctl', 'initctl', 'kill', 'killall', 'pkill',\n 'supervisorctl', 'brew', 'nginx', 'apachectl', 'pm2',\n])\n\n/** Commands that install software. */\nconst PACKAGE_INSTALL = new Set([\n 'apt', 'apt-get', 'dpkg', 'yum', 'dnf', 'rpm', 'pacman', 'apk', 'snap', 'port',\n])\n\n/** Commands that run as another, more powerful, identity. */\nconst PRIVILEGE = new Set(['sudo', 'su', 'doas', 'pkexec', 'runas'])\n\n/** Commands whose effect cannot be undone from inside a session. */\nconst CRITICAL = new Set([\n 'reboot', 'shutdown', 'halt', 'poweroff', 'mkfs', 'fdisk', 'parted',\n 'iptables', 'nft', 'ufw', 'pfctl', 'usermod', 'useradd', 'userdel', 'passwd',\n 'visudo', 'csrutil', 'spctl',\n])\n\n/**\n * Programs that read a credential without naming where it lives.\n *\n * Found by running real model output through this classifier: asked to show\n * AWS credentials, a model proposed `aws configure list` and `aws sts\n * get-caller-identity`. Neither names `~/.aws/credentials`, so a rule that\n * matches credential PATHS sees nothing — the secret is read inside the tool.\n * The program is the signal here, not its arguments.\n */\nconst CREDENTIAL_TOOLS = new Set([\n 'aws', 'gcloud', 'az', 'doctl', 'heroku', 'op', 'vault', 'pass', 'keyring',\n 'security', 'gpg', 'ssh-add', 'ssh-agent', 'kubelogin', 'aws-vault',\n])\n\n/** Subcommands of those tools that only report non-secret state. */\nconst CREDENTIAL_TOOL_SAFE_VERBS: Readonly<Record<string, ReadonlySet<string>>> = Object.freeze({\n // Even \"list\" prints an account identity, so nothing here is safe by default;\n // the map exists so a deployment can widen it deliberately rather than by\n // the classifier guessing.\n})\n\n/** Shell keywords that lead a segment without being the command. */\nconst SHELL_KEYWORDS = new Set([\n 'if', 'then', 'else', 'elif', 'fi', 'do', 'done', 'while', 'until', 'for', 'case',\n 'esac', 'in', '{', '}', '(', ')', '!', 'time', 'exec',\n])\n\n/** Paths whose contents authorize something, wherever they are read from. */\nconst CREDENTIAL_PATTERN =\n /(^|\\/)(\\.ssh|\\.aws|\\.gnupg|\\.kube|\\.docker\\/config\\.json|\\.npmrc|\\.netrc|\\.git-credentials|credentials|id_[a-z0-9]+|.*\\.pem|.*\\.key|\\.env(\\.[a-z]+)?)(\\/|$)/i\n\n/** Paths whose modification changes who may do what on the machine. */\nconst CRITICAL_PATH_PATTERN =\n /(^|\\/)(etc\\/(sudoers|shadow|passwd|ssh\\/sshd_config|pam\\.d)|boot|sys\\/kernel|proc\\/sys)(\\/|$)/i\n\n/** Shells whose `-c` argument is another command entirely. */\nconst SHELLS = new Set(['sh', 'bash', 'zsh', 'dash', 'ksh', 'fish', 'pwsh', 'powershell'])\n\n/** Wrappers that run another command without changing what it does. */\nconst TRANSPARENT = new Set(['env', 'nice', 'ionice', 'nohup', 'stdbuf', 'time', 'timeout', 'command'])\n\n/**\n * Classify one command.\n * @param argv - the exact argv, program first. A shell string is read through.\n * @param outcomes - the capability-to-outcome mapping a deployment uses.\n */\nexport function classifyExec(\n argv: readonly string[],\n outcomes: Readonly<Record<ExecCapability, ExecOutcome>> = DEFAULT_EXEC_OUTCOMES,\n): ExecClassification {\n // A segment that is only shell keywords — a bare `fi`, a closing `done` — is\n // punctuation, not a command. Left in, it classifies as unrecognised and\n // drags a whole chain to an approval prompt: `if ...; then npm test; fi`\n // would ask about running its own test suite.\n const parts = splitCommands(argv)\n .map(part => classifySingle(part, outcomes))\n .filter(part => part.program !== '(empty)')\n if (parts.length === 0) {\n return decide('unknown', '(empty)', 'no command to classify', [], outcomes)\n }\n if (parts.length === 1) return parts[0] as ExecClassification\n\n // A chain is as consequential as its worst link; wrapping `rm -rf /etc` after\n // an `echo` must not make the pair look like an `echo`.\n const worst = parts.reduce((left, right) =>\n CAPABILITY_RANK[right.capability] > CAPABILITY_RANK[left.capability] ? right : left)\n return decide(\n worst.capability, worst.program,\n `a chain of ${String(parts.length)} commands, decided by its riskiest: ${worst.reason}`,\n parts, outcomes,\n )\n}\n\n/** Consequence order, used to pick the decisive command in a chain. */\nconst CAPABILITY_RANK: Readonly<Record<ExecCapability, number>> = Object.freeze({\n observe: 0, use: 1, unknown: 2, modify: 3, 'service-control': 4,\n 'package-install': 5, privilege: 6, credential: 7, critical: 8,\n})\n\n/** Build a classification with its outcome resolved. */\nfunction decide(\n capability: ExecCapability, program: string, reason: string,\n parts: readonly ExecClassification[],\n outcomes: Readonly<Record<ExecCapability, ExecOutcome>>,\n): ExecClassification {\n return Object.freeze({\n capability, outcome: outcomes[capability], program, reason, parts: Object.freeze(parts),\n })\n}\n\n/** Classify a single command that contains no further commands. */\nfunction classifySingle(\n argv: readonly string[],\n outcomes: Readonly<Record<ExecCapability, ExecOutcome>>,\n): ExecClassification {\n // Redirection is not a command, so splitting on command separators never\n // finds it — yet `echo x > file` writes a file while naming only `echo`.\n const redirect = argv.findIndex(token => token === '>' || token === '>>')\n if (redirect >= 0) {\n const target = argv[redirect + 1] ?? ''\n const inner = classifySingle(argv.slice(0, redirect), outcomes)\n const written = classifySingle(['tee', target], outcomes)\n return CAPABILITY_RANK[written.capability] > CAPABILITY_RANK[inner.capability]\n ? decide(written.capability, inner.program, `redirects output into ${target}`, [], outcomes)\n : decide(inner.capability, inner.program, inner.reason, [], outcomes)\n }\n const stripped = stripWrappers(argv)\n const program = basename(stripped[0] ?? '')\n const args = stripped.slice(1)\n if (program === '') return decide('unknown', '(empty)', 'no program named', [], outcomes)\n\n // A path decides before the program does: reading a private key is reading a\n // private key whether `cat` or `grep` does it.\n const touched = args.filter(argument => !argument.startsWith('-'))\n const credential = touched.find(argument => CREDENTIAL_PATTERN.test(argument))\n if (credential !== undefined) {\n return decide('credential', program, `names a credential path: ${credential}`, [], outcomes)\n }\n const critical = touched.find(argument => CRITICAL_PATH_PATTERN.test(argument))\n if (critical !== undefined) {\n return decide('critical', program, `names a path that governs access: ${critical}`, [], outcomes)\n }\n\n // A flag can turn a reading tool into a writing one, and the program name\n // alone then reads as harmless: `sed -i` edits the file it is pointed at.\n const inPlace = IN_PLACE_FLAGS[program]\n if (inPlace !== undefined && args.some(argument => inPlace.some(flag => argument === flag || argument.startsWith(`${flag}`) && flag === '-i'))) {\n return decide('modify', program, 'edits its input in place', [], outcomes)\n }\n if (program === 'find' && args.some(argument =>\n argument === '-delete' || argument === '-exec' || argument === '-execdir'\n || argument === '-ok' || argument === '-okdir')) {\n return decide('modify', program, 'runs an action that may change files', [], outcomes)\n }\n if (program === 'awk' && args.some(argument => /(^|[^A-Za-z_])system\\s*\\(/.test(argument))) {\n return decide('unknown', program, 'evaluates another command dynamically', [], outcomes)\n }\n if (program === 'rm' && touched.some(argument => ROOTISH.has(argument.replace(/\\/+$/, '') || '/'))) {\n return decide('critical', program, `removes a system root: ${touched.join(' ')}`, [], outcomes)\n }\n\n if (CREDENTIAL_TOOLS.has(program)) {\n const verb = touched[0] ?? ''\n if (!(CREDENTIAL_TOOL_SAFE_VERBS[program]?.has(verb) ?? false)) {\n return decide('credential', program,\n `reads a credential the command never names${verb === '' ? '' : ` (${verb})`}`, [], outcomes)\n }\n }\n if (PRIVILEGE.has(program)) {\n return decide('privilege', program, 'runs as another identity', [], outcomes)\n }\n if (CRITICAL.has(program)) {\n return decide('critical', program, 'changes the machine in a way a session cannot undo', [], outcomes)\n }\n if (PACKAGE_INSTALL.has(program)) {\n return decide('package-install', program, 'installs software', [], outcomes)\n }\n if (SERVICE_CONTROL.has(program)) return classifyServiceCommand(program, args, outcomes)\n if (MODIFY.has(program)) return decide('modify', program, 'changes files', [], outcomes)\n if (USE.has(program)) return classifyToolCommand(program, args, outcomes)\n // `command -v foo` and `[ -f x ]` are tests, not the thing being tested.\n if (program === 'command' || program === '[' || program === 'test' || program === '[[') {\n return decide('observe', program, 'tests for something without running it', [], outcomes)\n }\n if (OBSERVE.has(program)) return decide('observe', program, 'reports state without changing it', [], outcomes)\n\n return decide('unknown', program, 'not a command this policy recognises', [], outcomes)\n}\n\n/**\n * Where a program puts the verb. `systemctl restart nginx` names the action\n * first; `service nginx restart` names the unit first, and reading position 0\n * for both makes every `service` invocation look like a control action.\n */\nconst VERB_POSITION: Readonly<Record<string, number>> = Object.freeze({\n service: 1, systemctl: 0, launchctl: 0, initctl: 0, supervisorctl: 0, brew: 0, pm2: 0,\n})\n\n/** Flags that turn a reading tool into a writing one. */\nconst IN_PLACE_FLAGS: Readonly<Record<string, readonly string[]>> = Object.freeze({\n sed: ['-i', '--in-place'],\n perl: ['-i'],\n awk: ['-i', '--in-place'],\n ruby: ['-i'],\n})\n\n/** Paths whose wholesale removal is not something a session can undo. */\nconst ROOTISH = new Set(['/', '/*', '/etc', '/usr', '/bin', '/sbin', '/var', '/boot', '/System', '/Library'])\n\n/** Verbs that only report a service's state. */\nconst SERVICE_READ_VERBS = new Set(['status', 'show', 'list', 'list-units', 'is-active',\n 'is-enabled', 'cat', 'get', 'services', 'print', 'info', 'ls', 'config', 'list-unit-files'])\n\n/**\n * Separate observing a service from controlling one — the distinction the file\n * seam cannot make, and the reason this module exists.\n */\nfunction classifyServiceCommand(\n program: string, args: readonly string[],\n outcomes: Readonly<Record<ExecCapability, ExecOutcome>>,\n): ExecClassification {\n const positional = args.filter(argument => !argument.startsWith('-'))\n const verb = positional[VERB_POSITION[program] ?? 0]\n if (verb === undefined) {\n // No action named: `systemctl`, `systemctl --failed`, `launchctl` alone all\n // list units. A real model reaches for exactly these when asked what is\n // broken, and reading them as control actions asks for approval to look.\n return decide('observe', program, 'lists services without naming an action', [], outcomes)\n }\n if (SERVICE_READ_VERBS.has(verb)) {\n return decide('observe', program, `reports service state (${verb})`, [], outcomes)\n }\n if (program === 'nginx' && args.includes('-t')) {\n return decide('observe', program, 'checks configuration without applying it', [], outcomes)\n }\n return decide('service-control', program, `changes a running service (${verb})`, [], outcomes)\n}\n\n/** Tool subcommands that install outside the workspace or raise privilege. */\nfunction classifyToolCommand(\n program: string, args: readonly string[],\n outcomes: Readonly<Record<ExecCapability, ExecOutcome>>,\n): ExecClassification {\n const global = args.includes('-g') || args.includes('--global') || args.includes('--location=global')\n if (global && (program === 'npm' || program === 'pnpm' || program === 'yarn')) {\n return decide('package-install', program, 'installs outside the workspace', [], outcomes)\n }\n if (program === 'docker' && args.some(argument => argument === 'run' || argument === 'exec')) {\n // A container can be given the host; the file seam never sees inside it.\n return decide('service-control', program, 'starts or enters a container', [], outcomes)\n }\n return decide('use', program, 'project tooling, scoped by the file policy', [], outcomes)\n}\n\n/** Remove wrappers, shell keywords and leading assignments without losing meaning. */\nfunction stripWrappers(argv: readonly string[]): readonly string[] {\n let current = [...argv]\n for (let guard = 0; guard < 8; guard++) {\n // Real shell output leads with keywords and braces: `then npm test` names\n // `then`, which is not a program and classifies as unrecognised, turning an\n // ordinary test run into an approval prompt.\n while (current.length > 0 && SHELL_KEYWORDS.has(current[0] ?? '')) current.shift()\n while (current.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(current[0] ?? '')) current.shift()\n const program = basename(current[0] ?? '')\n if (!TRANSPARENT.has(program)) break\n // `timeout 30 cmd` and `nice -n 5 cmd`: drop the wrapper and its own flags.\n current = current.slice(1)\n while (current.length > 0 && (current[0] ?? '').startsWith('-')) current.shift()\n if (program === 'timeout' && current.length > 0 && /^[0-9]/.test(current[0] ?? '')) current.shift()\n }\n return current\n}\n\n/**\n * Every command an argv actually runs.\n *\n * A shell invocation carries its real command in a string, and that string can\n * hold several. Splitting it with a pattern cannot work: `grep -E 'a|b'` puts a\n * separator inside a quoted word, and a pattern either splits there — inventing\n * commands out of a regex — or refuses to split anywhere a quote appears. The\n * script is therefore walked one character at a time, so a separator only\n * separates when nothing is quoting it.\n */\nexport function splitCommands(argv: readonly string[]): readonly (readonly string[])[] {\n const stripped = stripWrappers(argv)\n const program = basename(stripped[0] ?? '')\n const flagIndex = stripped.findIndex(argument => argument === '-c' || argument === '-Command')\n\n let script: string | undefined\n if (SHELLS.has(program) && flagIndex >= 0) {\n script = stripped[flagIndex + 1]\n } else if (stripped.some(token => UNQUOTED_SEPARATOR.test(token))) {\n // An argv is not always one command. A model emits a whole command line,\n // and `if [ -f package.json ]; then npm test; fi` names `[` first — read as\n // one argv it looks like a test, while what it runs is the test suite.\n script = stripped.join(' ')\n }\n if (script === undefined) return [stripped]\n\n const commands = tokenizeScript(script)\n if (hasDynamicShellSyntax(script)) {\n return Object.freeze([...commands, Object.freeze(['(dynamic-shell-syntax)'])])\n }\n return commands.length === 0 ? [stripped] : commands\n}\n\n/** Syntax that can execute code our deliberately small tokenizer cannot see. */\nfunction hasDynamicShellSyntax(script: string): boolean {\n let quote: '\"' | \"'\" | undefined\n for (let index = 0; index < script.length; index++) {\n const character = script[index] ?? ''\n if (character === '\\\\' && quote !== \"'\") { index += 1; continue }\n if (character === \"'\") {\n if (quote === undefined) quote = \"'\"\n else if (quote === \"'\") quote = undefined\n continue\n }\n if (character === '\"') {\n if (quote === undefined) quote = '\"'\n else if (quote === '\"') quote = undefined\n continue\n }\n if (quote === \"'\") continue\n if (character === '`') return true\n const pair = script.slice(index, index + 2)\n if (pair === '$(' || pair === '<(' || pair === '>(') return true\n }\n return quote !== undefined\n}\n\n/** A separator that is not inside quotes, used only to decide whether to walk. */\nconst UNQUOTED_SEPARATOR = /^(&&|\\|\\||;|\\|)$|[;|&]/\n\n/**\n * Split a shell script into commands, respecting quotes and escapes.\n *\n * Deliberately not a shell parser: it does not expand, substitute, or\n * understand control flow. It answers one question — which words belong to\n * which command — and leaves the rest to the classifier, which treats anything\n * it cannot read as something to ask about.\n */\nexport function tokenizeScript(script: string): readonly (readonly string[])[] {\n const commands: string[][] = []\n let command: string[] = []\n let word = ''\n let quote: '\"' | \"'\" | undefined\n let index = 0\n\n const endWord = (): void => { if (word !== '') { command.push(word); word = '' } }\n const endCommand = (): void => {\n endWord()\n if (command.length > 0) commands.push(command)\n command = []\n }\n\n while (index < script.length) {\n const character = script[index] ?? ''\n\n if (quote !== undefined) {\n if (character === '\\\\' && quote === '\"' && index + 1 < script.length) {\n word += script[index + 1] ?? ''\n index += 2\n continue\n }\n if (character === quote) quote = undefined\n else word += character\n index += 1\n continue\n }\n\n if (character === '\"' || character === \"'\") { quote = character; index += 1; continue }\n if (character === '\\\\' && index + 1 < script.length) {\n word += script[index + 1] ?? ''\n index += 2\n continue\n }\n if (character === ' ' || character === '\\t') { endWord(); index += 1; continue }\n if (character === '\\n') { endCommand(); index += 1; continue }\n\n // Separators end a command; redirection is kept as its own word so the\n // classifier can see that output goes into a file.\n const pair = script.slice(index, index + 2)\n if (pair === '&&' || pair === '||') { endCommand(); index += 2; continue }\n if (pair === '>>') { endWord(); command.push('>>'); index += 2; continue }\n if (character === ';' || character === '|' || character === '&') { endCommand(); index += 1; continue }\n if (character === '>') { endWord(); command.push('>'); index += 1; continue }\n if (character === '(' || character === ')' || character === '{' || character === '}') {\n endWord(); index += 1; continue\n }\n\n word += character\n index += 1\n }\n endCommand()\n return commands\n}\n\n/** The final path segment, so `/usr/bin/systemctl` decides like `systemctl`. */\nfunction basename(value: string): string {\n const cleaned = value.replaceAll('\\\\', '/')\n return cleaned.slice(cleaned.lastIndexOf('/') + 1)\n}\n","/**\n * Pure, dependency-free path algebra shared by every sandbox backend.\n *\n * This package is Universal: it must never import `node:path`, so the lexical\n * rules both POSIX and Win32 backends rely on live here as string operations.\n * Filesystem-dependent resolution (realpath, existence) is injected through\n * {@link PathResolver} by the Node-elevated provider instead.\n */\n\n/** Which lexical dialect a path is written in. */\nexport type PathFlavor = 'posix' | 'win32'\n\nconst WIN32_DRIVE = /^[A-Za-z]:[\\\\/]/\nconst WIN32_UNC = /^\\\\\\\\[^\\\\/]+[\\\\/][^\\\\/]+/\n\n/** Detect the dialect of an absolute path from its own shape. */\nexport function detectFlavor(path: string): PathFlavor {\n return WIN32_DRIVE.test(path) || WIN32_UNC.test(path) ? 'win32' : 'posix'\n}\n\n/** Whether the path is absolute in either dialect. */\nexport function isAbsolutePath(path: string): boolean {\n return path.startsWith('/') || WIN32_DRIVE.test(path) || WIN32_UNC.test(path)\n}\n\n/**\n * Collapse separators and resolve `.` / `..` lexically, without touching the\n * filesystem. A `..` that would escape the root is dropped, matching how both\n * bwrap and Seatbelt treat an over-popped absolute path.\n */\nexport function normalizePath(path: string): string {\n if (path === '') return path\n const flavor = detectFlavor(path)\n const unified = flavor === 'win32' ? path.replaceAll('\\\\', '/') : path\n const prefix = rootPrefix(unified, flavor)\n const body = unified.slice(prefix.length)\n const resolved: string[] = []\n for (const segment of body.split('/')) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n if (resolved.length > 0) resolved.pop()\n else if (prefix === '') resolved.push('..')\n continue\n }\n resolved.push(segment)\n }\n const joined = resolved.join('/')\n if (prefix === '') return joined === '' ? '.' : joined\n return joined === '' ? prefix : `${prefix}${joined}`\n}\n\n/** The absolute-root prefix of a path (`/`, `C:/`, `//server/share/`), or `''`. */\nfunction rootPrefix(unified: string, flavor: PathFlavor): string {\n if (flavor === 'win32') {\n if (WIN32_DRIVE.test(unified)) return `${unified.slice(0, 2).toUpperCase()}/`\n const unc = /^\\/\\/[^/]+\\/[^/]+/.exec(unified)\n if (unc?.[0] !== undefined) return `${unc[0]}/`\n }\n return unified.startsWith('/') ? '/' : ''\n}\n\n/** Normalized segments below the root prefix; the root itself has none. */\nexport function pathSegments(path: string): readonly string[] {\n const normalized = normalizePath(path)\n const prefix = rootPrefix(normalized, detectFlavor(normalized))\n const body = normalized.slice(prefix.length)\n return body === '' ? [] : body.split('/')\n}\n\n/**\n * Specificity rank used to order overlapping policy entries. A deeper path is\n * more specific, so it is applied later and wins over a broader ancestor.\n */\nexport function pathDepth(path: string): number {\n return pathSegments(path).length\n}\n\n/** Case-fold a normalized path for comparison under its own dialect. */\nfunction foldCase(path: string): string {\n return detectFlavor(path) === 'win32' ? path.toLowerCase() : path\n}\n\n/** Whether two paths identify the same location lexically. */\nexport function samePath(left: string, right: string): boolean {\n return foldCase(normalizePath(left)) === foldCase(normalizePath(right))\n}\n\n/**\n * Whether `candidate` is `root` itself or lies beneath it. Comparison is\n * segment-wise, so `/repo-secrets` is never treated as inside `/repo`.\n */\nexport function containsPath(root: string, candidate: string): boolean {\n const base = foldCase(normalizePath(root))\n const target = foldCase(normalizePath(candidate))\n if (base === target) return true\n const prefix = base.endsWith('/') ? base : `${base}/`\n return target.startsWith(prefix)\n}\n\n/** Append relative segments to an absolute base, normalizing the result. */\nexport function joinPath(base: string, ...parts: readonly string[]): string {\n const separator = base.endsWith('/') ? '' : '/'\n return normalizePath(parts.length === 0 ? base : `${base}${separator}${parts.join('/')}`)\n}\n\n/** The parent of a normalized path, or `undefined` at a filesystem root. */\nexport function parentPath(path: string): string | undefined {\n const normalized = normalizePath(path)\n const prefix = rootPrefix(normalized, detectFlavor(normalized))\n if (normalized === prefix) return undefined\n const cut = normalized.lastIndexOf('/')\n if (cut < 0) return undefined\n const parent = normalized.slice(0, cut)\n return parent.length < prefix.length ? prefix : parent === '' ? prefix : parent\n}\n\n/** Every ancestor of `path` from the filesystem root down to `path` itself. */\nexport function ancestorPaths(path: string): readonly string[] {\n const chain: string[] = []\n let current: string | undefined = normalizePath(path)\n while (current !== undefined) {\n chain.unshift(current)\n current = parentPath(current)\n }\n return chain\n}\n\n/** Drop paths already covered by a broader entry in the same list. */\nexport function dedupeRoots(roots: readonly string[]): readonly string[] {\n const normalized = [...new Set(roots.map(root => normalizePath(root)))]\n normalized.sort((left, right) => pathDepth(left) - pathDepth(right) || left.localeCompare(right))\n const kept: string[] = []\n for (const root of normalized) {\n if (!kept.some(existing => containsPath(existing, root))) kept.push(root)\n }\n return Object.freeze(kept)\n}\n","/**\n * Nested filesystem carve-outs.\n *\n * A single writable root is not enough: an agent that may write in a repository\n * must still be kept out of `.git`, and an operator must be able to reopen one\n * directory beneath a denied parent. Entries express that as an overlapping\n * list resolved by path specificity — the deepest matching entry wins, so\n * `/repo = write`, `/repo/a = deny`, `/repo/a/b = write` behaves as written.\n */\n\nimport { containsPath, normalizePath, pathDepth } from './path.ts'\n\n/** What one entry grants for the subtree it names. */\nexport type FileSystemAccess = 'write' | 'read' | 'deny'\n\n/** One carve-out in a filesystem policy. */\nexport interface FileSystemEntry {\n /** Absolute path whose subtree this entry governs. */\n readonly path: string\n /** Access granted for that subtree, overriding any broader entry. */\n readonly access: FileSystemAccess\n}\n\n/**\n * Order entries from broadest to narrowest so a consumer can apply them in\n * sequence and let the most specific one win. Equal-depth entries keep a stable\n * lexical order so a policy always produces the same backend profile.\n */\nexport function orderEntries(entries: readonly FileSystemEntry[]): readonly FileSystemEntry[] {\n return Object.freeze(\n [...entries]\n .map(entry => Object.freeze({ path: normalizePath(entry.path), access: entry.access }))\n .sort((left, right) => pathDepth(left.path) - pathDepth(right.path) || left.path.localeCompare(right.path)),\n )\n}\n\n/**\n * Resolve the effective access for one target against an ordered entry list.\n * @param target - absolute path being evaluated.\n * @param entries - carve-outs, in any order.\n * @param fallback - access to use when no entry covers the target.\n */\nexport function accessFor(\n target: string,\n entries: readonly FileSystemEntry[],\n fallback: FileSystemAccess,\n): FileSystemAccess {\n let effective = fallback\n for (const entry of orderEntries(entries)) {\n if (containsPath(entry.path, target)) effective = entry.access\n }\n return effective\n}\n\n/**\n * Entries that carve a narrower rule *inside* `root`. A backend that grants\n * `root` wholesale must re-apply these afterwards or the grant is too wide.\n */\nexport function entriesWithin(\n root: string,\n entries: readonly FileSystemEntry[],\n): readonly FileSystemEntry[] {\n return Object.freeze(\n orderEntries(entries).filter(entry => containsPath(root, entry.path) && !containsPath(entry.path, root)),\n )\n}\n","/**\n * The layered grant model — the single source of truth for file effects.\n *\n * A policy is not two sets of paths. It is an ordered stack of layers, broadest\n * first, each one overriding the layers beneath it for its own subtree. Order is\n * the semantics: `/repo` writable, `/repo/vendor` denied, `/repo/vendor/cache`\n * writable again is three layers, and flattening them into \"granted roots\" plus\n * \"denied paths\" loses the third — a narrower grant beneath a denial has no way\n * to survive a set that only records what is broadly writable.\n *\n * Every enforcement path reads this one function: the bubblewrap mounts, the\n * Seatbelt profile, and the in-process fence. Deriving them separately is how a\n * kernel profile and an application fence drift into disagreeing about what is\n * writable.\n */\n\nimport type { FileSystemAccess, FileSystemEntry } from './entries.ts'\nimport { accessFor, orderEntries } from './entries.ts'\nimport type { SandboxPolicy } from './policy.ts'\nimport { containsPath, joinPath, normalizePath, pathDepth } from './path.ts'\n\n/**\n * Directory names never writable inside a granted root. Writing `.git` lets a\n * command install a hook that runs arbitrary code on the next git invocation,\n * which defeats the point of confining the command; the credential files are\n * there so a confined command cannot rewrite the caller's own authentication.\n * They stay readable — this is a write boundary, not a read boundary.\n */\nexport const PROTECTED_SUBPATHS: readonly string[] = Object.freeze([\n '.git', '.hg', '.svn', '.ssh', '.aws', '.npmrc', '.netrc',\n])\n\n/** Where a layer came from, which decides ties at equal path specificity. */\nexport type GrantOrigin = 'mode' | 'protected' | 'entry' | 'restriction' | 'approval'\n\n/** One subtree's access, overriding whatever the layers beneath it said. */\nexport interface GrantLayer {\n /** Absolute, normalized path whose subtree this layer governs. */\n readonly path: string\n /** Access this layer establishes for that subtree. */\n readonly access: FileSystemAccess\n /** What contributed the layer; an explicit entry outranks a generated one. */\n readonly origin: GrantOrigin\n}\n\n/**\n * The baseline every policy starts from: the host is readable and nothing is\n * writable. Layers only ever move a subtree away from this.\n */\nexport const BASELINE_ACCESS: FileSystemAccess = 'read'\n\n/** Optional platform inputs the caller knows and this package must not guess. */\nexport interface WritableRootOptions {\n /** Temp directories `workspace-write` may also use (e.g. the OS temp root). */\n readonly tempRoots?: readonly string[]\n /** Whether to layer {@link PROTECTED_SUBPATHS} under every granted root. */\n readonly protectSubpaths?: boolean\n /**\n * Absolute paths the platform hides outright — credential stores and host\n * daemon sockets. Layered as `deny` above the mode grant and below any\n * explicit entry, so a deployment can still reopen one deliberately while a\n * tool-supplied restriction cannot widen it.\n */\n readonly deniedPaths?: readonly string[]\n /**\n * Permit writes to a file whose inode carries more than one name. Off by\n * default: a hard link reaching out of the workspace is otherwise invisible\n * to a boundary made of paths.\n */\n readonly allowAliasedWrites?: boolean\n}\n\nconst ORIGIN_RANK: Readonly<Record<GrantOrigin, number>> = Object.freeze({\n mode: 0, protected: 1, entry: 2, restriction: 3, approval: 4,\n})\n\nconst ACCESS_RANK: Readonly<Record<FileSystemAccess, number>> = Object.freeze({\n deny: 0, read: 1, write: 2,\n})\n\nfunction narrower(left: FileSystemAccess, right: FileSystemAccess): FileSystemAccess {\n return ACCESS_RANK[left] <= ACCESS_RANK[right] ? left : right\n}\n\nfunction collapseLayers(\n proposed: readonly GrantLayer[], baseline: FileSystemAccess,\n): GrantLayer[] {\n const sorted = [...proposed].sort((left, right) =>\n pathDepth(left.path) - pathDepth(right.path)\n || ORIGIN_RANK[left.origin] - ORIGIN_RANK[right.origin]\n || left.path.localeCompare(right.path))\n const lastAtPath = new Map<string, number>()\n sorted.forEach((layer, index) => lastAtPath.set(layer.path, index))\n const kept: GrantLayer[] = []\n sorted.forEach((layer, index) => {\n if (lastAtPath.get(layer.path) === index\n && accessInLayers(layer.path, kept, baseline) !== layer.access) {\n kept.push(Object.freeze(layer))\n }\n })\n return kept\n}\n\n/**\n * Resolve a policy into the ordered layers that express it.\n *\n * Layers are sorted broadest to narrowest, and an explicit entry wins a tie at\n * equal depth so a deployment can deliberately reopen a protected subpath. A\n * layer that would not change the access already in force is dropped, so the\n * result carries no mount or profile rule that does nothing.\n */\nexport function grantLayers(\n policy: SandboxPolicy,\n options: WritableRootOptions = {},\n): readonly GrantLayer[] {\n const proposed: GrantLayer[] = []\n if (policy.mode === 'workspace-write') {\n for (const root of [policy.workspaceRoot, ...(options.tempRoots ?? [])]) {\n proposed.push({ path: normalizePath(root), access: 'write', origin: 'mode' })\n }\n }\n for (const denied of options.deniedPaths ?? []) {\n proposed.push({ path: normalizePath(denied), access: 'deny', origin: 'protected' })\n }\n if (options.protectSubpaths !== false) {\n for (const layer of proposed.filter(candidate => candidate.access === 'write')) {\n for (const name of PROTECTED_SUBPATHS) {\n proposed.push({ path: joinPath(layer.path, name), access: 'read', origin: 'protected' })\n }\n }\n }\n for (const entry of orderEntries(policy.entries ?? [])) {\n proposed.push({ path: entry.path, access: entry.access, origin: 'entry' })\n }\n const baseline = policy.baseline ?? BASELINE_ACCESS\n let kept = collapseLayers(proposed, baseline)\n\n // A request is an intersection, not another last-wins grant list. Recompute\n // every boundary named by either side so a broad request restriction also\n // survives narrower standing grants nested beneath it.\n const restrictions = orderEntries(policy.restrictions ?? [])\n if (restrictions.length > 0) {\n const boundaries = orderEntries([\n ...kept.map(layer => ({ path: layer.path, access: layer.access })),\n ...restrictions,\n ]).map(entry => entry.path)\n const distinctBoundaries = [...new Set(boundaries)]\n const intersected: GrantLayer[] = []\n for (const path of distinctBoundaries) {\n const access = narrower(\n accessInLayers(path, kept, baseline),\n accessFor(path, restrictions, 'write'),\n )\n if (accessInLayers(path, intersected, baseline) !== access) {\n intersected.push(Object.freeze({ path, access, origin: 'restriction' }))\n }\n }\n kept = intersected\n }\n\n kept = collapseLayers([\n ...kept,\n ...orderEntries(policy.approvedEntries ?? []).map(entry => ({ ...entry, origin: 'approval' as const })),\n ], baseline)\n return Object.freeze(kept)\n}\n\n/**\n * The access in force at one path, given layers already applied in order.\n * The last layer whose subtree contains the path wins, which is what makes a\n * narrower grant reopen a denied parent.\n */\nexport function accessInLayers(\n target: string,\n layers: readonly GrantLayer[],\n baseline: FileSystemAccess = BASELINE_ACCESS,\n): FileSystemAccess {\n let effective = baseline\n for (const layer of layers) {\n if (containsPath(layer.path, target)) effective = layer.access\n }\n return effective\n}\n\n/** The write grants and re-denials one policy resolves to. */\nexport interface WritableRootSet {\n /** Subtrees that end up writable, broadest first. */\n readonly roots: readonly string[]\n /** Subtrees inside those roots that are not writable, in application order. */\n readonly denied: readonly string[]\n}\n\n/**\n * The writable subtrees and their re-denials, flattened from {@link grantLayers}\n * for callers that only need the two lists. Order is preserved; a narrower grant\n * beneath a denial appears in `roots` after the denial it reopens.\n */\nexport function writableRoots(\n policy: SandboxPolicy,\n options: WritableRootOptions = {},\n): WritableRootSet {\n const layers = grantLayers(policy, options)\n return Object.freeze({\n roots: Object.freeze(layers.filter(layer => layer.access === 'write').map(layer => layer.path)),\n denied: Object.freeze(layers.filter(layer => layer.access !== 'write').map(layer => layer.path)),\n })\n}\n\n/** Subtrees whose contents must not be readable, for backends that can mask. */\nexport function unreadablePaths(\n policy: SandboxPolicy,\n options: WritableRootOptions = {},\n): readonly string[] {\n return Object.freeze(\n grantLayers(policy, options).filter(layer => layer.access === 'deny').map(layer => layer.path),\n )\n}\n\n/** Re-export so a backend can name the entry shape without a second import. */\nexport type { FileSystemAccess, FileSystemEntry }\n","/**\n * The in-process path fence.\n *\n * A process sandbox only governs what a *child process* does. Tools that read,\n * write, or edit files inside the agent host bypass it entirely, so those calls\n * are fenced here against the same {@link grantLayers} the kernel profiles are\n * built from. The logic is pure; the filesystem facts it needs arrive through an\n * injected {@link PathResolver}.\n */\n\nimport { SandboxDeniedError } from './errors.ts'\nimport { ancestorPaths, isAbsolutePath, normalizePath, parentPath } from './path.ts'\nimport type { SandboxPolicy } from './policy.ts'\nimport type { FsFence, PathResolver } from './provider.ts'\nimport type { GrantLayer, WritableRootOptions } from './roots.ts'\nimport { accessInLayers, BASELINE_ACCESS, grantLayers } from './roots.ts'\n\n/**\n * Build the fence for one policy.\n * @param policy - the same policy the process backends receive.\n * @param resolver - filesystem facts used to defeat symlinked paths.\n * @param options - platform temp roots and protected-subpath behaviour.\n */\nexport function createFsFence(\n policy: SandboxPolicy,\n resolver: PathResolver,\n options: WritableRootOptions = {},\n): FsFence {\n const layers = grantLayers(policy, options)\n\n /**\n * Layer paths are canonicalized too, not just the target.\n *\n * A workspace root is routinely reached through a symlink — `/tmp` IS\n * `/private/tmp` on macOS, and `/home` is often a link. Canonicalizing only\n * the target would then compare a resolved path against an unresolved layer\n * and refuse writes inside the very workspace that was granted. Resolved once\n * and reused, because a fence is built per call.\n */\n let canonical: Promise<readonly GrantLayer[]> | undefined\n function layersOnce(): Promise<readonly GrantLayer[]> {\n canonical ??= Promise.all(layers.map(async layer => Object.freeze({\n ...layer, path: await canonicalize(layer.path, resolver),\n })))\n return canonical\n }\n\n async function permits(path: string, want: 'write' | 'read'): Promise<boolean> {\n const [target, resolved] = await Promise.all([canonicalize(path, resolver), layersOnce()])\n const access = accessInLayers(target, resolved, policy.baseline ?? BASELINE_ACCESS)\n if (want === 'read') return access !== 'deny'\n if (access !== 'write') return false\n return options.allowAliasedWrites === true || !(await aliased(target))\n }\n\n /**\n * Whether the target is reachable under a name this policy never saw.\n *\n * A hard link gives one inode two names. Judging the name inside the\n * workspace says nothing about the other one, which may sit anywhere,\n * so a write through the inside name escapes a boundary made of paths. The\n * count is the only signal available without walking the whole filesystem;\n * when the host cannot report it, the check does not fire.\n */\n async function aliased(target: string): Promise<boolean> {\n if (resolver.hardLinkCount === undefined) return false\n try { return (await resolver.hardLinkCount(target)) > 1 }\n catch { return false }\n }\n\n return Object.freeze({\n writableRoots: Object.freeze(layers.filter(layer => layer.access === 'write').map(layer => layer.path)),\n isWritable: (path: string) => permits(path, 'write'),\n isReadable: (path: string) => permits(path, 'read'),\n async assertWritable(path: string): Promise<void> {\n if (await permits(path, 'write')) return\n const writable = layers.filter(layer => layer.access === 'write').map(layer => layer.path)\n throw new SandboxDeniedError(normalizePath(path), policy.mode, writable)\n },\n isAliased: (path: string) => canonicalize(path, resolver).then(aliased),\n })\n}\n\n/** Bound on link chasing, so a self-referential link cannot loop forever. */\nconst SYMLINK_DEPTH_LIMIT = 32\n\n/**\n * Resolve a path through its deepest existing ancestor.\n *\n * A path is checked before it exists (a file about to be created) and may pass\n * through a symlink that points outside the workspace. Canonicalizing the\n * deepest ancestor that does exist and re-appending the missing tail closes\n * both cases: a symlinked parent resolves to its real location, and a target\n * that does not exist yet is still judged where it would actually be created.\n */\nasync function canonicalize(\n path: string,\n resolver: PathResolver,\n depth = 0,\n): Promise<string> {\n const normalized = normalizePath(path)\n if (depth >= SYMLINK_DEPTH_LIMIT) return normalized\n const chain = ancestorPaths(normalized)\n for (let index = chain.length - 1; index >= 0; index--) {\n const candidate = chain[index]\n if (candidate === undefined) continue\n if (!(await resolver.exists(candidate))) continue\n\n // A link whose target does not exist yet is still a link, and `realpath`\n // cannot resolve it. Reading it directly is what keeps a dangling symlink\n // from being judged by its own name — which sits inside the workspace,\n // while what it names does not.\n const link = await resolver.readLink?.(candidate)\n if (link !== undefined) {\n const parent = parentPath(candidate) ?? candidate\n const target = isAbsolutePath(link) ? link : `${parent}/${link}`\n const tail = normalized.slice(candidate.length).replace(/^[\\\\/]+/, '')\n const resolved = await canonicalize(target, resolver, depth + 1)\n return tail === '' ? resolved : normalizePath(`${resolved}/${tail}`)\n }\n\n const real = await resolver.realpath(candidate)\n const tail = normalized.slice(candidate.length).replace(/^[\\\\/]+/, '')\n return tail === '' ? normalizePath(real) : normalizePath(`${real}/${tail}`)\n }\n return normalized\n}\n","/** File-effect vocabulary shared by every sandbox backend and consumer. */\n\n/**\n * File-effect policy for confined processes. `read-only` permits only the sinks\n * a shell requires (`/dev/null` and its platform equivalent); `workspace-write`\n * additionally permits the workspace root and a backend-defined temp area;\n * `danger-full-access` bypasses confinement entirely.\n *\n * Network reachability and process visibility are deliberately outside this\n * vocabulary — they are governed by their own seam, not by a file-effect mode.\n */\nexport type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'\n\n/** A confining mode — the only modes a provider can be asked to enforce. */\nexport type ConfinedSandboxMode = Exclude<SandboxMode, 'danger-full-access'>\n\n/**\n * Enforcement completeness for this host, reported as a fact rather than\n * promised. `partial` means the selected backend cannot govern every file\n * effect the mode promises; `fence-only` means no process confinement exists on\n * this platform and just the in-process path fence applies, so a consumer that\n * needs a kernel boundary must reject it rather than treat it as enforcement.\n */\nexport type SandboxEnforcement = 'full' | 'partial' | 'fence-only'\n\n/** Every mode, in widening order of authority. */\nexport const SANDBOX_MODES: readonly SandboxMode[] = Object.freeze([\n 'read-only', 'workspace-write', 'danger-full-access',\n])\n\n/** Whether an arbitrary value is one of the known modes. */\nexport function isSandboxMode(value: unknown): value is SandboxMode {\n return typeof value === 'string' && (SANDBOX_MODES as readonly string[]).includes(value)\n}\n\n/** Whether a mode still asks a provider to confine the execution. */\nexport function isConfinedMode(mode: SandboxMode): mode is ConfinedSandboxMode {\n return mode !== 'danger-full-access'\n}\n\n/** Rank used when a narrower mode must not be widened by a weaker source. */\nexport function modeAuthority(mode: SandboxMode): number {\n return SANDBOX_MODES.indexOf(mode)\n}\n","/**\n * Network reachability — a seam of its own, deliberately not a file-effect mode.\n *\n * `SandboxMode` governs file effects and says so. Folding network reachability\n * into it would make the mode claim something it does not decide, and a mode\n * that lies about its scope is worse than one with a narrow one. So network is\n * a second, independent axis carried on the same policy: a call can be\n * `read-only` on the filesystem and still reach the internet, or writable in\n * its workspace and reach nothing.\n *\n * The distinction matters because the two are enforced by different mechanisms\n * — mount bindings versus a network namespace — and a host can provide one\n * without the other.\n */\n\nimport { SandboxPolicyError } from './errors.ts'\n\n/**\n * What a confined execution may reach.\n *\n * `loopback` is not a weaker `deny`: it is what a command needs when the\n * deployment runs a proxy or a language server it is meant to talk to, and\n * nothing else. On a host whose only mechanism is a network namespace the two\n * enforce identically, which the enforcement report says rather than hides.\n */\nexport type NetworkMode = 'deny' | 'loopback' | 'allow-all'\n\n/** Every network mode, in widening order of reach. */\nexport const NETWORK_MODES: readonly NetworkMode[] = Object.freeze(['deny', 'loopback', 'allow-all'])\n\n/** How completely a backend enforces a network mode. */\nexport type NetworkEnforcement = 'full' | 'loopback-only' | 'none'\n\n/** Whether an arbitrary value is one of the known network modes. */\nexport function isNetworkMode(value: unknown): value is NetworkMode {\n return typeof value === 'string' && (NETWORK_MODES as readonly string[]).includes(value)\n}\n\n/** Rank used so an untrusted request can narrow reach but never widen it. */\nexport function networkAuthority(mode: NetworkMode): number {\n return NETWORK_MODES.indexOf(mode)\n}\n\n/**\n * Narrow a network mode toward a stricter one, refusing to widen.\n * @param ceiling - the reach already permitted.\n * @param requested - the reach being asked for.\n * @returns the stricter of the two.\n */\nexport function narrowNetwork(ceiling: NetworkMode, requested: NetworkMode | undefined): NetworkMode {\n if (requested === undefined) return ceiling\n if (!isNetworkMode(requested)) {\n throw new SandboxPolicyError(`Unknown network mode '${String(requested)}'`)\n }\n return networkAuthority(requested) < networkAuthority(ceiling) ? requested : ceiling\n}\n","/**\n * Outcome classification.\n *\n * Two failures look alike in a shell but mean opposite things. A *denial* means\n * confinement worked and blocked the command. A *runner failure* means the\n * sandbox itself refused or crashed and the command never ran at all. Reporting\n * the second as the first sends a model off rewriting correct code, so runner\n * failure is always checked first and never inferred from an exit code alone.\n */\n\n/** Evidence that a runner failed before it executed the wrapped command. */\nexport interface RunnerFailureRule {\n /** Nonzero exits this rule may match; omitted permits any nonzero exit. */\n readonly allowedExitCodes?: readonly number[]\n /** Case-insensitive substrings identifying a fatal runner diagnostic. */\n readonly fatalSignatures: readonly string[]\n /** Benign lines removed by exact, case-insensitive full-line equality first. */\n readonly informationalLines?: readonly string[]\n /**\n * Substrings that disqualify a line from proving runner failure, applied\n * before {@link fatalSignatures}. A runner reports the child's failed `exec`\n * under its own name and often its own exit code, so the prefix that\n * identifies its diagnostics also matches an ordinary missing program; this\n * is how that one case is carved back out.\n */\n readonly excludedSignatures?: readonly string[]\n}\n\n/** What a finished confined command turned out to be. */\nexport type SandboxOutcomeKind = 'success' | 'runner-failure' | 'denied' | 'command-failure'\n\n/** The observable result of spawning a confined argv. */\nexport interface CommandOutcome {\n /** Process exit code; a signalled process reports its conventional code. */\n readonly exitCode: number\n /** Stderr text as produced, never rewritten by classification. */\n readonly stderr: string\n /** Terminating signal name, when the host reports one. */\n readonly signal?: string | null\n /**\n * Whether the runner reported, on a channel of its own, that it executed the\n * command. Stderr cannot answer this: the runner and the command share that\n * stream, so a command can print the runner's fatal signature and exit with\n * the runner's code, and claim it never ran. When this is `true` no\n * runner-failure rule applies, because the claim is already contradicted by\n * evidence the command could not write.\n */\n readonly childStarted?: boolean\n}\n\n/** Evidence a consumer needs to classify one confined command's outcome. */\nexport interface SandboxClassificationInput {\n /** Denial dialect of the backend that actually wrapped this command. */\n readonly denialSignatures: readonly string[]\n /** Structured runner-failure evidence for that same backend. */\n readonly runnerFailureRules: readonly RunnerFailureRule[]\n}\n\n/** A classified outcome plus the stderr line that proved it. */\nexport interface SandboxClassification {\n readonly kind: SandboxOutcomeKind\n /** The stderr line that matched, kept verbatim for the caller to surface. */\n readonly evidence?: string\n}\n\n/**\n * Exit codes that are ordinary shell failures and never sandbox evidence:\n * 2 misuse of a builtin, 126 not executable, 127 command not found.\n */\nconst SHELL_FAILURE_EXIT_CODES: readonly number[] = Object.freeze([2, 126, 127])\n\n/** POSIX `SIGSYS`, raised when a seccomp filter kills the process. */\nconst SIGSYS_EXIT_CODE = 128 + 31\n\n/**\n * Classify one confined command's outcome.\n *\n * Runner failure is tested first, then a seccomp kill (deterministic, no text\n * matching needed), then the backend's own denial dialect. A cross-backend\n * union of denial strings is deliberately not used: it would claim denials a\n * given backend never produces.\n */\nexport function classifyOutcome(\n outcome: CommandOutcome,\n input: SandboxClassificationInput,\n): SandboxClassification {\n if (outcome.exitCode === 0) return Object.freeze({ kind: 'success' })\n\n const lines = outcome.stderr.split(/\\r?\\n/)\n if (outcome.childStarted !== true) {\n for (const rule of input.runnerFailureRules) {\n const evidence = matchRunnerFailure(outcome.exitCode, lines, rule)\n if (evidence !== undefined) return Object.freeze({ kind: 'runner-failure', evidence })\n }\n }\n\n if (outcome.signal === 'SIGSYS' || outcome.exitCode === SIGSYS_EXIT_CODE) {\n return Object.freeze({ kind: 'denied', evidence: 'process killed by SIGSYS (seccomp)' })\n }\n\n if (SHELL_FAILURE_EXIT_CODES.includes(outcome.exitCode)) {\n return Object.freeze({ kind: 'command-failure' })\n }\n\n const denial = matchSignature(lines, input.denialSignatures)\n return denial === undefined\n ? Object.freeze({ kind: 'command-failure' })\n : Object.freeze({ kind: 'denied', evidence: denial })\n}\n\n/** Apply one runner-failure rule to a finished process. */\nfunction matchRunnerFailure(\n exitCode: number,\n lines: readonly string[],\n rule: RunnerFailureRule,\n): string | undefined {\n if (rule.allowedExitCodes !== undefined && !rule.allowedExitCodes.includes(exitCode)) return undefined\n const informational = new Set((rule.informationalLines ?? []).map(line => line.trim().toLowerCase()))\n const excluded = (rule.excludedSignatures ?? []).map(signature => signature.toLowerCase())\n const remaining = lines.filter((line) => {\n const normalized = line.trim().toLowerCase()\n if (informational.has(normalized)) return false\n return !excluded.some(signature => signature !== '' && normalized.includes(signature))\n })\n return matchSignature(remaining, rule.fatalSignatures)\n}\n\n/** The first line containing any signature, matched case-insensitively. */\nfunction matchSignature(lines: readonly string[], signatures: readonly string[]): string | undefined {\n if (signatures.length === 0) return undefined\n for (const line of lines) {\n const haystack = line.toLowerCase()\n if (signatures.some(signature => signature !== '' && haystack.includes(signature.toLowerCase()))) return line\n }\n return undefined\n}\n\n/**\n * Append a short, factual note to stderr explaining a sandbox outcome, so a\n * reader never has to infer confinement from a bare error string.\n */\nexport function annotateStderr(\n stderr: string,\n classification: SandboxClassification,\n mode: string,\n): string {\n if (classification.kind === 'success' || classification.kind === 'command-failure') return stderr\n const note = classification.kind === 'runner-failure'\n ? `[sandbox] The sandbox runner failed before the command ran; the command did not execute. ${classification.evidence ?? ''}`.trim()\n : `[sandbox] Blocked by sandbox mode '${mode}'. ${classification.evidence ?? ''}`.trim()\n return stderr.endsWith('\\n') || stderr === '' ? `${stderr}${note}\\n` : `${stderr}\\n${note}\\n`\n}\n","/**\n * Per-call policy resolution.\n *\n * The policy is carried per capability call rather than fixed on the provider:\n * two consumers may confine under different boundaries at the same instant, and\n * an approved escalation is a new call with a wider policy — never a mutation\n * of shared provider state.\n */\n\nimport type { SandboxApproval } from './approval.ts'\nimport { requireSandboxApproval } from './approval.ts'\nimport type { FileSystemAccess, FileSystemEntry } from './entries.ts'\nimport { orderEntries } from './entries.ts'\nimport { SandboxPolicyError } from './errors.ts'\nimport type { ConfinedSandboxMode, SandboxMode } from './mode.ts'\nimport type { NetworkMode } from './network.ts'\nimport { narrowNetwork } from './network.ts'\nimport { isConfinedMode, isSandboxMode, modeAuthority } from './mode.ts'\nimport { isAbsolutePath, normalizePath } from './path.ts'\n\n/** The complete file-effect policy resolved for one capability call. */\nexport interface SandboxExecutionPolicy {\n /** The file-effect mode this execution runs under. */\n readonly mode: SandboxMode\n /** Absolute root directory `workspace-write` may write under. */\n readonly workspaceRoot: string\n /** Nested carve-outs layered over the mode's base grant. */\n readonly entries?: readonly FileSystemEntry[]\n /** Untrusted per-call carve-outs, kept separate so they can only intersect. */\n readonly restrictions?: readonly FileSystemEntry[]\n /** Carve-outs carried by a minted approval and therefore allowed to widen. */\n readonly approvedEntries?: readonly FileSystemEntry[]\n /**\n * The access in force where no layer applies.\n *\n * `read` — the default — means the host is readable and a policy closes\n * paths one at a time, which is a deny-list: it protects what someone\n * remembered to name. `deny` inverts that into an allow-list, where nothing\n * is readable until an entry says so, and a path nobody thought about is\n * closed rather than open.\n */\n readonly baseline?: FileSystemAccess\n /**\n * What this execution may reach over the network. Independent of\n * {@link SandboxExecutionPolicy.mode}, which governs file effects only.\n */\n readonly network?: NetworkMode\n /** Opaque calling-session identity; backends key per-session state off it. */\n readonly sessionId?: string\n}\n\n/**\n * A policy narrowed to a confining mode — the only shape a provider accepts.\n * Resolution happens at the consumer boundary; the provider treats what it\n * receives as fully specified and never re-defaults anything.\n */\nexport interface SandboxPolicy extends SandboxExecutionPolicy {\n readonly mode: ConfinedSandboxMode\n}\n\n/**\n * Inputs that select the policy for one capability call.\n *\n * `mode` and `entries` are the UNTRUSTED half: they arrive from whatever asked\n * for the execution, which in an agent is a model-authored tool payload. They\n * may only narrow. Widening lives behind {@link approval}, which a tool payload\n * cannot contain because it is a capability rather than data.\n */\nexport interface SandboxPolicyRequest {\n /**\n * The mode the caller asks for. Honoured only when it is at least as strict\n * as the session's own mode — a request can tighten its own execution, never\n * loosen it.\n */\n readonly mode?: SandboxMode\n /** The calling session's mode, as last logged for that session. */\n readonly sessionMode?: SandboxMode\n /** The calling session's immutable cwd; becomes the workspace boundary. */\n readonly cwd?: string\n /**\n * Carve-outs the caller asks for. Restrictions only: a `write` entry here is\n * an escalation attempt and is refused, because granting write to `.git` or\n * `~/.ssh` defeats the boundary just as completely as raising the mode.\n */\n readonly entries?: readonly FileSystemEntry[]\n /** The network reach the caller asks for; honoured only when it narrows. */\n readonly network?: NetworkMode\n /** An approval minted by `approveSandboxEscalation`; the only way to widen. */\n readonly approval?: SandboxApproval\n /** Opaque calling-session identity. */\n readonly sessionId?: string\n}\n\n/** Deployment-level fallbacks applied when a request omits them. */\nexport interface SandboxPolicyDefaults {\n /** Mode for calls that carry neither an override nor a session mode. */\n readonly mode: SandboxMode\n /** Workspace root for agentless calls and sessions without a cwd. */\n readonly workspaceRoot: string\n /** Carve-outs that always apply, before request-supplied ones. */\n readonly entries?: readonly FileSystemEntry[]\n /** Access where no layer applies; see {@link SandboxExecutionPolicy.baseline}. */\n readonly baseline?: FileSystemAccess\n /**\n * Network reach for calls that do not narrow it. Defaults to `allow-all`,\n * which is what this package did before the seam existed; a deployment\n * running anything untrusted should set `deny` and widen per call.\n */\n readonly network?: NetworkMode\n}\n\n/**\n * Resolve the complete policy for one capability call.\n *\n * Authority only ever decreases across untrusted inputs: the deployment default\n * and the session's mode set a ceiling, a request may narrow beneath it, and a\n * minted approval is the single path that raises it. A session cwd is its\n * `workspace-write` boundary; the configured root is the fallback for agentless\n * calls and sessions without a cwd.\n * @param request - the calling session's untrusted ask, plus any approval.\n * @param defaults - deployment mode, workspace root, and standing carve-outs.\n * @throws SandboxPolicyError when a request tries to widen without an approval.\n */\nexport function resolveSandboxPolicy(\n request: SandboxPolicyRequest,\n defaults: SandboxPolicyDefaults,\n): SandboxExecutionPolicy {\n const approval = request.approval === undefined ? undefined : requireSandboxApproval(request.approval)\n\n // The ceiling is what the deployment and the session already allow. A request\n // is clamped to it; only an approval may raise it.\n const ceiling = request.sessionMode ?? defaults.mode\n if (!isSandboxMode(ceiling)) throw new SandboxPolicyError(`Unknown sandbox mode '${String(ceiling)}'`)\n const requested = request.mode\n if (requested !== undefined && !isSandboxMode(requested)) {\n throw new SandboxPolicyError(`Unknown sandbox mode '${String(requested)}'`)\n }\n const narrowed = requested !== undefined && modeAuthority(requested) < modeAuthority(ceiling)\n ? requested\n : ceiling\n const mode = approval?.mode ?? narrowed\n\n const workspaceRoot = normalizePath(request.cwd ?? defaults.workspaceRoot)\n if (!isAbsolutePath(workspaceRoot)) {\n throw new SandboxPolicyError(`Sandbox workspace root must be absolute, received '${workspaceRoot}'`)\n }\n\n for (const entry of request.entries ?? []) {\n if (entry.access === 'write') {\n throw new SandboxPolicyError(\n `A requested entry may not grant write access to '${entry.path}'; `\n + 'widening a policy requires an approval minted by approveSandboxEscalation()',\n )\n }\n }\n\n // Preserve the authority boundary instead of flattening all three sources\n // into one last-entry-wins list. grantLayers() intersects restrictions with\n // the standing policy, then applies only minted approvals as widenings.\n const entries = orderEntries(defaults.entries ?? [])\n const restrictions = orderEntries(request.entries ?? [])\n const approvedEntries = orderEntries(approval?.entries ?? [])\n // Reach narrows the same way authority does, and widens only with approval.\n const network = approval?.network\n ?? narrowNetwork(defaults.network ?? 'allow-all', request.network)\n\n return Object.freeze({\n mode, workspaceRoot, network,\n ...(defaults.baseline === undefined ? {} : { baseline: defaults.baseline }),\n ...(entries.length === 0 ? {} : { entries }),\n ...(restrictions.length === 0 ? {} : { restrictions }),\n ...(approvedEntries.length === 0 ? {} : { approvedEntries }),\n ...(request.sessionId === undefined ? {} : { sessionId: request.sessionId }),\n })\n}\n\n/**\n * Narrow a resolved policy to the confining shape a provider accepts.\n * @returns the confining policy, or `undefined` under `danger-full-access`,\n * whose consumer spawns its original argv and never calls the provider.\n */\nexport function confiningPolicy(policy: SandboxExecutionPolicy): SandboxPolicy | undefined {\n return isConfinedMode(policy.mode) ? (policy as SandboxPolicy) : undefined\n}\n\n/**\n * Narrow a policy toward a stricter mode without widening it. Used where a\n * consumer may tighten a caller's policy but must never loosen it.\n */\nexport function narrowPolicy(policy: SandboxExecutionPolicy, mode: SandboxMode): SandboxExecutionPolicy {\n return modeAuthority(mode) < modeAuthority(policy.mode) ? Object.freeze({ ...policy, mode }) : policy\n}\n","/**\n * Resource limits — a third axis, and the one this package enforces weakest.\n *\n * A filesystem boundary can be completely correct while the host falls over: a\n * fork storm, a growing allocation, or a loop that never ends costs nothing in\n * file effects. Measured against the current backends, 150 processes, 2 GB of\n * memory and 20 000 files met no resistance at all.\n *\n * What a host can actually promise differs so much that the promise has to be\n * reported rather than assumed. A real quota needs cgroup v2 or a Job Object;\n * without one, limits can still be observed and acted on, which stops a runaway\n * but does not prevent the spike that precedes it.\n */\n\n/** What a confined execution may consume. Every field is optional. */\nexport interface ResourceLimits {\n /** Wall-clock time before the execution is ended. */\n readonly wallClockMs?: number\n /** Resident memory across the whole process tree. */\n readonly memoryBytes?: number\n /** Processes in the tree, including the sandbox process itself. */\n readonly processes?: number\n /** Accumulated CPU time across the tree. */\n readonly cpuMs?: number\n}\n\n/**\n * How a host enforces those limits.\n *\n * `quota` means the kernel refuses to exceed them. `monitor` means they are\n * sampled and the execution is ended once exceeded — which bounds a runaway but\n * not the spike between two samples. `none` means nothing is watching.\n */\nexport type ResourceEnforcement = 'quota' | 'monitor' | 'none'\n\n/** Why a supervised execution was ended. */\nexport type ResourceBreach = 'wall-clock' | 'memory' | 'processes' | 'cpu'\n\n/** What a supervised execution actually consumed. */\nexport interface ResourceUsage {\n /** Highest resident memory observed across the tree. */\n readonly peakMemoryBytes: number\n /** Highest process count observed in the tree. */\n readonly peakProcesses: number\n /** Highest accumulated CPU time observed. */\n readonly cpuMs: number\n /** Wall-clock time the execution ran for. */\n readonly wallClockMs: number\n}\n\n/** Whether any limit is set at all, so a caller can skip supervision entirely. */\nexport function hasResourceLimits(limits: ResourceLimits): boolean {\n return limits.wallClockMs !== undefined || limits.memoryBytes !== undefined\n || limits.processes !== undefined || limits.cpuMs !== undefined\n}\n\n/**\n * The first limit the usage exceeds, or `undefined` while it is within them.\n * Checked in the order a runaway usually announces itself.\n */\nexport function breachedLimit(\n usage: ResourceUsage,\n limits: ResourceLimits,\n): ResourceBreach | undefined {\n if (limits.wallClockMs !== undefined && usage.wallClockMs > limits.wallClockMs) return 'wall-clock'\n if (limits.processes !== undefined && usage.peakProcesses > limits.processes) return 'processes'\n if (limits.memoryBytes !== undefined && usage.peakMemoryBytes > limits.memoryBytes) return 'memory'\n if (limits.cpuMs !== undefined && usage.cpuMs > limits.cpuMs) return 'cpu'\n return undefined\n}\n","/** Normalized sandbox violations, shaped for audit and telemetry. */\n\nimport type { SandboxClassification } from './classify.ts'\n\n/** The enforcement layer that observed a violation. */\nexport type SandboxViolationBackend = 'bwrap' | 'seatbelt' | 'windows-acl' | 'fence' | 'custom'\n\n/** Why a file effect was refused, normalized across backend dialects. */\nexport type SandboxViolationReason =\n | 'operation-not-permitted'\n | 'permission-denied'\n | 'read-only-filesystem'\n | 'policy-denied'\n | 'runner-failure'\n\n/** One observed violation, retaining the evidence that identified it. */\nexport interface SandboxViolation {\n readonly backend: SandboxViolationBackend\n readonly reason: SandboxViolationReason\n readonly mode: string\n /** The path involved, when the backend named one. */\n readonly path?: string\n /** Bounded evidence excerpt; never the whole output. */\n readonly snippet: string\n}\n\n/** Upper bound on retained evidence, so a violation never carries a log dump. */\nconst SNIPPET_MAX_LENGTH = 512\n\nconst REASON_SIGNATURES: readonly (readonly [SandboxViolationReason, string])[] = Object.freeze([\n ['operation-not-permitted', 'operation not permitted'],\n ['permission-denied', 'permission denied'],\n ['read-only-filesystem', 'read-only file system'],\n ['read-only-filesystem', 'erofs'],\n ['policy-denied', 'seccomp'],\n ['policy-denied', 'landlock'],\n])\n\n/**\n * Build a violation record from a classified outcome.\n * @returns the record, or `undefined` when the outcome was not a violation.\n */\nexport function sandboxViolation(\n classification: SandboxClassification,\n backend: SandboxViolationBackend,\n mode: string,\n): SandboxViolation | undefined {\n if (classification.kind !== 'denied' && classification.kind !== 'runner-failure') return undefined\n const evidence = classification.evidence ?? ''\n const reason: SandboxViolationReason = classification.kind === 'runner-failure'\n ? 'runner-failure'\n : reasonFor(evidence)\n const path = pathIn(evidence)\n return Object.freeze({\n backend, reason, mode,\n ...(path === undefined ? {} : { path }),\n snippet: evidence.slice(0, SNIPPET_MAX_LENGTH),\n })\n}\n\n/** Map one evidence line to a normalized reason. */\nfunction reasonFor(evidence: string): SandboxViolationReason {\n const haystack = evidence.toLowerCase()\n for (const [reason, signature] of REASON_SIGNATURES) {\n if (haystack.includes(signature)) return reason\n }\n return 'policy-denied'\n}\n\n/** Extract the first absolute path a denial message mentions, if any. */\nfunction pathIn(evidence: string): string | undefined {\n const match = /(\\/[^\\s:'\"]+|[A-Za-z]:[\\\\/][^\\s:'\"]*)/.exec(evidence)\n return match?.[0]\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@alvin0/ai-agent-sdk-sandbox",
3
+ "author": {
4
+ "name": "alvin0 - chaulamdinhai",
5
+ "email": "chaulamdinhai@gmail.com"
6
+ },
7
+ "version": "0.1.3",
8
+ "description": "Universal sandbox contract, policy algebra, and outcome classification for ai-agent-sdk",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/alvin0/ai-agent-sdk.git",
13
+ "directory": "packages/sandbox"
14
+ },
15
+ "homepage": "https://github.com/alvin0/ai-agent-sdk/tree/main/packages/sandbox#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/alvin0/ai-agent-sdk/issues"
18
+ },
19
+ "type": "module",
20
+ "sideEffects": false,
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "devDependencies": {
41
+ "@arethetypeswrong/cli": "0.18.5",
42
+ "publint": "0.3.24",
43
+ "tsdown": "0.22.14",
44
+ "typescript": "7.0.2",
45
+ "vitest": "4.1.11"
46
+ },
47
+ "engines": {
48
+ "node": ">=22.12"
49
+ },
50
+ "aiAgentSdk": {
51
+ "runtime": "universal",
52
+ "coreApi": 1,
53
+ "roles": [
54
+ "sandbox-contract"
55
+ ]
56
+ },
57
+ "scripts": {
58
+ "build": "tsdown",
59
+ "clean": "node -e \"for(const p of ['dist','artifacts'])require('node:fs').rmSync(p,{recursive:true,force:true})\"",
60
+ "typecheck": "tsc --noEmit",
61
+ "test": "vitest run --config vitest.config.ts",
62
+ "pack": "pnpm pack --pack-destination artifacts",
63
+ "test:pack": "node ../../scripts/test-packed-sandbox.mts sandbox",
64
+ "check:publint": "publint",
65
+ "check:types": "attw --profile esm-only --pack ."
66
+ }
67
+ }