@voratiq/sandbox-runtime 0.7.0-voratiq1

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 (81) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +11 -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 +243 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +7 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/sandbox/generate-seccomp-filter.d.ts +56 -0
  14. package/dist/sandbox/generate-seccomp-filter.d.ts.map +1 -0
  15. package/dist/sandbox/generate-seccomp-filter.js +158 -0
  16. package/dist/sandbox/generate-seccomp-filter.js.map +1 -0
  17. package/dist/sandbox/http-proxy.d.ts +12 -0
  18. package/dist/sandbox/http-proxy.d.ts.map +1 -0
  19. package/dist/sandbox/http-proxy.js +489 -0
  20. package/dist/sandbox/http-proxy.js.map +1 -0
  21. package/dist/sandbox/linux-sandbox-utils.d.ts +111 -0
  22. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -0
  23. package/dist/sandbox/linux-sandbox-utils.js +518 -0
  24. package/dist/sandbox/linux-sandbox-utils.js.map +1 -0
  25. package/dist/sandbox/macos-sandbox-utils.d.ts +54 -0
  26. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -0
  27. package/dist/sandbox/macos-sandbox-utils.js +559 -0
  28. package/dist/sandbox/macos-sandbox-utils.js.map +1 -0
  29. package/dist/sandbox/sandbox-config.d.ts +170 -0
  30. package/dist/sandbox/sandbox-config.d.ts.map +1 -0
  31. package/dist/sandbox/sandbox-config.js +126 -0
  32. package/dist/sandbox/sandbox-config.js.map +1 -0
  33. package/dist/sandbox/sandbox-manager.d.ts +35 -0
  34. package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
  35. package/dist/sandbox/sandbox-manager.js +666 -0
  36. package/dist/sandbox/sandbox-manager.js.map +1 -0
  37. package/dist/sandbox/sandbox-schemas.d.ts +17 -0
  38. package/dist/sandbox/sandbox-schemas.d.ts.map +1 -0
  39. package/dist/sandbox/sandbox-schemas.js +2 -0
  40. package/dist/sandbox/sandbox-schemas.js.map +1 -0
  41. package/dist/sandbox/sandbox-utils.d.ts +53 -0
  42. package/dist/sandbox/sandbox-utils.d.ts.map +1 -0
  43. package/dist/sandbox/sandbox-utils.js +368 -0
  44. package/dist/sandbox/sandbox-utils.js.map +1 -0
  45. package/dist/sandbox/sandbox-violation-store.d.ts +19 -0
  46. package/dist/sandbox/sandbox-violation-store.d.ts.map +1 -0
  47. package/dist/sandbox/sandbox-violation-store.js +54 -0
  48. package/dist/sandbox/sandbox-violation-store.js.map +1 -0
  49. package/dist/sandbox/socks-proxy.d.ts +18 -0
  50. package/dist/sandbox/socks-proxy.d.ts.map +1 -0
  51. package/dist/sandbox/socks-proxy.js +242 -0
  52. package/dist/sandbox/socks-proxy.js.map +1 -0
  53. package/dist/utils/debug.d.ts +7 -0
  54. package/dist/utils/debug.d.ts.map +1 -0
  55. package/dist/utils/debug.js +22 -0
  56. package/dist/utils/debug.js.map +1 -0
  57. package/dist/utils/platform.d.ts +6 -0
  58. package/dist/utils/platform.d.ts.map +1 -0
  59. package/dist/utils/platform.js +16 -0
  60. package/dist/utils/platform.js.map +1 -0
  61. package/dist/utils/ripgrep.d.ts +20 -0
  62. package/dist/utils/ripgrep.d.ts.map +1 -0
  63. package/dist/utils/ripgrep.js +51 -0
  64. package/dist/utils/ripgrep.js.map +1 -0
  65. package/dist/utils/telemetry.d.ts +67 -0
  66. package/dist/utils/telemetry.d.ts.map +1 -0
  67. package/dist/utils/telemetry.js +249 -0
  68. package/dist/utils/telemetry.js.map +1 -0
  69. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  70. package/dist/vendor/seccomp/arm64/unix-block.bpf +0 -0
  71. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  72. package/dist/vendor/seccomp/x64/unix-block.bpf +0 -0
  73. package/dist/vendor/seccomp-src/apply-seccomp.c +98 -0
  74. package/dist/vendor/seccomp-src/seccomp-unix-block.c +97 -0
  75. package/package.json +80 -0
  76. package/vendor/seccomp/arm64/apply-seccomp +0 -0
  77. package/vendor/seccomp/arm64/unix-block.bpf +0 -0
  78. package/vendor/seccomp/x64/apply-seccomp +0 -0
  79. package/vendor/seccomp/x64/unix-block.bpf +0 -0
  80. package/vendor/seccomp-src/apply-seccomp.c +98 -0
  81. package/vendor/seccomp-src/seccomp-unix-block.c +97 -0
