@voratiq/sandbox-runtime 0.0.29-voratiq0

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 (85) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +17 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +158 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +9 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/sandbox/generate-seccomp-filter.d.ts +65 -0
  14. package/dist/sandbox/generate-seccomp-filter.d.ts.map +1 -0
  15. package/dist/sandbox/generate-seccomp-filter.js +185 -0
  16. package/dist/sandbox/generate-seccomp-filter.js.map +1 -0
  17. package/dist/sandbox/http-proxy.d.ts +14 -0
  18. package/dist/sandbox/http-proxy.d.ts.map +1 -0
  19. package/dist/sandbox/http-proxy.js +238 -0
  20. package/dist/sandbox/http-proxy.js.map +1 -0
  21. package/dist/sandbox/linux-sandbox-utils.d.ts +121 -0
  22. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -0
  23. package/dist/sandbox/linux-sandbox-utils.js +723 -0
  24. package/dist/sandbox/linux-sandbox-utils.js.map +1 -0
  25. package/dist/sandbox/macos-sandbox-utils.d.ts +57 -0
  26. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -0
  27. package/dist/sandbox/macos-sandbox-utils.js +611 -0
  28. package/dist/sandbox/macos-sandbox-utils.js.map +1 -0
  29. package/dist/sandbox/observability.d.ts +56 -0
  30. package/dist/sandbox/observability.d.ts.map +1 -0
  31. package/dist/sandbox/observability.js +140 -0
  32. package/dist/sandbox/observability.js.map +1 -0
  33. package/dist/sandbox/sandbox-config.d.ts +277 -0
  34. package/dist/sandbox/sandbox-config.d.ts.map +1 -0
  35. package/dist/sandbox/sandbox-config.js +166 -0
  36. package/dist/sandbox/sandbox-config.js.map +1 -0
  37. package/dist/sandbox/sandbox-manager.d.ts +50 -0
  38. package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
  39. package/dist/sandbox/sandbox-manager.js +816 -0
  40. package/dist/sandbox/sandbox-manager.js.map +1 -0
  41. package/dist/sandbox/sandbox-schemas.d.ts +53 -0
  42. package/dist/sandbox/sandbox-schemas.d.ts.map +1 -0
  43. package/dist/sandbox/sandbox-schemas.js +3 -0
  44. package/dist/sandbox/sandbox-schemas.js.map +1 -0
  45. package/dist/sandbox/sandbox-utils.d.ts +83 -0
  46. package/dist/sandbox/sandbox-utils.d.ts.map +1 -0
  47. package/dist/sandbox/sandbox-utils.js +343 -0
  48. package/dist/sandbox/sandbox-utils.js.map +1 -0
  49. package/dist/sandbox/sandbox-violation-store.d.ts +19 -0
  50. package/dist/sandbox/sandbox-violation-store.d.ts.map +1 -0
  51. package/dist/sandbox/sandbox-violation-store.js +54 -0
  52. package/dist/sandbox/sandbox-violation-store.js.map +1 -0
  53. package/dist/sandbox/socks-proxy.d.ts +14 -0
  54. package/dist/sandbox/socks-proxy.d.ts.map +1 -0
  55. package/dist/sandbox/socks-proxy.js +109 -0
  56. package/dist/sandbox/socks-proxy.js.map +1 -0
  57. package/dist/utils/config-loader.d.ts +11 -0
  58. package/dist/utils/config-loader.d.ts.map +1 -0
  59. package/dist/utils/config-loader.js +60 -0
  60. package/dist/utils/config-loader.js.map +1 -0
  61. package/dist/utils/debug.d.ts +7 -0
  62. package/dist/utils/debug.d.ts.map +1 -0
  63. package/dist/utils/debug.js +25 -0
  64. package/dist/utils/debug.js.map +1 -0
  65. package/dist/utils/platform.d.ts +15 -0
  66. package/dist/utils/platform.d.ts.map +1 -0
  67. package/dist/utils/platform.js +49 -0
  68. package/dist/utils/platform.js.map +1 -0
  69. package/dist/utils/ripgrep.d.ts +20 -0
  70. package/dist/utils/ripgrep.d.ts.map +1 -0
  71. package/dist/utils/ripgrep.js +51 -0
  72. package/dist/utils/ripgrep.js.map +1 -0
  73. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  74. package/dist/vendor/seccomp/arm64/unix-block.bpf +0 -0
  75. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  76. package/dist/vendor/seccomp/x64/unix-block.bpf +0 -0
  77. package/dist/vendor/seccomp-src/apply-seccomp.c +98 -0
  78. package/dist/vendor/seccomp-src/seccomp-unix-block.c +97 -0
  79. package/package.json +90 -0
  80. package/vendor/seccomp/arm64/apply-seccomp +0 -0
  81. package/vendor/seccomp/arm64/unix-block.bpf +0 -0
  82. package/vendor/seccomp/x64/apply-seccomp +0 -0
  83. package/vendor/seccomp/x64/unix-block.bpf +0 -0
  84. package/vendor/seccomp-src/apply-seccomp.c +98 -0
  85. package/vendor/seccomp-src/seccomp-unix-block.c +97 -0
