@sysid/sandbox-runtime-improved 0.0.45-sysid.2 → 0.0.49-sysid.2

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.
Files changed (36) hide show
  1. package/README.md +9 -30
  2. package/dist/sandbox/generate-seccomp-filter.d.ts +0 -51
  3. package/dist/sandbox/generate-seccomp-filter.d.ts.map +1 -1
  4. package/dist/sandbox/generate-seccomp-filter.js +0 -106
  5. package/dist/sandbox/generate-seccomp-filter.js.map +1 -1
  6. package/dist/sandbox/linux-sandbox-utils.d.ts +4 -13
  7. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
  8. package/dist/sandbox/linux-sandbox-utils.js +84 -105
  9. package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
  10. package/dist/sandbox/macos-sandbox-utils.d.ts +1 -1
  11. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
  12. package/dist/sandbox/macos-sandbox-utils.js +41 -20
  13. package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
  14. package/dist/sandbox/sandbox-config.d.ts +16 -12
  15. package/dist/sandbox/sandbox-config.d.ts.map +1 -1
  16. package/dist/sandbox/sandbox-config.js +17 -10
  17. package/dist/sandbox/sandbox-config.js.map +1 -1
  18. package/dist/sandbox/sandbox-manager.d.ts +1 -1
  19. package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
  20. package/dist/sandbox/sandbox-manager.js +6 -9
  21. package/dist/sandbox/sandbox-manager.js.map +1 -1
  22. package/dist/vendor/seccomp/Dockerfile.build +6 -0
  23. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  24. package/dist/vendor/seccomp/build.ts +91 -0
  25. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  26. package/dist/vendor/seccomp-src/apply-seccomp.c +18 -23
  27. package/package.json +3 -8
  28. package/vendor/seccomp/Dockerfile.build +6 -0
  29. package/vendor/seccomp/arm64/apply-seccomp +0 -0
  30. package/vendor/seccomp/build.ts +91 -0
  31. package/vendor/seccomp/x64/apply-seccomp +0 -0
  32. package/vendor/seccomp-src/apply-seccomp.c +18 -23
  33. package/dist/vendor/seccomp/arm64/unix-block.bpf +0 -0
  34. package/dist/vendor/seccomp/x64/unix-block.bpf +0 -0
  35. package/vendor/seccomp/arm64/unix-block.bpf +0 -0
  36. package/vendor/seccomp/x64/unix-block.bpf +0 -0
@@ -1,9 +1,9 @@
1
1
  /*
2
2
  * apply-seccomp.c - Apply seccomp BPF filter in an isolated PID namespace
3
3
  *
4
- * Usage: apply-seccomp <filter.bpf> <command> [args...]
4
+ * Usage: apply-seccomp <command> [args...]
5
5
  *
6
- * This program reads a pre-compiled BPF filter from a file, isolates the
6
+ * This program applies a baked-in seccomp BPF filter, isolates the
7
7
  * target command in a nested user+PID+mount namespace so it cannot see or
8
8
  * ptrace any process that lacks the filter, applies the filter with
9
9
  * prctl(PR_SET_SECCOMP), and execs the command.
@@ -45,6 +45,8 @@
45
45
  #include <linux/seccomp.h>
46
46
  #include <linux/filter.h>
47
47
 
48
+ #include "unix-block-bpf.h"
49
+
48
50
  #ifndef PR_SET_NO_NEW_PRIVS
49
51
  #define PR_SET_NO_NEW_PRIVS 38
50
52
  #endif
@@ -58,8 +60,6 @@
58
60
  #define SECCOMP_MODE_FILTER 2
59
61
  #endif
60
62
 
61
- #define MAX_FILTER_SIZE 4096
62
-
63
63
  static void die(const char *msg) {
64
64
  perror(msg);
65
65
  _exit(1);
@@ -144,29 +144,18 @@ static int reap_until(pid_t main_child) {
144
144
  }
145
145
 
146
146
  int main(int argc, char *argv[]) {
147
- if (argc < 3) {
148
- fprintf(stderr, "Usage: %s <filter.bpf> <command> [args...]\n", argv[0]);
147
+ if (argc < 2) {
148
+ fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
149
149
  return 1;
150
150
  }
151
151
 
152
- const char *filter_path = argv[1];
153
- char **command_argv = &argv[2];
152
+ char **command_argv = &argv[1];
154
153
 
155
- /* ---- Load the BPF filter up front so we fail before any namespace work. ---- */
156
- int fd = open(filter_path, O_RDONLY);
157
- if (fd < 0) {
158
- die("apply-seccomp: open(filter)");
159
- }
160
- static unsigned char filter_bytes[MAX_FILTER_SIZE];
161
- ssize_t filter_size = read(fd, filter_bytes, MAX_FILTER_SIZE);
162
- close(fd);
163
- if (filter_size <= 0 || filter_size % 8 != 0) {
164
- fprintf(stderr, "apply-seccomp: invalid BPF filter (size=%zd)\n", filter_size);
165
- return 1;
166
- }
154
+ _Static_assert(sizeof(unix_block_bpf) % sizeof(struct sock_filter) == 0,
155
+ "BPF filter size must be a multiple of sock_filter");
167
156
  struct sock_fprog prog = {
168
- .len = (unsigned short)(filter_size / 8),
169
- .filter = (struct sock_filter *)filter_bytes,
157
+ .len = (unsigned short)(sizeof(unix_block_bpf) / sizeof(struct sock_filter)),
158
+ .filter = (struct sock_filter *)unix_block_bpf,
170
159
  };
