diff --git a/package-lock.json b/package-lock.json index d016dc6..4b7815f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@anthropic-ai/sandbox-runtime", - "version": "0.0.42", + "version": "0.0.43", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@anthropic-ai/sandbox-runtime", - "version": "0.0.42", + "version": "0.0.43", "license": "Apache-2.0", "dependencies": { "@pondwader/socks5-server": "^1.0.10", diff --git a/package.json b/package.json index 7c3cb7c..25e14bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@anthropic-ai/sandbox-runtime", - "version": "0.0.42", + "version": "0.0.43", "description": "Anthropic Sandbox Runtime (ASRT) - A general-purpose tool for wrapping security boundaries around arbitrary processes", "type": "module", "main": "./dist/index.js", diff --git a/scripts/build-seccomp-binaries.sh b/scripts/build-seccomp-binaries.sh index f81bd8b..64c27e1 100755 --- a/scripts/build-seccomp-binaries.sh +++ b/scripts/build-seccomp-binaries.sh @@ -99,6 +99,11 @@ build_platform() { echo 'Binary size:' ls -lh /output/seccomp-unix-block + echo '' + echo 'Generating BPF filter...' + /output/seccomp-unix-block /output/unix-block.bpf + ls -lh /output/unix-block.bpf + echo '' echo 'Building apply-seccomp (no libseccomp dependency)...' gcc -o /output/apply-seccomp /src/apply-seccomp.c \ @@ -124,23 +129,7 @@ build_platform() { return 1 } - # Verify binary exists - if [ ! -f "$output_dir/seccomp-unix-block" ]; then - echo "✗ Error: Binary not found in $output_dir" - return 1 - fi - - # Generate BPF filter using the seccomp-unix-block binary - echo "Generating BPF filter..." - local bpf_file="$output_dir/unix-block.bpf" - - # Run the generator to create the BPF file - if ! "$output_dir/seccomp-unix-block" "$bpf_file" 2>&1; then - echo "✗ Error: Failed to generate BPF filter" - return 1 - fi - - # Verify BPF file was created + # Verify BPF file was created inside the container if [ ! -f "$bpf_file" ]; then echo "✗ Error: BPF file not created" return 1 @@ -149,7 +138,6 @@ build_platform() { echo "✓ BPF filter generated: $(ls -lh "$bpf_file" | awk '{print $5}')" # Remove the generator binary (we only need the BPF file) - echo "Removing generator binary to save space..." rm -f "$output_dir/seccomp-unix-block" # Verify final state diff --git a/src/sandbox/macos-sandbox-utils.ts b/src/sandbox/macos-sandbox-utils.ts index e82e507..b88df28 100644 --- a/src/sandbox/macos-sandbox-utils.ts +++ b/src/sandbox/macos-sandbox-utils.ts @@ -265,11 +265,8 @@ function generateReadRules( // directory (e.g. /Users, /Users/chris) even if only a subdirectory like // ~/.local is in allowWithinDeny. This only allows metadata reads on // directories — not listing contents (readdir) or reading files. - if ((config.denyOnly).length > 0) { - rules.push( - `(allow file-read-metadata`, - ` (vnode-type DIRECTORY))`, - ) + if (config.denyOnly.length > 0) { + rules.push(`(allow file-read-metadata`, ` (vnode-type DIRECTORY))`) } // Block file movement to prevent bypass via mv/rename @@ -292,17 +289,6 @@ function generateWriteRules( const rules: string[] = [] - // Automatically allow TMPDIR parent on macOS when write restrictions are enabled - const tmpdirParents = getTmpdirParentIfMacOSPattern() - for (const tmpdirParent of tmpdirParents) { - const normalizedPath = normalizePathForSandbox(tmpdirParent) - rules.push( - `(allow file-write*`, - ` (subpath ${escapePath(normalizedPath)})`, - ` (with message "${logTag}"))`, - ) - } - // Generate allow rules for (const pathPattern of config.allowOnly || []) { const normalizedPath = normalizePathForSandbox(pathPattern) @@ -651,31 +637,6 @@ function escapePath(pathStr: string): string { return JSON.stringify(pathStr) } -/** - * Get TMPDIR parent directory if it matches macOS pattern /var/folders/XX/YYY/T/ - * Returns both /var/ and /private/var/ versions since /var is a symlink - */ -function getTmpdirParentIfMacOSPattern(): string[] { - const tmpdir = process.env.TMPDIR - if (!tmpdir) return [] - - const match = tmpdir.match( - /^\/(private\/)?var\/folders\/[^/]{2}\/[^/]+\/T\/?$/, - ) - if (!match) return [] - - const parent = tmpdir.replace(/\/T\/?$/, '') - - // Return both /var/ and /private/var/ versions since /var is a symlink - if (parent.startsWith('/private/var/')) { - return [parent, parent.replace('/private', '')] - } else if (parent.startsWith('/var/')) { - return [parent, '/private' + parent] - } - - return [parent] -} - /** * Wrap command with macOS sandbox */ diff --git a/test/sandbox/symlink-boundary.test.ts b/test/sandbox/symlink-boundary.test.ts index 1029cef..8065993 100644 --- a/test/sandbox/symlink-boundary.test.ts +++ b/test/sandbox/symlink-boundary.test.ts @@ -46,8 +46,8 @@ function cleanupTmpClaude(): void { describe('macOS Seatbelt Symlink Boundary Validation', () => { // Use unique test directories per run - // IMPORTANT: Use /private/tmp (not os.tmpdir() which is /var/folders/...) - // because /var/folders is automatically allowed by the sandbox via TMPDIR parent rule + // Use /private/tmp (not os.tmpdir()) so test paths are outside any + // default-allowed write location const TEST_ID = Date.now() const TEST_BASE_DIR = `/private/tmp/symlink-boundary-test-${TEST_ID}` const WORKSPACE_DIR = join(TEST_BASE_DIR, 'workspace') diff --git a/vendor/seccomp-src/seccomp-unix-block.c b/vendor/seccomp-src/seccomp-unix-block.c index 2d8d0ae..70ecaab 100644 --- a/vendor/seccomp-src/seccomp-unix-block.c +++ b/vendor/seccomp-src/seccomp-unix-block.c @@ -65,8 +65,12 @@ int main(int argc, char *argv[]) { /* Add rule to block socket(AF_UNIX, ...) */ /* socket() syscall signature: int socket(int domain, int type, int protocol) */ /* arg0 = domain (AF_UNIX = 1) */ + /* Use SCMP_CMP_MASKED_EQ with a 32-bit mask: the domain argument is a 32-bit + * int, so the kernel ignores the upper 32 bits of the register. A plain + * SCMP_CMP_EQ would compare all 64 bits and miss calls where the upper bits + * are set. */ rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(socket), 1, - SCMP_A0(SCMP_CMP_EQ, AF_UNIX)); + SCMP_A0(SCMP_CMP_MASKED_EQ, 0xffffffff, AF_UNIX)); if (rc < 0) { fprintf(stderr, "Error: Failed to add seccomp rule: %s\n", strerror(-rc)); seccomp_release(ctx); diff --git a/vendor/seccomp/arm64/unix-block.bpf b/vendor/seccomp/arm64/unix-block.bpf index eb3bfda..98e6e09 100644 Binary files a/vendor/seccomp/arm64/unix-block.bpf and b/vendor/seccomp/arm64/unix-block.bpf differ diff --git a/vendor/seccomp/x64/unix-block.bpf b/vendor/seccomp/x64/unix-block.bpf index 13b38a9..688d4ba 100644 Binary files a/vendor/seccomp/x64/unix-block.bpf and b/vendor/seccomp/x64/unix-block.bpf differ