@@ -0,0 +1,185 @@
1
+ import { join, dirname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import * as fs from 'node:fs';
4
+ import { logForDebugging } from '../utils/debug.js';
5
+ /**
6
+ * Map Node.js process.arch to our vendor directory architecture names
7
+ * Returns null for unsupported architectures
8
+ */
9
+ function getVendorArchitecture() {
10
+ const arch = process.arch;
11
+ switch (arch) {
12
+ case 'x64':
13
+ case 'x86_64':
14
+ return 'x64';
15
+ case 'arm64':
16
+ case 'aarch64':
17
+ return 'arm64';
18
+ case 'ia32':
19
+ case 'x86':
20
+ // TODO: Add support for 32-bit x86 (ia32)
21
+ // Currently blocked because the seccomp filter does not block the socketcall() syscall,
22
+ // which is used on 32-bit x86 for all socket operations (socket, socketpair, bind, connect, etc.).
23
+ // On 32-bit x86, the direct socket() syscall doesn't exist - instead, all socket operations
24
+ // are multiplexed through socketcall(SYS_SOCKET, ...), socketcall(SYS_SOCKETPAIR, ...), etc.
25
+ //
26
+ // To properly support 32-bit x86, we need to:
27
+ // 1. Build a separate i386 BPF filter (BPF bytecode is architecture-specific)
28
+ // 2. Modify vendor/seccomp-src/seccomp-unix-block.c to conditionally add rules that block:
29
+ // - socketcall(SYS_SOCKET, [AF_UNIX, ...])
30
+ // - socketcall(SYS_SOCKETPAIR, [AF_UNIX, ...])
31
+ // 3. This requires complex BPF logic to inspect socketcall's sub-function argument
32
+ //
33
+ // Until then, 32-bit x86 is not supported to avoid a security bypass.
34
+ logForDebugging(`[SeccompFilter] 32-bit x86 (ia32) is not currently supported due to missing socketcall() syscall blocking. ` +
35
+ `The current seccomp filter only blocks socket(AF_UNIX, ...), but on 32-bit x86, socketcall() can be used to bypass this.`, { level: 'error' });
36
+ return null;
37
+ default:
38
+ logForDebugging(`[SeccompFilter] Unsupported architecture: ${arch}. Only x64 and arm64 are supported.`);
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Get the path to a pre-generated BPF filter file from the vendor directory
44
+ * Returns the path if it exists, null otherwise
45
+ *
46
+ * Pre-generated BPF files are organized by architecture:
47
+ * - vendor/seccomp/{x64,arm64}/unix-block.bpf
48
+ *
49
+ * Tries multiple paths for resilience:
50
+ * 0. Expected path provided via parameter (checked first if provided)
51
+ * 1. vendor/seccomp/{arch}/unix-block.bpf (bundled - when bundled into consuming packages)
52
+ * 2. ../../vendor/seccomp/{arch}/unix-block.bpf (package root - standard npm installs)
53
+ * 3. ../vendor/seccomp/{arch}/unix-block.bpf (dist/vendor - for bundlers)
54
+ *
55
+ * @param expectedPath - Optional path to check first (highest priority)
56
+ */
57
+ export function getPreGeneratedBpfPath(expectedPath) {
58
+ // Check expected path first (highest priority)
59
+ if (expectedPath) {
60
+ if (fs.existsSync(expectedPath)) {
61
+ logForDebugging(`[SeccompFilter] Using BPF filter from expected path: ${expectedPath}`);
62
+ return expectedPath;
63
+ }
64
+ logForDebugging(`[SeccompFilter] Expected path provided but file not found: ${expectedPath}`);
65
+ }
66
+ // Determine architecture
67
+ const arch = getVendorArchitecture();
68
+ if (!arch) {
69
+ logForDebugging(`[SeccompFilter] Cannot find pre-generated BPF filter: unsupported architecture ${process.arch}`);
70
+ return null;
71
+ }
72
+ logForDebugging(`[SeccompFilter] Detected architecture: ${arch}`);
73
+ // Try to locate the BPF file with fallback paths
74
+ // Path is relative to the compiled code location (dist/sandbox/)
75
+ const baseDir = dirname(fileURLToPath(import.meta.url));
76
+ const relativePath = join('vendor', 'seccomp', arch, 'unix-block.bpf');
77
+ // Try paths in order of preference
78
+ const pathsToTry = [
79
+ join(baseDir, relativePath), // bundled: same directory as bundle (e.g., when bundled into claude-cli)
80
+ join(baseDir, '..', '..', relativePath), // package root: vendor/seccomp/...
81
+ join(baseDir, '..', relativePath), // dist: dist/vendor/seccomp/...
82
+ ];
83
+ for (const bpfPath of pathsToTry) {
84
+ if (fs.existsSync(bpfPath)) {
85
+ logForDebugging(`[SeccompFilter] Found pre-generated BPF filter: ${bpfPath} (${arch})`);
86
+ return bpfPath;
87
+ }
88
+ }
89
+ logForDebugging(`[SeccompFilter] Pre-generated BPF filter not found in any expected location (${arch})`);
90
+ return null;
91
+ }
92
+ /**
93
+ * Get the path to the apply-seccomp binary from the vendor directory
94
+ * Returns the path if it exists, null otherwise
95
+ *
96
+ * Pre-built apply-seccomp binaries are organized by architecture:
97
+ * - vendor/seccomp/{x64,arm64}/apply-seccomp
98
+ *
99
+ * Tries multiple paths for resilience:
100
+ * 0. Expected path provided via parameter (checked first if provided)
101
+ * 1. vendor/seccomp/{arch}/apply-seccomp (bundled - when bundled into consuming packages)
102
+ * 2. ../../vendor/seccomp/{arch}/apply-seccomp (package root - standard npm installs)
103
+ * 3. ../vendor/seccomp/{arch}/apply-seccomp (dist/vendor - for bundlers)
104
+ *
105
+ * @param expectedPath - Optional path to check first (highest priority)
106
+ */
107
+ export function getApplySeccompBinaryPath(expectedPath) {
108
+ // Check expected path first (highest priority)
109
+ if (expectedPath) {
110
+ if (fs.existsSync(expectedPath)) {
111
+ logForDebugging(`[SeccompFilter] Using apply-seccomp binary from expected path: ${expectedPath}`);
112
+ return expectedPath;
113
+ }
114
+ logForDebugging(`[SeccompFilter] Expected path provided but file not found: ${expectedPath}`);
115
+ }
116
+ // Determine architecture
117
+ const arch = getVendorArchitecture();
118
+ if (!arch) {
119
+ logForDebugging(`[SeccompFilter] Cannot find apply-seccomp binary: unsupported architecture ${process.arch}`);
120
+ return null;
121
+ }
122
+ logForDebugging(`[SeccompFilter] Looking for apply-seccomp binary for architecture: ${arch}`);
123
+ // Try to locate the binary with fallback paths
124
+ // Path is relative to the compiled code location (dist/sandbox/)
125
+ const baseDir = dirname(fileURLToPath(import.meta.url));
126
+ const relativePath = join('vendor', 'seccomp', arch, 'apply-seccomp');
127
+ // Try paths in order of preference
128
+ const pathsToTry = [
129
+ join(baseDir, relativePath), // bundled: same directory as bundle (e.g., when bundled into claude-cli)
130
+ join(baseDir, '..', '..', relativePath), // package root: vendor/seccomp/...
131
+ join(baseDir, '..', relativePath), // dist: dist/vendor/seccomp/...
132
+ ];
133
+ for (const binaryPath of pathsToTry) {
134
+ if (fs.existsSync(binaryPath)) {
135
+ logForDebugging(`[SeccompFilter] Found apply-seccomp binary: ${binaryPath} (${arch})`);
136
+ return binaryPath;
137
+ }
138
+ }
139
+ logForDebugging(`[SeccompFilter] apply-seccomp binary not found in any expected location (${arch})`);
140
+ return null;
141
+ }
142
+ /**
143
+ * Get the path to a pre-generated seccomp BPF filter that blocks Unix domain socket creation
144
+ * Returns the path to the BPF filter file, or null if not available
145
+ *
146
+ * The filter blocks socket(AF_UNIX, ...) syscalls while allowing all other syscalls.
147
+ * This prevents creation of new Unix domain socket file descriptors.
148
+ *
149
+ * Security scope:
150
+ * - Blocks: socket(AF_UNIX, ...) syscall (creating new Unix socket FDs)
151
+ * - Does NOT block: Operations on inherited Unix socket FDs (bind, connect, sendto, etc.)
152
+ * - Does NOT block: Unix socket FDs passed via SCM_RIGHTS
153
+ * - For most sandboxing scenarios, blocking socket creation is sufficient
154
+ *
155
+ * Note: This blocks ALL Unix socket creation, regardless of path. The allowUnixSockets
156
+ * configuration is not supported on Linux due to seccomp-bpf limitations (it cannot
157
+ * read user-space memory to inspect socket paths).
158
+ *
159
+ * Requirements:
160
+ * - Pre-generated BPF filters included for x64 and ARM64 only
161
+ * - Other architectures are not supported
162
+ *
163
+ * @param expectedPath - Optional path to check first (highest priority)
164
+ * @returns Path to the pre-generated BPF filter file, or null if not available
165
+ */
166
+ export function generateSeccompFilter(expectedPath) {
167
+ const preGeneratedBpf = getPreGeneratedBpfPath(expectedPath);
168
+ if (preGeneratedBpf) {
169
+ logForDebugging('[SeccompFilter] Using pre-generated BPF filter');
170
+ return preGeneratedBpf;
171
+ }
172
+ logForDebugging('[SeccompFilter] Pre-generated BPF filter not available for this architecture. ' +
173
+ 'Only x64 and arm64 are supported.', { level: 'error' });
174
+ return null;
175
+ }
176
+ /**
177
+ * Clean up a seccomp filter file
178
+ * Since we only use pre-generated BPF files from vendor/, this is a no-op.
179
+ * Pre-generated files are never deleted.
180
+ * Kept for backward compatibility with existing code that calls it.
181
+ */
182
+ export function cleanupSeccompFilter(_filterPath) {
183
+ // No-op: pre-generated BPF files are never cleaned up
184
+ }
185
+ //# sourceMappingURL=generate-seccomp-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-seccomp-filter.js","sourceRoot":"","sources":["../../src/sandbox/generate-seccomp-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Socket, Server } from 'node:net';
2
+ import type { Duplex } from 'node:stream';
3
+ import { type NetworkFilterResult } from './observability.js';
4
+ export interface HttpProxyServerOptions {
5
+ filter(port: number, host: string, socket: Socket | Duplex): Promise<boolean | NetworkFilterResult> | boolean | NetworkFilterResult;
6
+ /**
7
+ * Optional function to get the MITM proxy socket path for a given host.
8
+ * If returns a socket path, the request will be routed through that MITM proxy.
9
+ * If returns undefined, the request will be handled directly.
10
+ */
11
+ getMitmSocketPath?(host: string): string | undefined;
12
+ }
13
+ export declare function createHttpProxyServer(options: HttpProxyServerOptions): Server;
14
+ //# sourceMappingURL=http-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-proxy.d.ts","sourceRoot":"","sources":["../../src/sandbox/http-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAOzC,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAA;AAE3B,MAAM,WAAW,sBAAsB;IACrC,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,MAAM,GACtB,OAAO,CAAC,OAAO,GAAG,mBAAmB,CAAC,GAAG,OAAO,GAAG,mBAAmB,CAAA;IAEzE;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CACrD;AAWD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAiR7E"}
@@ -0,0 +1,238 @@
1
+ import { Agent, createServer } from 'node:http';
2
+ import { request as httpRequest } from 'node:http';
3
+ import { request as httpsRequest } from 'node:https';
4
+ import { connect } from 'node:net';
5
+ import { URL } from 'node:url';
6
+ import { logForDebugging } from '../utils/debug.js';
7
+ import { emitNetworkDecisionEvent, } from './observability.js';
8
+ function normalizeFilterResult(result) {
9
+ if (typeof result === 'boolean') {
10
+ return { allowed: result, reason: 'no-match' };
11
+ }
12
+ return result;
13
+ }
14
+ export function createHttpProxyServer(options) {
15
+ const server = createServer();
16
+ // Handle CONNECT requests for HTTPS traffic
17
+ server.on('connect', async (req, socket) => {
18
+ // Attach error handler immediately to prevent unhandled errors
19
+ socket.on('error', err => {
20
+ logForDebugging(`Client socket error: ${err.message}`, { level: 'error' });
21
+ });
22
+ try {
23
+ const [hostname, portStr] = req.url.split(':');
24
+ const port = portStr === undefined ? undefined : parseInt(portStr, 10);
25
+ if (!hostname || !port) {
26
+ logForDebugging(`Invalid CONNECT request: ${req.url}`, {
27
+ level: 'error',
28
+ });
29
+ socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
30
+ return;
31
+ }
32
+ const filterResult = normalizeFilterResult(await options.filter(port, hostname, socket));
33
+ const mitmSocketPath = options.getMitmSocketPath?.(hostname);
34
+ const route = mitmSocketPath ? 'mitm' : 'direct';
35
+ emitNetworkDecisionEvent({
36
+ host: hostname,
37
+ port,
38
+ decision: filterResult.allowed ? 'allow' : 'deny',
39
+ reason: filterResult.reason,
40
+ route,
41
+ });
42
+ if (!filterResult.allowed) {
43
+ logForDebugging(`Connection blocked to ${hostname}:${port}`, {
44
+ level: 'error',
45
+ });
46
+ socket.end('HTTP/1.1 403 Forbidden\r\n' +
47
+ 'Content-Type: text/plain\r\n' +
48
+ 'X-Proxy-Error: blocked-by-allowlist\r\n' +
49
+ '\r\n' +
50
+ 'Connection blocked by network allowlist');
51
+ return;
52
+ }
53
+ if (mitmSocketPath) {
54
+ // Route through MITM proxy via Unix socket
55
+ logForDebugging(`Routing CONNECT ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
56
+ const mitmSocket = connect({ path: mitmSocketPath }, () => {
57
+ // Send CONNECT request to the MITM proxy
58
+ mitmSocket.write(`CONNECT ${hostname}:${port} HTTP/1.1\r\n` +
59
+ `Host: ${hostname}:${port}\r\n` +
60
+ '\r\n');
61
+ });
62
+ // Buffer to accumulate the MITM proxy's response
63
+ let responseBuffer = '';
64
+ const onMitmData = (chunk) => {
65
+ responseBuffer += chunk.toString();
66
+ // Check if we've received the full HTTP response headers
67
+ const headerEndIndex = responseBuffer.indexOf('\r\n\r\n');
68
+ if (headerEndIndex !== -1) {
69
+ // Remove data listener, we're done parsing the response
70
+ mitmSocket.removeListener('data', onMitmData);
71
+ // Check if MITM proxy accepted the connection
72
+ const statusLine = responseBuffer.substring(0, responseBuffer.indexOf('\r\n'));
73
+ if (statusLine.includes(' 200 ')) {
74
+ // Connection established, now pipe data between client and MITM
75
+ socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
76
+ // If there's any data after the headers, write it to the client
77
+ const remainingData = responseBuffer.substring(headerEndIndex + 4);
78
+ if (remainingData.length > 0) {
79
+ socket.write(remainingData);
80
+ }
81
+ mitmSocket.pipe(socket);
82
+ socket.pipe(mitmSocket);
83
+ }
84
+ else {
85
+ logForDebugging(`MITM proxy rejected CONNECT: ${statusLine}`, {
86
+ level: 'error',
87
+ });
88
+ socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
89
+ mitmSocket.destroy();
90
+ }
91
+ }
92
+ };
93
+ mitmSocket.on('data', onMitmData);
94
+ mitmSocket.on('error', err => {
95
+ logForDebugging(`MITM proxy connection failed: ${err.message}`, {
96
+ level: 'error',
97
+ });
98
+ socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
99
+ });
100
+ socket.on('error', err => {
101
+ logForDebugging(`Client socket error: ${err.message}`, {
102
+ level: 'error',
103
+ });
104
+ mitmSocket.destroy();
105
+ });
106
+ socket.on('end', () => mitmSocket.end());
107
+ mitmSocket.on('end', () => socket.end());
108
+ }
109
+ else {
110
+ // Direct connection (original behavior)
111
+ const serverSocket = connect(port, hostname, () => {
112
+ socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
113
+ serverSocket.pipe(socket);
114
+ socket.pipe(serverSocket);
115
+ });
116
+ serverSocket.on('error', err => {
117
+ logForDebugging(`CONNECT tunnel failed: ${err.message}`, {
118
+ level: 'error',
119
+ });
120
+ socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
121
+ });
122
+ socket.on('error', err => {
123
+ logForDebugging(`Client socket error: ${err.message}`, {
124
+ level: 'error',
125
+ });
126
+ serverSocket.destroy();
127
+ });
128
+ socket.on('end', () => serverSocket.end());
129
+ serverSocket.on('end', () => socket.end());
130
+ }
131
+ }
132
+ catch (err) {
133
+ logForDebugging(`Error handling CONNECT: ${err}`, { level: 'error' });
134
+ socket.end('HTTP/1.1 500 Internal Server Error\r\n\r\n');
135
+ }
136
+ });
137
+ // Handle regular HTTP requests
138
+ server.on('request', async (req, res) => {
139
+ try {
140
+ const url = new URL(req.url);
141
+ const hostname = url.hostname;
142
+ const port = url.port
143
+ ? parseInt(url.port, 10)
144
+ : url.protocol === 'https:'
145
+ ? 443
146
+ : 80;
147
+ const filterResult = normalizeFilterResult(await options.filter(port, hostname, req.socket));
148
+ const mitmSocketPath = options.getMitmSocketPath?.(hostname);
149
+ const route = mitmSocketPath ? 'mitm' : 'direct';
150
+ emitNetworkDecisionEvent({
151
+ host: hostname,
152
+ port,
153
+ decision: filterResult.allowed ? 'allow' : 'deny',
154
+ reason: filterResult.reason,
155
+ route,
156
+ });
157
+ if (!filterResult.allowed) {
158
+ logForDebugging(`HTTP request blocked to ${hostname}:${port}`, {
159
+ level: 'error',
160
+ });
161
+ res.writeHead(403, {
162
+ 'Content-Type': 'text/plain',
163
+ 'X-Proxy-Error': 'blocked-by-allowlist',
164
+ });
165
+ res.end('Connection blocked by network allowlist');
166
+ return;
167
+ }
168
+ if (mitmSocketPath) {
169
+ // Route through MITM proxy via Unix socket
170
+ // Use an agent that connects via the Unix socket
171
+ logForDebugging(`Routing HTTP ${req.method} ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
172
+ const mitmAgent = new Agent({
173
+ // @ts-expect-error - socketPath is valid but not in types
174
+ socketPath: mitmSocketPath,
175
+ });
176
+ // Send request to MITM proxy with full URL (proxy-style request)
177
+ const proxyReq = httpRequest({
178
+ agent: mitmAgent,
179
+ // For proxy requests, path should be the full URL
180
+ path: req.url,
181
+ method: req.method,
182
+ headers: {
183
+ ...req.headers,
184
+ host: url.host,
185
+ },
186
+ }, proxyRes => {
187
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
188
+ proxyRes.pipe(res);
189
+ });
190
+ proxyReq.on('error', err => {
191
+ logForDebugging(`MITM proxy request failed: ${err.message}`, {
192
+ level: 'error',
193
+ });
194
+ if (!res.headersSent) {
195
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
196
+ res.end('Bad Gateway');
197
+ }
198
+ });
199
+ req.pipe(proxyReq);
200
+ }
201
+ else {
202
+ // Direct request (original behavior)
203
+ // Choose http or https module
204
+ const requestFn = url.protocol === 'https:' ? httpsRequest : httpRequest;
205
+ const proxyReq = requestFn({
206
+ hostname,
207
+ port,
208
+ path: url.pathname + url.search,
209
+ method: req.method,
210
+ headers: {
211
+ ...req.headers,
212
+ host: url.host,
213
+ },
214
+ }, proxyRes => {
215
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
216
+ proxyRes.pipe(res);
217
+ });
218
+ proxyReq.on('error', err => {
219
+ logForDebugging(`Proxy request failed: ${err.message}`, {
220
+ level: 'error',
221
+ });
222
+ if (!res.headersSent) {
223
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
224
+ res.end('Bad Gateway');
225
+ }
226
+ });
227
+ req.pipe(proxyReq);
228
+ }
229
+ }
230
+ catch (err) {
231
+ logForDebugging(`Error handling HTTP request: ${err}`, { level: 'error' });
232
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
233
+ res.end('Internal Server Error');
234
+ }
235
+ });
236
+ return server;
237
+ }
238
+ //# sourceMappingURL=http-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-proxy.js","sourceRoot":"","sources":["../../src/sandbox/http-proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ import type { FsReadRestrictionConfig, FsWriteRestrictionConfig } from './sandbox-schemas.js';
3
+ export interface LinuxNetworkBridgeContext {
4
+ httpSocketPath: string;
5
+ socksSocketPath: string;
6
+ httpBridgeProcess: ChildProcess;
7
+ socksBridgeProcess: ChildProcess;
8
+ httpProxyPort: number;
9
+ socksProxyPort: number;
10
+ }
11
+ export interface LinuxSandboxParams {
12
+ command: string;
13
+ needsNetworkRestriction: boolean;
14
+ httpSocketPath?: string;
15
+ socksSocketPath?: string;
16
+ httpProxyPort?: number;
17
+ socksProxyPort?: number;
18
+ readConfig?: FsReadRestrictionConfig;
19
+ writeConfig?: FsWriteRestrictionConfig;
20
+ enableWeakerNestedSandbox?: boolean;
21
+ allowAllUnixSockets?: boolean;
22
+ binShell?: string;
23
+ ripgrepConfig?: {
24
+ command: string;
25
+ args?: string[];
26
+ };
27
+ /** Maximum directory depth to search for dangerous files (default: 3) */
28
+ mandatoryDenySearchDepth?: number;
29
+ /** Allow writes to .git/config files (default: false) */
30
+ allowGitConfig?: boolean;
31
+ /** Custom seccomp binary paths */
32
+ seccompConfig?: {
33
+ bpfPath?: string;
34
+ applyPath?: string;
35
+ };
36
+ /** Abort signal to cancel the ripgrep scan */
37
+ abortSignal?: AbortSignal;
38
+ }
39
+ /**
40
+ * Check if Linux sandbox dependencies are available (synchronous)
41
+ * Returns true if bwrap and socat are installed.
42
+ */
43
+ export declare function hasLinuxSandboxDependenciesSync(allowAllUnixSockets?: boolean, seccompConfig?: {
44
+ bpfPath?: string;
45
+ applyPath?: string;
46
+ }): boolean;
47
+ /**
48
+ * Initialize the Linux network bridge for sandbox networking
49
+ *
50
+ * ARCHITECTURE NOTE:
51
+ * Linux network sandboxing uses bwrap --unshare-net which creates a completely isolated
52
+ * network namespace with NO network access. To enable network access, we:
53
+ *
54
+ * 1. Host side: Run socat bridges that listen on Unix sockets and forward to host proxy servers
55
+ * - HTTP bridge: Unix socket -> host HTTP proxy (for HTTP/HTTPS traffic)
56
+ * - SOCKS bridge: Unix socket -> host SOCKS5 proxy (for SSH/git traffic)
57
+ *
58
+ * 2. Sandbox side: Bind the Unix sockets into the isolated namespace and run socat listeners
59
+ * - HTTP listener on port 3128 -> HTTP Unix socket -> host HTTP proxy
60
+ * - SOCKS listener on port 1080 -> SOCKS Unix socket -> host SOCKS5 proxy
61
+ *
62
+ * 3. Configure environment:
63
+ * - HTTP_PROXY=http://localhost:3128 for HTTP/HTTPS tools
64
+ * - GIT_SSH_COMMAND with socat for SSH through SOCKS5
65
+ *
66
+ * LIMITATION: Unlike macOS sandbox which can enforce domain-based allowlists at the kernel level,
67
+ * Linux's --unshare-net provides only all-or-nothing network isolation. Domain filtering happens
68
+ * at the host proxy level, not the sandbox boundary. This means network restrictions on Linux
69
+ * depend on the proxy's filtering capabilities.
70
+ *
71
+ * DEPENDENCIES: Requires bwrap (bubblewrap) and socat
72
+ */
73
+ export declare function initializeLinuxNetworkBridge(httpProxyPort: number, socksProxyPort: number): Promise<LinuxNetworkBridgeContext>;
74
+ /**
75
+ * Wrap a command with sandbox restrictions on Linux
76
+ *
77
+ * UNIX SOCKET BLOCKING (APPLY-SECCOMP):
78
+ * This implementation uses a custom apply-seccomp binary to block Unix domain socket
79
+ * creation for user commands while allowing network infrastructure:
80
+ *
81
+ * Stage 1: Outer bwrap - Network and filesystem isolation (NO seccomp)
82
+ * - Bubblewrap starts with isolated network namespace (--unshare-net)
83
+ * - Bubblewrap applies PID namespace isolation (--unshare-pid and --proc)
84
+ * - Filesystem restrictions are applied (read-only mounts, bind mounts, etc.)
85
+ * - Socat processes start and connect to Unix socket bridges (can use socket(AF_UNIX, ...))
86
+ *
87
+ * Stage 2: apply-seccomp - Seccomp filter application (ONLY seccomp)
88
+ * - apply-seccomp binary applies seccomp filter via prctl(PR_SET_SECCOMP)
89
+ * - Sets PR_SET_NO_NEW_PRIVS to allow seccomp without root
90
+ * - Execs user command with seccomp active (cannot create new Unix sockets)
91
+ *
92
+ * This solves the conflict between:
93
+ * - Security: Blocking arbitrary Unix socket creation in user commands
94
+ * - Functionality: Network sandboxing requires socat to call socket(AF_UNIX, ...) for bridge connections
95
+ *
96
+ * The seccomp-bpf filter blocks socket(AF_UNIX, ...) syscalls, preventing:
97
+ * - Creating new Unix domain socket file descriptors
98
+ *
99
+ * Security limitations:
100
+ * - Does NOT block operations (bind, connect, sendto, etc.) on inherited Unix socket FDs
101
+ * - Does NOT prevent passing Unix socket FDs via SCM_RIGHTS
102
+ * - For most sandboxing use cases, blocking socket creation is sufficient
103
+ *
104
+ * The filter allows:
105
+ * - All TCP/UDP sockets (AF_INET, AF_INET6) for normal network operations
106
+ * - All other syscalls
107
+ *
108
+ * PLATFORM NOTE:
109
+ * The allowUnixSockets configuration is not path-based on Linux (unlike macOS)
110
+ * because seccomp-bpf cannot inspect user-space memory to read socket paths.
111
+ *
112
+ * Requirements for seccomp filtering:
113
+ * - Pre-built apply-seccomp binaries are included for x64 and ARM64
114
+ * - Pre-generated BPF filters are included for x64 and ARM64
115
+ * - Other architectures are not currently supported (no apply-seccomp binary available)
116
+ * - To use sandboxing without Unix socket blocking on unsupported architectures,
117
+ * set allowAllUnixSockets: true in your configuration
118
+ * Dependencies are checked by hasLinuxSandboxDependenciesSync() before enabling the sandbox.
119
+ */
120
+ export declare function wrapCommandWithSandboxLinux(params: LinuxSandboxParams): Promise<string>;
121
+ //# sourceMappingURL=linux-sandbox-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linux-sandbox-utils.d.ts","sourceRoot":"","sources":["../../src/sandbox/linux-sandbox-utils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAWtD,OAAO,KAAK,EACV,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,sBAAsB,CAAA;AAQ7B,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,YAAY,CAAA;IAC/B,kBAAkB,EAAE,YAAY,CAAA;IAChC,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,uBAAuB,EAAE,OAAO,CAAA;IAChC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,uBAAuB,CAAA;IACpC,WAAW,CAAC,EAAE,wBAAwB,CAAA;IACtC,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IACpD,yEAAyE;IACzE,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,yDAAyD;IACzD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kCAAkC;IAClC,aAAa,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,8CAA8C;IAC9C,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AA2MD;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,mBAAmB,UAAQ,EAC3B,aAAa,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,OAAO,CAuCT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,yBAAyB,CAAC,CA2HpC;AAoOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAuPjB"}