171
160
 
172
161
  /* ---- New PID + mount namespaces. Children (not us) enter the PID ns. ----
@@ -246,7 +235,13 @@ int main(int argc, char *argv[]) {
246
235
  if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) {
247
236
  die("apply-seccomp: mount(MS_PRIVATE)");
248
237
  }
249
- if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0) {
238
+ /* EPERM here means a masked /proc is underneath (unprivileged Docker)
239
+ * and the kernel domination check refused the overmount. The nested
240
+ * userns above is the isolation boundary; this remount only hides
241
+ * outer PIDs from `ls /proc`. enableWeakerNestedSandbox targets
242
+ * exactly this environment. */
243
+ if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0
244
+ && errno != EPERM) {
250
245
  die("apply-seccomp: mount(/proc)");
251
246
  }
252
247
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sysid/sandbox-runtime-improved",
3
- "version": "0.0.45-sysid.2",
3
+ "version": "0.0.49-sysid.2",
4
4
  "description": "Improved Anthropic Sandbox Runtime (ASRT) - A general-purpose tool for wrapping security boundaries around arbitrary processes (sysid fork with several improvements and bug fixes)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,24 +13,19 @@
13
13
  },
14
14
  "scripts": {
15
15
  "build": "tsc",
16
- "postbuild": "[ -d vendor ] && cp -r vendor dist/ || true",
17
- "build:seccomp": "scripts/build-seccomp-binaries.sh",
16
+ "build:seccomp": "bun vendor/seccomp/build.ts",
18
17
  "clean": "rm -rf dist",
19
18
  "test": "bun test",
20
- "test:unit": "bun test test/config-validation.test.ts test/sandbox/seccomp-filter.test.ts",
21
- "test:integration": "bun test test/sandbox/integration.test.ts test/sandbox/allow-read.test.ts test/sandbox/wrap-with-sandbox.test.ts",
22
19
  "typecheck": "tsc --noEmit",
23
20
  "lint": "eslint 'src/**/*.ts' --fix --cache --cache-location=node_modules/.cache/.eslintcache",
24
21
  "lint:check": "eslint 'src/**/*.ts' --cache --cache-location=node_modules/.cache/.eslintcache",
25
22
  "format": "prettier --write 'src/**/*.ts' --cache --log-level warn",
26
- "prepublishOnly": "npm run clean && npm run build",
23
+ "prepublishOnly": "npm run clean && npm run build && cp -r vendor dist/ && node -e \"['x64','arm64'].forEach(a=>{if(!require('fs').existsSync('dist/vendor/seccomp/'+a+'/apply-seccomp')){console.error('Missing seccomp binary: '+a);process.exit(1)}})\"",
27
24
  "prepare": "husky"
28
25
  },