@@ -0,0 +1,666 @@
1
+ import { createHttpProxyServer } from './http-proxy.js';
2
+ import { createSocksProxyServer } from './socks-proxy.js';
3
+ import { logForDebugging } from '../utils/debug.js';
4
+ import { getPlatform } from '../utils/platform.js';
5
+ import * as fs from 'fs';
6
+ import { wrapCommandWithSandboxLinux, initializeLinuxNetworkBridge, hasLinuxSandboxDependenciesSync, } from './linux-sandbox-utils.js';
7
+ import { wrapCommandWithSandboxMacOS, startMacOSSandboxLogMonitor, } from './macos-sandbox-utils.js';
8
+ import { getDefaultWritePaths, containsGlobChars, removeTrailingGlobSuffix, } from './sandbox-utils.js';
9
+ import { hasRipgrepSync } from '../utils/ripgrep.js';
10
+ import { SandboxViolationStore } from './sandbox-violation-store.js';
11
+ import { EOL } from 'node:os';
12
+ // ============================================================================
13
+ // Private Module State
14
+ // ============================================================================
15
+ let config;
16
+ let httpProxyServer;
17
+ let socksProxyServer;
18
+ let managerContext;
19
+ let initializationPromise;
20
+ let cleanupRegistered = false;
21
+ let logMonitorShutdown;
22
+ const sandboxViolationStore = new SandboxViolationStore();
23
+ let dependenciesCheckCache;
24
+ // ============================================================================
25
+ // Private Helper Functions (not exported)
26
+ // ============================================================================
27
+ function registerCleanup() {
28
+ if (cleanupRegistered) {
29
+ return;
30
+ }
31
+ const cleanupHandler = () => reset().catch(e => {
32
+ logForDebugging(`Cleanup failed in registerCleanup ${e}`, {
33
+ level: 'error',
34
+ });
35
+ });
36
+ process.once('exit', cleanupHandler);
37
+ process.once('SIGINT', cleanupHandler);
38
+ process.once('SIGTERM', cleanupHandler);
39
+ cleanupRegistered = true;
40
+ }
41
+ function matchesDomainPattern(hostname, pattern) {
42
+ // Support wildcard patterns like *.example.com
43
+ // This matches any subdomain but not the base domain itself
44
+ if (pattern.startsWith('*.')) {
45
+ const baseDomain = pattern.substring(2); // Remove '*.'
46
+ return hostname.toLowerCase().endsWith('.' + baseDomain.toLowerCase());
47
+ }
48
+ // Exact match for non-wildcard patterns
49
+ return hostname.toLowerCase() === pattern.toLowerCase();
50
+ }
51
+ function createNetworkVerdict(decision, reason, policyTag = 'network.allowlist') {
52
+ return {
53
+ decision,
54
+ reason,
55
+ policy_tag: policyTag,
56
+ };
57
+ }
58
+ async function filterNetworkRequest(port, host, sandboxAskCallback) {
59
+ if (!config) {
60
+ logForDebugging('No config available, denying network request');
61
+ return {
62
+ allowed: false,
63
+ verdict: createNetworkVerdict('deny', 'runtime_config_missing'),
64
+ };
65
+ }
66
+ // Check denied domains first
67
+ for (const deniedDomain of config.network.deniedDomains) {
68
+ if (matchesDomainPattern(host, deniedDomain)) {
69
+ logForDebugging(`Denied by config rule: ${host}:${port}`);
70
+ return {
71
+ allowed: false,
72
+ verdict: createNetworkVerdict('deny', `denylist:${deniedDomain}`),
73
+ };
74
+ }
75
+ }
76
+ // Check allowed domains
77
+ for (const allowedDomain of config.network.allowedDomains) {
78
+ if (matchesDomainPattern(host, allowedDomain)) {
79
+ logForDebugging(`Allowed by config rule: ${host}:${port}`);
80
+ return {
81
+ allowed: true,
82
+ verdict: createNetworkVerdict('allow', `allowlist:${allowedDomain}`),
83
+ };
84
+ }
85
+ }
86
+ // No matching rules - ask user or deny
87
+ if (!sandboxAskCallback) {
88
+ logForDebugging(`No matching config rule, denying: ${host}:${port}`);
89
+ return {
90
+ allowed: false,
91
+ verdict: createNetworkVerdict('deny', 'no_matching_rule'),
92
+ };
93
+ }
94
+ logForDebugging(`No matching config rule, asking user: ${host}:${port}`);
95
+ try {
96
+ const userAllowed = await sandboxAskCallback({ host, port });
97
+ if (userAllowed) {
98
+ logForDebugging(`User allowed: ${host}:${port}`);
99
+ return {
100
+ allowed: true,
101
+ verdict: createNetworkVerdict('allow', 'user_override'),
102
+ };
103
+ }
104
+ else {
105
+ logForDebugging(`User denied: ${host}:${port}`);
106
+ return {
107
+ allowed: false,
108
+ verdict: createNetworkVerdict('deny', 'user_override_denied'),
109
+ };
110
+ }
111
+ }
112
+ catch (error) {
113
+ logForDebugging(`Error in permission callback: ${error}`, {
114
+ level: 'error',
115
+ });
116
+ return {
117
+ allowed: false,
118
+ verdict: createNetworkVerdict('deny', 'callback_error'),
119
+ };
120
+ }
121
+ }
122
+ async function startHttpProxyServer(sandboxAskCallback) {
123
+ httpProxyServer = createHttpProxyServer({
124
+ filter: (port, host) => filterNetworkRequest(port, host, sandboxAskCallback),
125
+ });
126
+ return new Promise((resolve, reject) => {
127
+ if (!httpProxyServer) {
128
+ reject(new Error('HTTP proxy server undefined before listen'));
129
+ return;
130
+ }
131
+ const server = httpProxyServer;
132
+ server.once('error', reject);
133
+ server.once('listening', () => {
134
+ const address = server.address();
135
+ if (address && typeof address === 'object') {
136
+ server.unref();
137
+ logForDebugging(`HTTP proxy listening on localhost:${address.port}`);
138
+ resolve(address.port);
139
+ }
140
+ else {
141
+ reject(new Error('Failed to get proxy server address'));
142
+ }
143
+ });
144
+ server.listen(0, '127.0.0.1');
145
+ });
146
+ }
147
+ async function startSocksProxyServer(sandboxAskCallback) {
148
+ socksProxyServer = createSocksProxyServer({
149
+ filter: (port, host) => filterNetworkRequest(port, host, sandboxAskCallback),
150
+ });
151
+ return new Promise((resolve, reject) => {
152
+ if (!socksProxyServer) {
153
+ // This is mostly just for the typechecker
154
+ reject(new Error('SOCKS proxy server undefined before listen'));
155
+ return;
156
+ }
157
+ socksProxyServer
158
+ .listen(0, '127.0.0.1')
159
+ .then((port) => {
160
+ socksProxyServer?.unref();
161
+ resolve(port);
162
+ })
163
+ .catch(reject);
164
+ });
165
+ }
166
+ // ============================================================================
167
+ // Public Module Functions (will be exported via namespace)
168
+ // ============================================================================
169
+ async function initialize(runtimeConfig, sandboxAskCallback, enableLogMonitor = false) {
170
+ // Return if already initializing
171
+ if (initializationPromise) {
172
+ await initializationPromise;
173
+ return;
174
+ }
175
+ // Store config for use by other functions
176
+ config = runtimeConfig;
177
+ // Check dependencies now that we have config with ripgrep info
178
+ if (!checkDependencies()) {
179
+ const platform = getPlatform();
180
+ let errorMessage = 'Sandbox dependencies are not available on this system.';
181
+ if (platform === 'linux') {
182
+ errorMessage += ' Required: ripgrep (rg), bubblewrap (bwrap), and socat.';
183
+ }
184
+ else if (platform === 'macos') {
185
+ errorMessage += ' Required: ripgrep (rg).';
186
+ }
187
+ else {
188
+ errorMessage += ` Platform '${platform}' is not supported.`;
189
+ }
190
+ throw new Error(errorMessage);
191
+ }
192
+ // Start log monitor for macOS if enabled
193
+ if (enableLogMonitor && getPlatform() === 'macos') {
194
+ logMonitorShutdown = startMacOSSandboxLogMonitor(sandboxViolationStore.addViolation.bind(sandboxViolationStore), config.ignoreViolations);
195
+ logForDebugging('Started macOS sandbox log monitor');
196
+ }
197
+ // Register cleanup handlers first time
198
+ registerCleanup();
199
+ // Initialize network infrastructure
200
+ initializationPromise = (async () => {
201
+ try {
202
+ const shouldUseExternalHttpProxy = config.network.httpProxyPort !== undefined;
203
+ const shouldUseExternalSocksProxy = config.network.socksProxyPort !== undefined;
204
+ const [httpProxyPort, socksProxyPort] = await Promise.all([
205
+ shouldUseExternalHttpProxy
206
+ ? Promise.resolve(config.network.httpProxyPort)
207
+ : startHttpProxyServer(sandboxAskCallback),
208
+ shouldUseExternalSocksProxy
209
+ ? Promise.resolve(config.network.socksProxyPort)
210
+ : startSocksProxyServer(sandboxAskCallback),
211
+ ]);
212
+ if (shouldUseExternalHttpProxy) {
213
+ logForDebugging(`Using external HTTP proxy on port ${config.network.httpProxyPort}`);
214
+ }
215
+ if (shouldUseExternalSocksProxy) {
216
+ logForDebugging(`Using external SOCKS proxy on port ${config.network.socksProxyPort}`);
217
+ }
218
+ // Initialize platform-specific infrastructure
219
+ let linuxBridge;
220
+ if (getPlatform() === 'linux') {
221
+ linuxBridge = await initializeLinuxNetworkBridge(httpProxyPort, socksProxyPort);
222
+ }
223
+ const context = {
224
+ httpProxyPort,
225
+ socksProxyPort,
226
+ linuxBridge,
227
+ };
228
+ managerContext = context;
229
+ logForDebugging('Network infrastructure initialized');
230
+ return context;
231
+ }
232
+ catch (error) {
233
+ // Clear state on error so initialization can be retried
234
+ initializationPromise = undefined;
235
+ managerContext = undefined;
236
+ reset().catch(e => {
237
+ logForDebugging(`Cleanup failed in initializationPromise ${e}`, {
238
+ level: 'error',
239
+ });
240
+ });
241
+ throw error;
242
+ }
243
+ })();
244
+ await initializationPromise;
245
+ }
246
+ function isSupportedPlatform(platform) {
247
+ const supportedPlatforms = ['macos', 'linux'];
248
+ return supportedPlatforms.includes(platform);
249
+ }
250
+ function isSandboxingEnabled() {
251
+ // Sandboxing is enabled if config has been set (via initialize())
252
+ return config !== undefined;
253
+ }
254
+ /**
255
+ * Check if all sandbox dependencies are available for the current platform
256
+ * @returns true if all dependencies are available, false otherwise
257
+ */
258
+ function checkDependencies() {
259
+ // Return cached result if available
260
+ if (dependenciesCheckCache !== undefined) {
261
+ return dependenciesCheckCache;
262
+ }
263
+ function computeDependencies() {
264
+ const platform = getPlatform();
265
+ // Check platform support
266
+ if (!isSupportedPlatform(platform)) {
267
+ return false;
268
+ }
269
+ // Check ripgrep - only check 'rg' if no custom command is configured
270
+ // If custom command is provided, we trust it exists (will fail naturally if not)
271
+ const hasCustomRipgrep = config?.ripgrep?.command !== undefined;
272
+ if (!hasCustomRipgrep) {
273
+ // Only check for default 'rg' command
274
+ if (!hasRipgrepSync()) {
275
+ return false;
276
+ }
277
+ }
278
+ // Platform-specific dependency checks
279
+ if (platform === 'linux') {
280
+ const allowAllUnixSockets = config?.network?.allowAllUnixSockets ?? false;
281
+ return hasLinuxSandboxDependenciesSync(allowAllUnixSockets);
282
+ }
283
+ // macOS only needs ripgrep (already checked above)
284
+ return true;
285
+ }
286
+ dependenciesCheckCache = computeDependencies();
287
+ return dependenciesCheckCache;
288
+ }
289
+ function getFsReadConfig() {
290
+ if (!config) {
291
+ return { denyOnly: [] };
292
+ }
293
+ // Filter out glob patterns on Linux
294
+ const denyPaths = config.filesystem.denyRead
295
+ .map(path => removeTrailingGlobSuffix(path))
296
+ .filter(path => {
297
+ if (getPlatform() === 'linux' && containsGlobChars(path)) {
298
+ logForDebugging(`Skipping glob pattern on Linux: ${path}`);
299
+ return false;
300
+ }
301
+ return true;
302
+ });
303
+ return {
304
+ denyOnly: denyPaths,
305
+ };
306
+ }
307
+ function getFsWriteConfig() {
308
+ if (!config) {
309
+ return { allowOnly: getDefaultWritePaths(), denyWithinAllow: [] };
310
+ }
311
+ // Filter out glob patterns on Linux for allowWrite
312
+ const allowPaths = config.filesystem.allowWrite
313
+ .map(path => removeTrailingGlobSuffix(path))
314
+ .filter(path => {
315
+ if (getPlatform() === 'linux' && containsGlobChars(path)) {
316
+ logForDebugging(`Skipping glob pattern on Linux: ${path}`);
317
+ return false;
318
+ }
319
+ return true;
320
+ });
321
+ // Filter out glob patterns on Linux for denyWrite
322
+ const denyPaths = config.filesystem.denyWrite
323
+ .map(path => removeTrailingGlobSuffix(path))
324
+ .filter(path => {
325
+ if (getPlatform() === 'linux' && containsGlobChars(path)) {
326
+ logForDebugging(`Skipping glob pattern on Linux: ${path}`);
327
+ return false;
328
+ }
329
+ return true;
330
+ });
331
+ // Build allowOnly list: default paths + configured allow paths
332
+ const allowOnly = [...getDefaultWritePaths(), ...allowPaths];
333
+ return {
334
+ allowOnly,
335
+ denyWithinAllow: denyPaths,
336
+ };
337
+ }
338
+ function getNetworkRestrictionConfig() {
339
+ if (!config) {
340
+ return {};
341
+ }
342
+ const allowedHosts = config.network.allowedDomains;
343
+ const deniedHosts = config.network.deniedDomains;
344
+ return {
345
+ ...(allowedHosts.length > 0 && { allowedHosts }),
346
+ ...(deniedHosts.length > 0 && { deniedHosts }),
347
+ };
348
+ }
349
+ function getAllowUnixSockets() {
350
+ return config?.network?.allowUnixSockets;
351
+ }
352
+ function getAllowAllUnixSockets() {
353
+ return config?.network?.allowAllUnixSockets;
354
+ }
355
+ function getAllowLocalBinding() {
356
+ return config?.network?.allowLocalBinding;
357
+ }
358
+ function getIgnoreViolations() {
359
+ return config?.ignoreViolations;
360
+ }
361
+ function getEnableWeakerNestedSandbox() {
362
+ return config?.enableWeakerNestedSandbox;
363
+ }
364
+ function getRipgrepConfig() {
365
+ return config?.ripgrep ?? { command: 'rg' };
366
+ }
367
+ function getProxyPort() {
368
+ return managerContext?.httpProxyPort;
369
+ }
370
+ function getSocksProxyPort() {
371
+ return managerContext?.socksProxyPort;
372
+ }
373
+ function getLinuxHttpSocketPath() {
374
+ return managerContext?.linuxBridge?.httpSocketPath;
375
+ }
376
+ function getLinuxSocksSocketPath() {
377
+ return managerContext?.linuxBridge?.socksSocketPath;
378
+ }
379
+ /**
380
+ * Wait for network initialization to complete if already in progress
381
+ * Returns true if initialized successfully, false otherwise
382
+ */
383
+ async function waitForNetworkInitialization() {
384
+ if (!config) {
385
+ return false;
386
+ }
387
+ if (initializationPromise) {
388
+ try {
389
+ await initializationPromise;
390
+ return true;
391
+ }
392
+ catch {
393
+ return false;
394
+ }
395
+ }
396
+ return managerContext !== undefined;
397
+ }
398
+ async function wrapWithSandbox(command, binShell) {
399
+ // If no config, return command as-is
400
+ if (!config) {
401
+ return command;
402
+ }
403
+ const platform = getPlatform();
404
+ // Wait for network initialization
405
+ await waitForNetworkInitialization();
406
+ switch (platform) {
407
+ case 'macos':
408
+ return await wrapCommandWithSandboxMacOS({
409
+ command,
410
+ httpProxyPort: getProxyPort(),
411
+ socksProxyPort: getSocksProxyPort(),
412
+ readConfig: getFsReadConfig(),
413
+ writeConfig: getFsWriteConfig(),
414
+ needsNetworkRestriction: true,
415
+ allowUnixSockets: getAllowUnixSockets(),
416
+ allowAllUnixSockets: getAllowAllUnixSockets(),
417
+ allowLocalBinding: getAllowLocalBinding(),
418
+ ignoreViolations: getIgnoreViolations(),
419
+ binShell,
420
+ ripgrepConfig: getRipgrepConfig(),
421
+ });
422
+ case 'linux':
423
+ return wrapCommandWithSandboxLinux({
424
+ command,
425
+ hasNetworkRestrictions: true,
426
+ hasFilesystemRestrictions: true,
427
+ httpSocketPath: getLinuxHttpSocketPath(),
428
+ socksSocketPath: getLinuxSocksSocketPath(),
429
+ httpProxyPort: managerContext?.httpProxyPort,
430
+ socksProxyPort: managerContext?.socksProxyPort,
431
+ readConfig: getFsReadConfig(),
432
+ writeConfig: getFsWriteConfig(),
433
+ enableWeakerNestedSandbox: getEnableWeakerNestedSandbox(),
434
+ allowAllUnixSockets: getAllowAllUnixSockets(),
435
+ binShell,
436
+ ripgrepConfig: getRipgrepConfig(),
437
+ });
438
+ default:
439
+ // Unsupported platform - this should not happen since isSandboxingEnabled() checks platform support
440
+ throw new Error(`Sandbox configuration is not supported on platform: ${platform}`);
441
+ }
442
+ }
443
+ async function reset() {
444
+ // Stop log monitor
445
+ if (logMonitorShutdown) {
446
+ logMonitorShutdown();
447
+ logMonitorShutdown = undefined;
448
+ }
449
+ if (managerContext?.linuxBridge) {
450
+ const { httpSocketPath, socksSocketPath, httpBridgeProcess, socksBridgeProcess, } = managerContext.linuxBridge;
451
+ // Create array to wait for process exits
452
+ const exitPromises = [];
453
+ // Kill HTTP bridge and wait for it to exit
454
+ if (httpBridgeProcess.pid && !httpBridgeProcess.killed) {
455
+ try {
456
+ process.kill(httpBridgeProcess.pid, 'SIGTERM');
457
+ logForDebugging('Sent SIGTERM to HTTP bridge process');
458
+ // Wait for process to exit
459
+ exitPromises.push(new Promise(resolve => {
460
+ httpBridgeProcess.once('exit', () => {
461
+ logForDebugging('HTTP bridge process exited');
462
+ resolve();
463
+ });
464
+ // Timeout after 5 seconds
465
+ setTimeout(() => {
466
+ if (!httpBridgeProcess.killed) {
467
+ logForDebugging('HTTP bridge did not exit, forcing SIGKILL', {
468
+ level: 'warn',
469
+ });
470
+ try {
471
+ if (httpBridgeProcess.pid) {
472
+ process.kill(httpBridgeProcess.pid, 'SIGKILL');
473
+ }
474
+ }
475
+ catch {
476
+ // Process may have already exited
477
+ }
478
+ }
479
+ resolve();
480
+ }, 5000);
481
+ }));
482
+ }
483
+ catch (err) {
484
+ if (err.code !== 'ESRCH') {
485
+ logForDebugging(`Error killing HTTP bridge: ${err}`, {
486
+ level: 'error',
487
+ });
488
+ }
489
+ }
490
+ }
491
+ // Kill SOCKS bridge and wait for it to exit
492
+ if (socksBridgeProcess.pid && !socksBridgeProcess.killed) {
493
+ try {
494
+ process.kill(socksBridgeProcess.pid, 'SIGTERM');
495
+ logForDebugging('Sent SIGTERM to SOCKS bridge process');
496
+ // Wait for process to exit
497
+ exitPromises.push(new Promise(resolve => {
498
+ socksBridgeProcess.once('exit', () => {
499
+ logForDebugging('SOCKS bridge process exited');
500
+ resolve();
501
+ });
502
+ // Timeout after 5 seconds
503
+ setTimeout(() => {
504
+ if (!socksBridgeProcess.killed) {
505
+ logForDebugging('SOCKS bridge did not exit, forcing SIGKILL', {
506
+ level: 'warn',
507
+ });
508
+ try {
509
+ if (socksBridgeProcess.pid) {
510
+ process.kill(socksBridgeProcess.pid, 'SIGKILL');
511
+ }
512
+ }
513
+ catch {
514
+ // Process may have already exited
515
+ }
516
+ }
517
+ resolve();
518
+ }, 5000);
519
+ }));
520
+ }
521
+ catch (err) {
522
+ if (err.code !== 'ESRCH') {
523
+ logForDebugging(`Error killing SOCKS bridge: ${err}`, {
524
+ level: 'error',
525
+ });
526
+ }
527
+ }
528
+ }
529
+ // Wait for both processes to exit
530
+ await Promise.all(exitPromises);
531
+ // Clean up sockets
532
+ if (httpSocketPath) {
533
+ try {
534
+ fs.rmSync(httpSocketPath, { force: true });
535
+ logForDebugging('Cleaned up HTTP socket');
536
+ }
537
+ catch (err) {
538
+ logForDebugging(`HTTP socket cleanup error: ${err}`, {
539
+ level: 'error',
540
+ });
541
+ }
542
+ }
543
+ if (socksSocketPath) {
544
+ try {
545
+ fs.rmSync(socksSocketPath, { force: true });
546
+ logForDebugging('Cleaned up SOCKS socket');
547
+ }
548
+ catch (err) {
549
+ logForDebugging(`SOCKS socket cleanup error: ${err}`, {
550
+ level: 'error',
551
+ });
552
+ }
553
+ }
554
+ }
555
+ // Close servers in parallel
556
+ const closePromises = [];
557
+ if (httpProxyServer) {
558
+ const server = httpProxyServer; // Capture reference to avoid TypeScript error
559
+ const httpClose = new Promise(resolve => {
560
+ server.close(error => {
561
+ if (error && error.message !== 'Server is not running.') {
562
+ logForDebugging(`Error closing HTTP proxy server: ${error.message}`, {
563
+ level: 'error',
564
+ });
565
+ }
566
+ resolve();
567
+ });
568
+ });
569
+ closePromises.push(httpClose);
570
+ }
571
+ if (socksProxyServer) {
572
+ const socksClose = socksProxyServer.close().catch((error) => {
573
+ logForDebugging(`Error closing SOCKS proxy server: ${error.message}`, {
574
+ level: 'error',
575
+ });
576
+ });
577
+ closePromises.push(socksClose);
578
+ }
579
+ // Wait for all servers to close
580
+ await Promise.all(closePromises);
581
+ // Clear references
582
+ httpProxyServer = undefined;
583
+ socksProxyServer = undefined;
584
+ managerContext = undefined;
585
+ initializationPromise = undefined;
586
+ dependenciesCheckCache = undefined;
587
+ }
588
+ function getSandboxViolationStore() {
589
+ return sandboxViolationStore;
590
+ }
591
+ function annotateStderrWithSandboxFailures(command, stderr) {
592
+ if (!config) {
593
+ return stderr;
594
+ }
595
+ const violations = sandboxViolationStore.getViolationsForCommand(command);
596
+ if (violations.length === 0) {
597
+ return stderr;
598
+ }
599
+ let annotated = stderr;
600
+ annotated += EOL + '<sandbox_violations>' + EOL;
601
+ for (const violation of violations) {
602
+ annotated += violation.line + EOL;
603
+ }
604
+ annotated += '</sandbox_violations>';
605
+ return annotated;
606
+ }
607
+ /**
608
+ * Returns glob patterns from Edit/Read permission rules that are not
609
+ * fully supported on Linux. Returns empty array on macOS or when
610
+ * sandboxing is disabled.
611
+ *
612
+ * Patterns ending with /** are excluded since they work as subpaths.
613
+ */
614
+ function getLinuxGlobPatternWarnings() {
615
+ // Only warn on Linux
616
+ // macOS supports glob patterns via regex conversion
617
+ if (getPlatform() !== 'linux' || !config) {
618
+ return [];
619
+ }
620
+ const globPatterns = [];
621
+ // Check filesystem paths for glob patterns
622
+ const allPaths = [
623
+ ...config.filesystem.denyRead,
624
+ ...config.filesystem.allowWrite,
625
+ ...config.filesystem.denyWrite,
626
+ ];
627
+ for (const path of allPaths) {
628
+ // Strip trailing /** since that's just a subpath (directory and everything under it)
629
+ const pathWithoutTrailingStar = removeTrailingGlobSuffix(path);
630
+ // Only warn if there are still glob characters after removing trailing /**
631
+ if (containsGlobChars(pathWithoutTrailingStar)) {
632
+ globPatterns.push(path);
633
+ }
634
+ }
635
+ return globPatterns;
636
+ }
637
+ // ============================================================================
638
+ // Export as Namespace with Interface
639
+ // ============================================================================
640
+ /**
641
+ * Global sandbox manager that handles both network and filesystem restrictions
642
+ * for this session. This runs outside of the sandbox, on the host machine.
643
+ */
644
+ export const SandboxManager = {
645
+ initialize,
646
+ isSupportedPlatform,
647
+ isSandboxingEnabled,
648
+ checkDependencies,
649
+ getFsReadConfig,
650
+ getFsWriteConfig,
651
+ getNetworkRestrictionConfig,
652
+ getAllowUnixSockets,
653
+ getAllowLocalBinding,
654
+ getEnableWeakerNestedSandbox,
655
+ getProxyPort,
656
+ getSocksProxyPort,
657
+ getLinuxHttpSocketPath,
658
+ getLinuxSocksSocketPath,
659
+ waitForNetworkInitialization,
660
+ wrapWithSandbox,
661
+ reset,
662
+ getSandboxViolationStore,
663
+ annotateStderrWithSandboxFailures,
664
+ getLinuxGlobPatternWarnings,
665
+ };
666
+ //# sourceMappingURL=sandbox-manager.js.map