Skip to content

Commit 20bbd45

Browse files
oratisclaude
andauthored
feat(core): harden Linux bwrap sandbox (--new-session, --die-with-parent) (#110)
The bwrap profile was missing two standard hardening flags: - --new-session: fresh session so the sandboxed process can't TIOCSTI-inject keystrokes into the controlling terminal (a known sandbox escape). - --die-with-parent: kill the sandbox when the agent exits (no orphans). Also corrected the stale "skeleton" header — the Linux path is complete except the selective-domain net allowlist (needs a slirp4netns helper; deny-all and full-net modes work). +1 test. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 61cd820 commit 20bbd45

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

packages/core/src/sandbox/profile.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ describe('buildLinuxBwrapArgs', () => {
100100
expect(args).toContain('--unshare-uts');
101101
});
102102

103+
it('adds hardening flags (--new-session blocks TIOCSTI, --die-with-parent)', () => {
104+
const args = buildLinuxBwrapArgs({ enabled: true }, '/x');
105+
expect(args).toContain('--new-session');
106+
expect(args).toContain('--die-with-parent');
107+
});
108+
103109
it('unshares net when allowedDomains is empty array', () => {
104110
const args = buildLinuxBwrapArgs({ enabled: true, network: { allowedDomains: [] } }, '/x');
105111
expect(args).toContain('--unshare-net');

packages/core/src/sandbox/profile.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
// sandbox specifications.
33
// Spec: docs/DEVELOPMENT_PLAN.md §3.9a + docs/design/sandbox-plan-worktree.md
44
//
5-
// M3.5: macOS sandbox-exec SBPL profile generation. Linux bwrap arg generation
6-
// is partial (skeleton). Windows: disabled per §0.2.
5+
// M3.5: macOS sandbox-exec SBPL profile generation + Linux bwrap arg generation
6+
// (ro system mounts, rw cwd, read/write allowlists, net unshare, pid/ipc/uts
7+
// unshare, --new-session + --die-with-parent hardening). The one remaining gap
8+
// is the selective-domain net allowlist, which needs a slirp4netns helper to
9+
// bridge UDP into the netns (deny-all-net and full-net modes both work today).
10+
// Windows: disabled per §0.2.
711

812
import { homedir, platform } from 'node:os';
913
import type { SandboxConfig } from '../config/types.js';
@@ -188,5 +192,12 @@ export function buildLinuxBwrapArgs(
188192
// Default: unshare pid + ipc + uts
189193
args.push('--unshare-pid', '--unshare-ipc', '--unshare-uts');
190194

195+
// Hardening:
196+
// · --new-session: run in a fresh session so the sandboxed process can't use
197+
// the TIOCSTI ioctl to inject keystrokes into the controlling terminal — a
198+
// known sandbox-escape. Safe for non-interactive Bash-tool commands.
199+
// · --die-with-parent: kill the sandbox if the agent dies (no orphans).
200+
args.push('--new-session', '--die-with-parent');
201+
191202
return args;
192203
}

0 commit comments

Comments
 (0)