29
26
  "dependencies": {
30
27
  "@pondwader/socks5-server": "^1.0.10",
31
- "@types/lodash-es": "^4.17.12",
32
28
  "commander": "^12.1.0",
33
- "lodash-es": "^4.17.23",
34
29
  "shell-quote": "^1.8.3",
35
30
  "zod": "^3.24.1"
36
31
  },
@@ -0,0 +1,6 @@
1
+ FROM ubuntu:24.04
2
+ RUN apt-get update && apt-get install -y --no-install-recommends \
3
+ gcc libc6-dev libseccomp-dev curl unzip ca-certificates && \
4
+ rm -rf /var/lib/apt/lists/*
5
+ RUN curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash
6
+ WORKDIR /build
Binary file
@@ -0,0 +1,91 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ if (process.platform !== 'linux') {
7
+ console.error('seccomp build: Linux only')
8
+ process.exit(1)
9
+ }
10
+
11
+ const HERE = dirname(fileURLToPath(import.meta.url))
12
+ const SRC = join(HERE, '..', 'seccomp-src')
13
+
14
+ const nodeArchToDir: Record<string, string> = { x64: 'x64', arm64: 'arm64' }
15
+ const arch = nodeArchToDir[process.arch]
16
+ if (!arch) {
17
+ console.error('seccomp build: unsupported arch ' + process.arch)
18
+ process.exit(1)
19
+ }
20
+ const OUT = join(HERE, arch)
21
+
22
+ function run(argv: string[]): void {
23
+ const [cmd, ...args] = argv
24
+ const r = spawnSync(cmd, args, { stdio: 'inherit' })
25
+ if (r.status !== 0) {
26
+ console.error(argv.join(' ') + ' exited ' + (r.status ?? r.signal))
27
+ process.exit(1)
28
+ }
29
+ }
30
+
31
+ function toCArray(bytes: Buffer): string {
32
+ const hex = Array.from(bytes, b => '0x' + b.toString(16).padStart(2, '0'))
33
+ const lines: string[] = []
34
+ for (let i = 0; i < hex.length; i += 8) {
35
+ lines.push(' ' + hex.slice(i, i + 8).join(', ') + ',')
36
+ }
37
+ return lines.join('\n')
38
+ }
39
+
40
+ mkdirSync(OUT, { recursive: true })
41
+
42
+ const cflags = ['-static', '-O2', '-Wall', '-Wextra']
43
+
44
+ const gen = join(OUT, 'seccomp-unix-block')
45
+ run([
46
+ 'gcc',
47
+ ...cflags,
48
+ '-o',
49
+ gen,
50
+ join(SRC, 'seccomp-unix-block.c'),
51
+ '-lseccomp',
52
+ ])
53
+
54
+ const bpf: Record<string, Buffer> = {}
55
+ for (const target of ['x86_64', 'aarch64']) {
56
+ const tmp = join(OUT, target + '.bpf')
57
+ run([gen, tmp, target])
58
+ bpf[target] = readFileSync(tmp)
59
+ rmSync(tmp)
60
+ }
61
+ rmSync(gen)
62
+
63
+ const header = join(OUT, 'unix-block-bpf.h')
64
+ writeFileSync(
65
+ header,
66
+ '#if defined(__x86_64__)\n' +
67
+ 'static const unsigned char unix_block_bpf[] = {\n' +
68
+ toCArray(bpf.x86_64) +
69
+ '\n};\n' +
70
+ '#elif defined(__aarch64__)\n' +
71
+ 'static const unsigned char unix_block_bpf[] = {\n' +
72
+ toCArray(bpf.aarch64) +
73
+ '\n};\n' +
74
+ '#else\n' +
75
+ '#error "unsupported architecture for unix-block BPF filter"\n' +
76
+ '#endif\n',
77
+ )
78
+
79
+ run([
80
+ 'gcc',
81
+ ...cflags,
82
+ '-I',
83
+ OUT,
84
+ '-o',
85
+ join(OUT, 'apply-seccomp'),
86
+ join(SRC, 'apply-seccomp.c'),
87
+ ])
88
+ run(['strip', join(OUT, 'apply-seccomp')])
89
+ rmSync(header)
90
+
91
+ console.log('built ' + join(OUT, 'apply-seccomp'))
Binary file
@@ -1,9 +1,9 @@
1
1
  /*
2
2
  * apply-seccomp.c - Apply seccomp BPF filter in an isolated PID namespace
3
3
  *
4
- * Usage: apply-seccomp <filter.bpf> <command> [args...]
4
+ * Usage: apply-seccomp <command> [args...]
5
5
  *
6
- * This program reads a pre-compiled BPF filter from a file, isolates the
6
+ * This program applies a baked-in seccomp BPF filter, isolates the
7
7
  * target command in a nested user+PID+mount namespace so it cannot see or
8
8
  * ptrace any process that lacks the filter, applies the filter with
9
9
  * prctl(PR_SET_SECCOMP), and execs the command.
@@ -45,6 +45,8 @@
45
45
  #include <linux/seccomp.h>
46
46
  #include <linux/filter.h>
47
47
 
48
+ #include "unix-block-bpf.h"
49
+
48
50
  #ifndef PR_SET_NO_NEW_PRIVS
49
51
  #define PR_SET_NO_NEW_PRIVS 38
50
52
  #endif
@@ -58,8 +60,6 @@
58
60
  #define SECCOMP_MODE_FILTER 2
59
61
  #endif
60
62
 
61
- #define MAX_FILTER_SIZE 4096
62
-
63
63
  static void die(const char *msg) {
64
64
  perror(msg);
65
65
  _exit(1);
@@ -144,29 +144,18 @@ static int reap_until(pid_t main_child) {
144
144
  }
145
145
 
146
146
  int main(int argc, char *argv[]) {
147
- if (argc < 3) {
148
- fprintf(stderr, "Usage: %s <filter.bpf> <command> [args...]\n", argv[0]);
147
+ if (argc < 2) {
148
+ fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
149
149
  return 1;
150
150
  }
151
151
 
152
- const char *filter_path = argv[1];
153
- char **command_argv = &argv[2];
152
+ char **command_argv = &argv[1];
154
153
 
155
- /* ---- Load the BPF filter up front so we fail before any namespace work. ---- */
156
- int fd = open(filter_path, O_RDONLY);
157
- if (fd < 0) {
158
- die("apply-seccomp: open(filter)");
159
- }
160
- static unsigned char filter_bytes[MAX_FILTER_SIZE];
161
- ssize_t filter_size = read(fd, filter_bytes, MAX_FILTER_SIZE);
162
- close(fd);
163
- if (filter_size <= 0 || filter_size % 8 != 0) {
164
- fprintf(stderr, "apply-seccomp: invalid BPF filter (size=%zd)\n", filter_size);
165
- return 1;
166
- }
154
+ _Static_assert(sizeof(unix_block_bpf) % sizeof(struct sock_filter) == 0,
155
+ "BPF filter size must be a multiple of sock_filter");
167
156
  struct sock_fprog prog = {
168
- .len = (unsigned short)(filter_size / 8),
169
- .filter = (struct sock_filter *)filter_bytes,
157
+ .len = (unsigned short)(sizeof(unix_block_bpf) / sizeof(struct sock_filter)),
158
+ .filter = (struct sock_filter *)unix_block_bpf,
170
159
  };
171
160
 
172
161
  /* ---- New PID + mount namespaces. Children (not us) enter the PID ns. ----
@@ -246,7 +235,13 @@ int main(int argc, char *argv[]) {
246
235
  if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) {
247
236
  die("apply-seccomp: mount(MS_PRIVATE)");
248
237
  }
249
- if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0) {
238
+ /* EPERM here means a masked /proc is underneath (unprivileged Docker)
239
+ * and the kernel domination check refused the overmount. The nested
240
+ * userns above is the isolation boundary; this remount only hides
241
+ * outer PIDs from `ls /proc`. enableWeakerNestedSandbox targets
242
+ * exactly this environment. */
243
+ if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0
244
+ && errno != EPERM) {
250
245
  die("apply-seccomp: mount(/proc)");
251
246
  }
252
247
 
Binary file
Binary file