@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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +17 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +158 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/generate-seccomp-filter.d.ts +65 -0
- package/dist/sandbox/generate-seccomp-filter.d.ts.map +1 -0
- package/dist/sandbox/generate-seccomp-filter.js +185 -0
- package/dist/sandbox/generate-seccomp-filter.js.map +1 -0
- package/dist/sandbox/http-proxy.d.ts +14 -0
- package/dist/sandbox/http-proxy.d.ts.map +1 -0
- package/dist/sandbox/http-proxy.js +238 -0
- package/dist/sandbox/http-proxy.js.map +1 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts +121 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/linux-sandbox-utils.js +723 -0
- package/dist/sandbox/linux-sandbox-utils.js.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts +57 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.js +611 -0
- package/dist/sandbox/macos-sandbox-utils.js.map +1 -0
- package/dist/sandbox/observability.d.ts +56 -0
- package/dist/sandbox/observability.d.ts.map +1 -0
- package/dist/sandbox/observability.js +140 -0
- package/dist/sandbox/observability.js.map +1 -0
- package/dist/sandbox/sandbox-config.d.ts +277 -0
- package/dist/sandbox/sandbox-config.d.ts.map +1 -0
- package/dist/sandbox/sandbox-config.js +166 -0
- package/dist/sandbox/sandbox-config.js.map +1 -0
- package/dist/sandbox/sandbox-manager.d.ts +50 -0
- package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox/sandbox-manager.js +816 -0
- package/dist/sandbox/sandbox-manager.js.map +1 -0
- package/dist/sandbox/sandbox-schemas.d.ts +53 -0
- package/dist/sandbox/sandbox-schemas.d.ts.map +1 -0
- package/dist/sandbox/sandbox-schemas.js +3 -0
- package/dist/sandbox/sandbox-schemas.js.map +1 -0
- package/dist/sandbox/sandbox-utils.d.ts +83 -0
- package/dist/sandbox/sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/sandbox-utils.js +343 -0
- package/dist/sandbox/sandbox-utils.js.map +1 -0
- package/dist/sandbox/sandbox-violation-store.d.ts +19 -0
- package/dist/sandbox/sandbox-violation-store.d.ts.map +1 -0
- package/dist/sandbox/sandbox-violation-store.js +54 -0
- package/dist/sandbox/sandbox-violation-store.js.map +1 -0
- package/dist/sandbox/socks-proxy.d.ts +14 -0
- package/dist/sandbox/socks-proxy.d.ts.map +1 -0
- package/dist/sandbox/socks-proxy.js +109 -0
- package/dist/sandbox/socks-proxy.js.map +1 -0
- package/dist/utils/config-loader.d.ts +11 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +60 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/debug.d.ts +7 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +25 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/platform.d.ts +15 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +49 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/ripgrep.d.ts +20 -0
- package/dist/utils/ripgrep.d.ts.map +1 -0
- package/dist/utils/ripgrep.js +51 -0
- package/dist/utils/ripgrep.js.map +1 -0
- package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/arm64/unix-block.bpf +0 -0
- package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/x64/unix-block.bpf +0 -0
- package/dist/vendor/seccomp-src/apply-seccomp.c +98 -0
- package/dist/vendor/seccomp-src/seccomp-unix-block.c +97 -0
- package/package.json +90 -0
- package/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/vendor/seccomp/arm64/unix-block.bpf +0 -0
- package/vendor/seccomp/x64/apply-seccomp +0 -0
- package/vendor/seccomp/x64/unix-block.bpf +0 -0
- package/vendor/seccomp-src/apply-seccomp.c +98 -0
- package/vendor/seccomp-src/seccomp-unix-block.c +97 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import { createHttpProxyServer } from './http-proxy.js';
|
|
2
|
+
import { createSocksProxyServer } from './socks-proxy.js';
|
|
3
|
+
import { logForDebugging } from '../utils/debug.js';
|
|
4
|
+
import { cloneDeep } from 'lodash-es';
|
|
5
|
+
import { getPlatform, getWslVersion } from '../utils/platform.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import { wrapCommandWithSandboxLinux, initializeLinuxNetworkBridge, hasLinuxSandboxDependenciesSync, } from './linux-sandbox-utils.js';
|
|
8
|
+
import { wrapCommandWithSandboxMacOS, startMacOSSandboxLogMonitor, } from './macos-sandbox-utils.js';
|
|
9
|
+
import { getDefaultWritePaths, containsGlobChars, removeTrailingGlobSuffix, } from './sandbox-utils.js';
|
|
10
|
+
import { hasRipgrepSync } from '../utils/ripgrep.js';
|
|
11
|
+
import { SandboxViolationStore } from './sandbox-violation-store.js';
|
|
12
|
+
import { EOL } from 'node:os';
|
|
13
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
14
|
+
import { emitFsViolationEvent, getSandboxEventContextDepth, pushSandboxEventContext, } from './observability.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Private Module State
|
|
17
|
+
// ============================================================================
|
|
18
|
+
let config;
|
|
19
|
+
let httpProxyServer;
|
|
20
|
+
let socksProxyServer;
|
|
21
|
+
let managerContext;
|
|
22
|
+
let initializationPromise;
|
|
23
|
+
let cleanupRegistered = false;
|
|
24
|
+
let logMonitorShutdown;
|
|
25
|
+
const sandboxViolationStore = new SandboxViolationStore();
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Private Helper Functions (not exported)
|
|
28
|
+
// ============================================================================
|
|
29
|
+
function registerCleanup() {
|
|
30
|
+
if (cleanupRegistered) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const cleanupHandler = () => reset().catch(e => {
|
|
34
|
+
logForDebugging(`Cleanup failed in registerCleanup ${e}`, {
|
|
35
|
+
level: 'error',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
process.once('exit', cleanupHandler);
|
|
39
|
+
process.once('SIGINT', cleanupHandler);
|
|
40
|
+
process.once('SIGTERM', cleanupHandler);
|
|
41
|
+
cleanupRegistered = true;
|
|
42
|
+
}
|
|
43
|
+
function matchesDomainPattern(hostname, pattern) {
|
|
44
|
+
// Support wildcard patterns like *.example.com
|
|
45
|
+
// This matches any subdomain but not the base domain itself
|
|
46
|
+
if (pattern.startsWith('*.')) {
|
|
47
|
+
const baseDomain = pattern.substring(2); // Remove '*.'
|
|
48
|
+
return hostname.toLowerCase().endsWith('.' + baseDomain.toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
// Exact match for non-wildcard patterns
|
|
51
|
+
return hostname.toLowerCase() === pattern.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
async function filterNetworkRequest(port, host, sandboxAskCallback) {
|
|
54
|
+
if (!config) {
|
|
55
|
+
logForDebugging('No config available, denying network request');
|
|
56
|
+
return { allowed: false, reason: 'no-match' };
|
|
57
|
+
}
|
|
58
|
+
// Check denied domains first
|
|
59
|
+
for (const deniedDomain of config.network.deniedDomains) {
|
|
60
|
+
if (matchesDomainPattern(host, deniedDomain)) {
|
|
61
|
+
logForDebugging(`Denied by config rule: ${host}:${port}`);
|
|
62
|
+
return { allowed: false, reason: 'denylist' };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Check allowed domains
|
|
66
|
+
for (const allowedDomain of config.network.allowedDomains) {
|
|
67
|
+
if (matchesDomainPattern(host, allowedDomain)) {
|
|
68
|
+
logForDebugging(`Allowed by config rule: ${host}:${port}`);
|
|
69
|
+
return { allowed: true, reason: 'allowlist' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// No matching rules - ask user or deny
|
|
73
|
+
if (!sandboxAskCallback) {
|
|
74
|
+
logForDebugging(`No matching config rule, denying: ${host}:${port}`);
|
|
75
|
+
return { allowed: false, reason: 'no-match' };
|
|
76
|
+
}
|
|
77
|
+
logForDebugging(`No matching config rule, asking user: ${host}:${port}`);
|
|
78
|
+
try {
|
|
79
|
+
const userAllowed = await sandboxAskCallback({ host, port });
|
|
80
|
+
if (userAllowed) {
|
|
81
|
+
logForDebugging(`User allowed: ${host}:${port}`);
|
|
82
|
+
return { allowed: true, reason: 'no-match' };
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
logForDebugging(`User denied: ${host}:${port}`);
|
|
86
|
+
return { allowed: false, reason: 'no-match' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
logForDebugging(`Error in permission callback: ${error}`, {
|
|
91
|
+
level: 'error',
|
|
92
|
+
});
|
|
93
|
+
return { allowed: false, reason: 'no-match' };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the MITM proxy socket path for a given host, if configured.
|
|
98
|
+
* Returns the socket path if the host matches any MITM domain pattern,
|
|
99
|
+
* otherwise returns undefined.
|
|
100
|
+
*/
|
|
101
|
+
function getMitmSocketPath(host) {
|
|
102
|
+
if (!config?.network.mitmProxy) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const { socketPath, domains } = config.network.mitmProxy;
|
|
106
|
+
for (const pattern of domains) {
|
|
107
|
+
if (matchesDomainPattern(host, pattern)) {
|
|
108
|
+
logForDebugging(`Host ${host} matches MITM pattern ${pattern}`);
|
|
109
|
+
return socketPath;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
async function startHttpProxyServer(sandboxAskCallback) {
|
|
115
|
+
httpProxyServer = createHttpProxyServer({
|
|
116
|
+
filter: (port, host) => filterNetworkRequest(port, host, sandboxAskCallback),
|
|
117
|
+
getMitmSocketPath,
|
|
118
|
+
});
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
if (!httpProxyServer) {
|
|
121
|
+
reject(new Error('HTTP proxy server undefined before listen'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const server = httpProxyServer;
|
|
125
|
+
server.once('error', reject);
|
|
126
|
+
server.once('listening', () => {
|
|
127
|
+
const address = server.address();
|
|
128
|
+
if (address && typeof address === 'object') {
|
|
129
|
+
server.unref();
|
|
130
|
+
logForDebugging(`HTTP proxy listening on localhost:${address.port}`);
|
|
131
|
+
resolve(address.port);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
reject(new Error('Failed to get proxy server address'));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
server.listen(0, '127.0.0.1');
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function startSocksProxyServer(sandboxAskCallback) {
|
|
141
|
+
socksProxyServer = createSocksProxyServer({
|
|
142
|
+
filter: (port, host) => filterNetworkRequest(port, host, sandboxAskCallback),
|
|
143
|
+
});
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
if (!socksProxyServer) {
|
|
146
|
+
// This is mostly just for the typechecker
|
|
147
|
+
reject(new Error('SOCKS proxy server undefined before listen'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
socksProxyServer
|
|
151
|
+
.listen(0, '127.0.0.1')
|
|
152
|
+
.then((port) => {
|
|
153
|
+
socksProxyServer?.unref();
|
|
154
|
+
resolve(port);
|
|
155
|
+
})
|
|
156
|
+
.catch(reject);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Public Module Functions (will be exported via namespace)
|
|
161
|
+
// ============================================================================
|
|
162
|
+
async function initialize(runtimeConfig, sandboxAskCallback, enableLogMonitor = false) {
|
|
163
|
+
// Return if already initializing
|
|
164
|
+
if (initializationPromise) {
|
|
165
|
+
await initializationPromise;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Store config for use by other functions
|
|
169
|
+
config = runtimeConfig;
|
|
170
|
+
// Check dependencies now that we have config with ripgrep info
|
|
171
|
+
if (!checkDependencies()) {
|
|
172
|
+
const platform = getPlatform();
|
|
173
|
+
let errorMessage = 'Sandbox dependencies are not available on this system.';
|
|
174
|
+
if (platform === 'linux') {
|
|
175
|
+
errorMessage += ' Required: ripgrep (rg), bubblewrap (bwrap), and socat.';
|
|
176
|
+
}
|
|
177
|
+
else if (platform === 'macos') {
|
|
178
|
+
errorMessage += ' Required: ripgrep (rg).';
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
errorMessage += ` Platform '${platform}' is not supported.`;
|
|
182
|
+
}
|
|
183
|
+
throw new Error(errorMessage);
|
|
184
|
+
}
|
|
185
|
+
// Start log monitor for macOS if enabled
|
|
186
|
+
if (enableLogMonitor && getPlatform() === 'macos') {
|
|
187
|
+
logMonitorShutdown = startMacOSSandboxLogMonitor(violation => {
|
|
188
|
+
sandboxViolationStore.addViolation(violation);
|
|
189
|
+
// Emit structured violation events for observability consumers.
|
|
190
|
+
// Only emit when a consumer has registered an event handler.
|
|
191
|
+
const parsed = parseFsViolationLine(violation.line);
|
|
192
|
+
if (parsed) {
|
|
193
|
+
emitFsViolationEvent(parsed);
|
|
194
|
+
}
|
|
195
|
+
}, config.ignoreViolations);
|
|
196
|
+
logForDebugging('Started macOS sandbox log monitor');
|
|
197
|
+
}
|
|
198
|
+
// Register cleanup handlers first time
|
|
199
|
+
registerCleanup();
|
|
200
|
+
// Initialize network infrastructure
|
|
201
|
+
initializationPromise = (async () => {
|
|
202
|
+
try {
|
|
203
|
+
// Conditionally start proxy servers based on config
|
|
204
|
+
let httpProxyPort;
|
|
205
|
+
if (config.network.httpProxyPort !== undefined) {
|
|
206
|
+
// Use external HTTP proxy (don't start a server)
|
|
207
|
+
httpProxyPort = config.network.httpProxyPort;
|
|
208
|
+
logForDebugging(`Using external HTTP proxy on port ${httpProxyPort}`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Start local HTTP proxy
|
|
212
|
+
httpProxyPort = await startHttpProxyServer(sandboxAskCallback);
|
|
213
|
+
}
|
|
214
|
+
let socksProxyPort;
|
|
215
|
+
if (config.network.socksProxyPort !== undefined) {
|
|
216
|
+
// Use external SOCKS proxy (don't start a server)
|
|
217
|
+
socksProxyPort = config.network.socksProxyPort;
|
|
218
|
+
logForDebugging(`Using external SOCKS proxy on port ${socksProxyPort}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Start local SOCKS proxy
|
|
222
|
+
socksProxyPort = await startSocksProxyServer(sandboxAskCallback);
|
|
223
|
+
}
|
|
224
|
+
// Initialize platform-specific infrastructure
|
|
225
|
+
let linuxBridge;
|
|
226
|
+
if (getPlatform() === 'linux') {
|
|
227
|
+
linuxBridge = await initializeLinuxNetworkBridge(httpProxyPort, socksProxyPort);
|
|
228
|
+
}
|
|
229
|
+
const context = {
|
|
230
|
+
httpProxyPort,
|
|
231
|
+
socksProxyPort,
|
|
232
|
+
linuxBridge,
|
|
233
|
+
};
|
|
234
|
+
managerContext = context;
|
|
235
|
+
logForDebugging('Network infrastructure initialized');
|
|
236
|
+
return context;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// Clear state on error so initialization can be retried
|
|
240
|
+
initializationPromise = undefined;
|
|
241
|
+
managerContext = undefined;
|
|
242
|
+
reset().catch(e => {
|
|
243
|
+
logForDebugging(`Cleanup failed in initializationPromise ${e}`, {
|
|
244
|
+
level: 'error',
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
})();
|
|
250
|
+
await initializationPromise;
|
|
251
|
+
}
|
|
252
|
+
function isSupportedPlatform() {
|
|
253
|
+
const platform = getPlatform();
|
|
254
|
+
if (platform === 'linux') {
|
|
255
|
+
// WSL1 doesn't support bubblewrap
|
|
256
|
+
return getWslVersion() !== '1';
|
|
257
|
+
}
|
|
258
|
+
return platform === 'macos';
|
|
259
|
+
}
|
|
260
|
+
function isSandboxingEnabled() {
|
|
261
|
+
// Sandboxing is enabled if config has been set (via initialize())
|
|
262
|
+
return config !== undefined;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Check if all sandbox dependencies are available for the current platform
|
|
266
|
+
* @param ripgrepConfig - Optional ripgrep configuration to check. If not provided, uses config from initialization or defaults to 'rg'
|
|
267
|
+
* @returns true if all dependencies are available, false otherwise
|
|
268
|
+
*/
|
|
269
|
+
function checkDependencies(ripgrepConfig) {
|
|
270
|
+
// Check platform support
|
|
271
|
+
if (!isSupportedPlatform()) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
// Determine which ripgrep to check:
|
|
275
|
+
// 1. Parameter takes precedence
|
|
276
|
+
// 2. Then config from initialization
|
|
277
|
+
// 3. Finally default to 'rg'
|
|
278
|
+
const rgToCheck = ripgrepConfig ?? config?.ripgrep;
|
|
279
|
+
// Check ripgrep - only check 'rg' if no custom command is configured
|
|
280
|
+
// If custom command is provided, we trust it exists (will fail naturally if not)
|
|
281
|
+
const hasCustomRipgrep = rgToCheck?.command !== undefined;
|
|
282
|
+
if (!hasCustomRipgrep) {
|
|
283
|
+
// Only check for default 'rg' command
|
|
284
|
+
if (!hasRipgrepSync()) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Platform-specific dependency checks
|
|
289
|
+
const platform = getPlatform();
|
|
290
|
+
if (platform === 'linux') {
|
|
291
|
+
const allowAllUnixSockets = config?.network?.allowAllUnixSockets ?? false;
|
|
292
|
+
const seccompConfig = config?.seccomp;
|
|
293
|
+
return hasLinuxSandboxDependenciesSync(allowAllUnixSockets, seccompConfig);
|
|
294
|
+
}
|
|
295
|
+
// macOS only needs ripgrep (already checked above)
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
function getFsReadConfig() {
|
|
299
|
+
if (!config) {
|
|
300
|
+
return { denyOnly: [] };
|
|
301
|
+
}
|
|
302
|
+
// Filter out glob patterns on Linux/WSL (bubblewrap doesn't support globs)
|
|
303
|
+
const denyPaths = config.filesystem.denyRead
|
|
304
|
+
.map(path => removeTrailingGlobSuffix(path))
|
|
305
|
+
.filter(path => {
|
|
306
|
+
if (getPlatform() === 'linux' && containsGlobChars(path)) {
|
|
307
|
+
logForDebugging(`Skipping glob pattern on Linux/WSL: ${path}`);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
denyOnly: denyPaths,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function getFsWriteConfig() {
|
|
317
|
+
if (!config) {
|
|
318
|
+
return { allowOnly: getDefaultWritePaths(), denyWithinAllow: [] };
|
|
319
|
+
}
|
|
320
|
+
// Filter out glob patterns on Linux/WSL for allowWrite (bubblewrap doesn't support globs)
|
|
321
|
+
const allowPaths = config.filesystem.allowWrite
|
|
322
|
+
.map(path => removeTrailingGlobSuffix(path))
|
|
323
|
+
.filter(path => {
|
|
324
|
+
if (getPlatform() === 'linux' && containsGlobChars(path)) {
|
|
325
|
+
logForDebugging(`Skipping glob pattern on Linux/WSL: ${path}`);
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
});
|
|
330
|
+
// Filter out glob patterns on Linux/WSL for denyWrite (bubblewrap doesn't support globs)
|
|
331
|
+
const denyPaths = config.filesystem.denyWrite
|
|
332
|
+
.map(path => removeTrailingGlobSuffix(path))
|
|
333
|
+
.filter(path => {
|
|
334
|
+
if (getPlatform() === 'linux' && containsGlobChars(path)) {
|
|
335
|
+
logForDebugging(`Skipping glob pattern on Linux/WSL: ${path}`);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
});
|
|
340
|
+
// Build allowOnly list: default paths + configured allow paths
|
|
341
|
+
const allowOnly = [...getDefaultWritePaths(), ...allowPaths];
|
|
342
|
+
return {
|
|
343
|
+
allowOnly,
|
|
344
|
+
denyWithinAllow: denyPaths,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function getNetworkRestrictionConfig() {
|
|
348
|
+
if (!config) {
|
|
349
|
+
return {};
|
|
350
|
+
}
|
|
351
|
+
const allowedHosts = config.network.allowedDomains;
|
|
352
|
+
const deniedHosts = config.network.deniedDomains;
|
|
353
|
+
return {
|
|
354
|
+
...(allowedHosts.length > 0 && { allowedHosts }),
|
|
355
|
+
...(deniedHosts.length > 0 && { deniedHosts }),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function getAllowUnixSockets() {
|
|
359
|
+
return config?.network?.allowUnixSockets;
|
|
360
|
+
}
|
|
361
|
+
function getAllowAllUnixSockets() {
|
|
362
|
+
return config?.network?.allowAllUnixSockets;
|
|
363
|
+
}
|
|
364
|
+
function getAllowLocalBinding() {
|
|
365
|
+
return config?.network?.allowLocalBinding;
|
|
366
|
+
}
|
|
367
|
+
function getIgnoreViolations() {
|
|
368
|
+
return config?.ignoreViolations;
|
|
369
|
+
}
|
|
370
|
+
function getEnableWeakerNestedSandbox() {
|
|
371
|
+
return config?.enableWeakerNestedSandbox;
|
|
372
|
+
}
|
|
373
|
+
function getRipgrepConfig() {
|
|
374
|
+
return config?.ripgrep ?? { command: 'rg' };
|
|
375
|
+
}
|
|
376
|
+
function getMandatoryDenySearchDepth() {
|
|
377
|
+
return config?.mandatoryDenySearchDepth ?? 3;
|
|
378
|
+
}
|
|
379
|
+
function getAllowGitConfig() {
|
|
380
|
+
return config?.filesystem?.allowGitConfig ?? false;
|
|
381
|
+
}
|
|
382
|
+
function getSeccompConfig() {
|
|
383
|
+
return config?.seccomp;
|
|
384
|
+
}
|
|
385
|
+
function getProxyPort() {
|
|
386
|
+
return managerContext?.httpProxyPort;
|
|
387
|
+
}
|
|
388
|
+
function getSocksProxyPort() {
|
|
389
|
+
return managerContext?.socksProxyPort;
|
|
390
|
+
}
|
|
391
|
+
function getLinuxHttpSocketPath() {
|
|
392
|
+
return managerContext?.linuxBridge?.httpSocketPath;
|
|
393
|
+
}
|
|
394
|
+
function getLinuxSocksSocketPath() {
|
|
395
|
+
return managerContext?.linuxBridge?.socksSocketPath;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Wait for network initialization to complete if already in progress
|
|
399
|
+
* Returns true if initialized successfully, false otherwise
|
|
400
|
+
*/
|
|
401
|
+
async function waitForNetworkInitialization() {
|
|
402
|
+
if (!config) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
if (initializationPromise) {
|
|
406
|
+
try {
|
|
407
|
+
await initializationPromise;
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return managerContext !== undefined;
|
|
415
|
+
}
|
|
416
|
+
async function wrapWithSandbox(command, binShell, customConfig, abortSignal) {
|
|
417
|
+
const platform = getPlatform();
|
|
418
|
+
// Get configs - use custom if provided, otherwise fall back to main config
|
|
419
|
+
// If neither exists, defaults to empty arrays (most restrictive)
|
|
420
|
+
// Always include default system write paths (like /dev/null, /tmp/claude)
|
|
421
|
+
const userAllowWrite = customConfig?.filesystem?.allowWrite ?? config?.filesystem.allowWrite ?? [];
|
|
422
|
+
const writeConfig = {
|
|
423
|
+
allowOnly: [...getDefaultWritePaths(), ...userAllowWrite],
|
|
424
|
+
denyWithinAllow: customConfig?.filesystem?.denyWrite ?? config?.filesystem.denyWrite ?? [],
|
|
425
|
+
};
|
|
426
|
+
const readConfig = {
|
|
427
|
+
denyOnly: customConfig?.filesystem?.denyRead ?? config?.filesystem.denyRead ?? [],
|
|
428
|
+
};
|
|
429
|
+
// Check if network config is specified - this determines if we need network restrictions
|
|
430
|
+
// Network restriction is needed when:
|
|
431
|
+
// 1. customConfig has network.allowedDomains defined (even if empty array = block all)
|
|
432
|
+
// 2. OR config has network.allowedDomains defined (even if empty array = block all)
|
|
433
|
+
// An empty allowedDomains array means "no domains allowed" = block all network access
|
|
434
|
+
const hasNetworkConfig = customConfig?.network?.allowedDomains !== undefined ||
|
|
435
|
+
config?.network?.allowedDomains !== undefined;
|
|
436
|
+
// Network RESTRICTION is needed whenever network config is specified
|
|
437
|
+
// This includes empty allowedDomains which means "block all network"
|
|
438
|
+
const needsNetworkRestriction = hasNetworkConfig;
|
|
439
|
+
// Network PROXY is needed whenever network config is specified
|
|
440
|
+
// Even with empty allowedDomains, we route through proxy so that:
|
|
441
|
+
// 1. updateConfig() can enable network access for already-running processes
|
|
442
|
+
// 2. The proxy blocks all requests when allowlist is empty
|
|
443
|
+
const needsNetworkProxy = hasNetworkConfig;
|
|
444
|
+
// Wait for network initialization only if proxy is actually needed
|
|
445
|
+
if (needsNetworkProxy) {
|
|
446
|
+
await waitForNetworkInitialization();
|
|
447
|
+
}
|
|
448
|
+
// Check custom config to allow pseudo-terminal (can be applied dynamically)
|
|
449
|
+
const allowPty = customConfig?.allowPty ?? config?.allowPty;
|
|
450
|
+
switch (platform) {
|
|
451
|
+
case 'macos':
|
|
452
|
+
// macOS sandbox profile supports glob patterns directly, no ripgrep needed
|
|
453
|
+
return wrapCommandWithSandboxMacOS({
|
|
454
|
+
command,
|
|
455
|
+
needsNetworkRestriction,
|
|
456
|
+
// Only pass proxy ports if proxy is running (when there are domains to filter)
|
|
457
|
+
httpProxyPort: needsNetworkProxy ? getProxyPort() : undefined,
|
|
458
|
+
socksProxyPort: needsNetworkProxy ? getSocksProxyPort() : undefined,
|
|
459
|
+
readConfig,
|
|
460
|
+
writeConfig,
|
|
461
|
+
allowUnixSockets: getAllowUnixSockets(),
|
|
462
|
+
allowAllUnixSockets: getAllowAllUnixSockets(),
|
|
463
|
+
allowLocalBinding: getAllowLocalBinding(),
|
|
464
|
+
ignoreViolations: getIgnoreViolations(),
|
|
465
|
+
allowPty,
|
|
466
|
+
allowGitConfig: getAllowGitConfig(),
|
|
467
|
+
binShell,
|
|
468
|
+
});
|
|
469
|
+
case 'linux':
|
|
470
|
+
return wrapCommandWithSandboxLinux({
|
|
471
|
+
command,
|
|
472
|
+
needsNetworkRestriction,
|
|
473
|
+
// Only pass socket paths if proxy is running (when there are domains to filter)
|
|
474
|
+
httpSocketPath: needsNetworkProxy
|
|
475
|
+
? getLinuxHttpSocketPath()
|
|
476
|
+
: undefined,
|
|
477
|
+
socksSocketPath: needsNetworkProxy
|
|
478
|
+
? getLinuxSocksSocketPath()
|
|
479
|
+
: undefined,
|
|
480
|
+
httpProxyPort: needsNetworkProxy
|
|
481
|
+
? managerContext?.httpProxyPort
|
|
482
|
+
: undefined,
|
|
483
|
+
socksProxyPort: needsNetworkProxy
|
|
484
|
+
? managerContext?.socksProxyPort
|
|
485
|
+
: undefined,
|
|
486
|
+
readConfig,
|
|
487
|
+
writeConfig,
|
|
488
|
+
enableWeakerNestedSandbox: getEnableWeakerNestedSandbox(),
|
|
489
|
+
allowAllUnixSockets: getAllowAllUnixSockets(),
|
|
490
|
+
binShell,
|
|
491
|
+
ripgrepConfig: getRipgrepConfig(),
|
|
492
|
+
mandatoryDenySearchDepth: getMandatoryDenySearchDepth(),
|
|
493
|
+
allowGitConfig: getAllowGitConfig(),
|
|
494
|
+
seccompConfig: getSeccompConfig(),
|
|
495
|
+
abortSignal,
|
|
496
|
+
});
|
|
497
|
+
default:
|
|
498
|
+
// Unsupported platform - this should not happen since isSandboxingEnabled() checks platform support
|
|
499
|
+
throw new Error(`Sandbox configuration is not supported on platform: ${platform}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Get the current sandbox configuration
|
|
504
|
+
* @returns The current configuration, or undefined if not initialized
|
|
505
|
+
*/
|
|
506
|
+
function getConfig() {
|
|
507
|
+
return config;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Update the sandbox configuration
|
|
511
|
+
* @param newConfig - The new configuration to use
|
|
512
|
+
*/
|
|
513
|
+
function updateConfig(newConfig) {
|
|
514
|
+
// Deep clone the config to avoid mutations
|
|
515
|
+
config = cloneDeep(newConfig);
|
|
516
|
+
logForDebugging('Sandbox configuration updated');
|
|
517
|
+
}
|
|
518
|
+
async function reset() {
|
|
519
|
+
// Stop log monitor
|
|
520
|
+
if (logMonitorShutdown) {
|
|
521
|
+
logMonitorShutdown();
|
|
522
|
+
logMonitorShutdown = undefined;
|
|
523
|
+
}
|
|
524
|
+
if (managerContext?.linuxBridge) {
|
|
525
|
+
const { httpSocketPath, socksSocketPath, httpBridgeProcess, socksBridgeProcess, } = managerContext.linuxBridge;
|
|
526
|
+
// Create array to wait for process exits
|
|
527
|
+
const exitPromises = [];
|
|
528
|
+
// Kill HTTP bridge and wait for it to exit
|
|
529
|
+
if (httpBridgeProcess.pid && !httpBridgeProcess.killed) {
|
|
530
|
+
try {
|
|
531
|
+
process.kill(httpBridgeProcess.pid, 'SIGTERM');
|
|
532
|
+
logForDebugging('Sent SIGTERM to HTTP bridge process');
|
|
533
|
+
// Wait for process to exit
|
|
534
|
+
exitPromises.push(new Promise(resolve => {
|
|
535
|
+
httpBridgeProcess.once('exit', () => {
|
|
536
|
+
logForDebugging('HTTP bridge process exited');
|
|
537
|
+
resolve();
|
|
538
|
+
});
|
|
539
|
+
// Timeout after 5 seconds
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
if (!httpBridgeProcess.killed) {
|
|
542
|
+
logForDebugging('HTTP bridge did not exit, forcing SIGKILL', {
|
|
543
|
+
level: 'warn',
|
|
544
|
+
});
|
|
545
|
+
try {
|
|
546
|
+
if (httpBridgeProcess.pid) {
|
|
547
|
+
process.kill(httpBridgeProcess.pid, 'SIGKILL');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
// Process may have already exited
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
resolve();
|
|
555
|
+
}, 5000);
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (err.code !== 'ESRCH') {
|
|
560
|
+
logForDebugging(`Error killing HTTP bridge: ${err}`, {
|
|
561
|
+
level: 'error',
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Kill SOCKS bridge and wait for it to exit
|
|
567
|
+
if (socksBridgeProcess.pid && !socksBridgeProcess.killed) {
|
|
568
|
+
try {
|
|
569
|
+
process.kill(socksBridgeProcess.pid, 'SIGTERM');
|
|
570
|
+
logForDebugging('Sent SIGTERM to SOCKS bridge process');
|
|
571
|
+
// Wait for process to exit
|
|
572
|
+
exitPromises.push(new Promise(resolve => {
|
|
573
|
+
socksBridgeProcess.once('exit', () => {
|
|
574
|
+
logForDebugging('SOCKS bridge process exited');
|
|
575
|
+
resolve();
|
|
576
|
+
});
|
|
577
|
+
// Timeout after 5 seconds
|
|
578
|
+
setTimeout(() => {
|
|
579
|
+
if (!socksBridgeProcess.killed) {
|
|
580
|
+
logForDebugging('SOCKS bridge did not exit, forcing SIGKILL', {
|
|
581
|
+
level: 'warn',
|
|
582
|
+
});
|
|
583
|
+
try {
|
|
584
|
+
if (socksBridgeProcess.pid) {
|
|
585
|
+
process.kill(socksBridgeProcess.pid, 'SIGKILL');
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
// Process may have already exited
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
resolve();
|
|
593
|
+
}, 5000);
|
|
594
|
+
}));
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
if (err.code !== 'ESRCH') {
|
|
598
|
+
logForDebugging(`Error killing SOCKS bridge: ${err}`, {
|
|
599
|
+
level: 'error',
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Wait for both processes to exit
|
|
605
|
+
await Promise.all(exitPromises);
|
|
606
|
+
// Clean up sockets
|
|
607
|
+
if (httpSocketPath) {
|
|
608
|
+
try {
|
|
609
|
+
fs.rmSync(httpSocketPath, { force: true });
|
|
610
|
+
logForDebugging('Cleaned up HTTP socket');
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
logForDebugging(`HTTP socket cleanup error: ${err}`, {
|
|
614
|
+
level: 'error',
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (socksSocketPath) {
|
|
619
|
+
try {
|
|
620
|
+
fs.rmSync(socksSocketPath, { force: true });
|
|
621
|
+
logForDebugging('Cleaned up SOCKS socket');
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
logForDebugging(`SOCKS socket cleanup error: ${err}`, {
|
|
625
|
+
level: 'error',
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// Close servers in parallel (only if they exist, i.e., were started by us)
|
|
631
|
+
const closePromises = [];
|
|
632
|
+
if (httpProxyServer) {
|
|
633
|
+
const server = httpProxyServer; // Capture reference to avoid TypeScript error
|
|
634
|
+
const httpClose = new Promise(resolve => {
|
|
635
|
+
server.close(error => {
|
|
636
|
+
if (error && error.message !== 'Server is not running.') {
|
|
637
|
+
logForDebugging(`Error closing HTTP proxy server: ${error.message}`, {
|
|
638
|
+
level: 'error',
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
resolve();
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
closePromises.push(httpClose);
|
|
645
|
+
}
|
|
646
|
+
if (socksProxyServer) {
|
|
647
|
+
const socksClose = socksProxyServer.close().catch((error) => {
|
|
648
|
+
logForDebugging(`Error closing SOCKS proxy server: ${error.message}`, {
|
|
649
|
+
level: 'error',
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
closePromises.push(socksClose);
|
|
653
|
+
}
|
|
654
|
+
// Wait for all servers to close
|
|
655
|
+
await Promise.all(closePromises);
|
|
656
|
+
// Clear references
|
|
657
|
+
httpProxyServer = undefined;
|
|
658
|
+
socksProxyServer = undefined;
|
|
659
|
+
managerContext = undefined;
|
|
660
|
+
initializationPromise = undefined;
|
|
661
|
+
}
|
|
662
|
+
async function spawn(command, options = {}) {
|
|
663
|
+
const { correlationId, events, binShell, customConfig, abortSignal, ...spawnOptions } = options;
|
|
664
|
+
// Prefer explicit abortSignal, but fall back to Node's spawn AbortSignal.
|
|
665
|
+
const wrapAbortSignal = abortSignal ?? spawnOptions.signal;
|
|
666
|
+
const sandboxedCommand = await wrapWithSandbox(command, binShell, customConfig, wrapAbortSignal);
|
|
667
|
+
const shouldEmitEvents = !!events?.onEvent;
|
|
668
|
+
if (shouldEmitEvents && getSandboxEventContextDepth() > 0) {
|
|
669
|
+
throw new Error('SandboxManager.spawn does not support concurrent spawns when events are enabled.');
|
|
670
|
+
}
|
|
671
|
+
const disposeContext = shouldEmitEvents
|
|
672
|
+
? pushSandboxEventContext({ correlationId, onEvent: events?.onEvent })
|
|
673
|
+
: () => { };
|
|
674
|
+
let cleanedUp = false;
|
|
675
|
+
const cleanup = () => {
|
|
676
|
+
if (cleanedUp)
|
|
677
|
+
return;
|
|
678
|
+
cleanedUp = true;
|
|
679
|
+
disposeContext();
|
|
680
|
+
};
|
|
681
|
+
try {
|
|
682
|
+
const child = nodeSpawn(sandboxedCommand, {
|
|
683
|
+
...spawnOptions,
|
|
684
|
+
shell: spawnOptions.shell ?? true,
|
|
685
|
+
});
|
|
686
|
+
child.once('exit', cleanup);
|
|
687
|
+
child.once('error', cleanup);
|
|
688
|
+
return child;
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
cleanup();
|
|
692
|
+
throw err;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function getSandboxViolationStore() {
|
|
696
|
+
return sandboxViolationStore;
|
|
697
|
+
}
|
|
698
|
+
function parseFsViolationLine(line) {
|
|
699
|
+
// Example: "bash(123) deny file-read-data /etc/passwd"
|
|
700
|
+
// Example: "bash(123) deny file-write-data /private/tmp/foo"
|
|
701
|
+
const match = line.match(/\bdeny\s+([^\s]+)\s+(.+?)\s*$/);
|
|
702
|
+
if (!match?.[1] || !match[2])
|
|
703
|
+
return;
|
|
704
|
+
const operationToken = match[1].toLowerCase();
|
|
705
|
+
let rawPath = match[2];
|
|
706
|
+
const tagIndex = rawPath.indexOf('CMD64_');
|
|
707
|
+
if (tagIndex !== -1) {
|
|
708
|
+
rawPath = rawPath.slice(0, tagIndex).trimEnd();
|
|
709
|
+
}
|
|
710
|
+
const isRead = operationToken.startsWith('file-read');
|
|
711
|
+
const isWrite = operationToken.startsWith('file-write');
|
|
712
|
+
if (!isRead && !isWrite)
|
|
713
|
+
return;
|
|
714
|
+
const operation = isRead ? 'read' : 'write';
|
|
715
|
+
// Heuristic: if write restrictions are enabled, distinguish denies due to
|
|
716
|
+
// explicit denyWrite vs default-deny (no allowWrite match).
|
|
717
|
+
if (operation === 'read') {
|
|
718
|
+
return { path: rawPath, operation, reason: 'denyRead' };
|
|
719
|
+
}
|
|
720
|
+
// If sandboxing isn't enabled or no config, default to no-allowWrite.
|
|
721
|
+
if (!config) {
|
|
722
|
+
return { path: rawPath, operation, reason: 'no-allowWrite' };
|
|
723
|
+
}
|
|
724
|
+
const writeConfig = getFsWriteConfig();
|
|
725
|
+
const normalizedPath = rawPath;
|
|
726
|
+
const withinAnyAllow = writeConfig.allowOnly.some(allowPath => {
|
|
727
|
+
// allowPath values may include globs in config, but getFsWriteConfig() has
|
|
728
|
+
// already normalized them for platform support.
|
|
729
|
+
return (normalizedPath === allowPath ||
|
|
730
|
+
normalizedPath.startsWith(allowPath.endsWith('/') ? allowPath : allowPath + '/'));
|
|
731
|
+
});
|
|
732
|
+
// If the write was within an allowed subtree but still denied, it must have
|
|
733
|
+
// hit an explicit deny rule (denyWrite or mandatory denies).
|
|
734
|
+
const reason = withinAnyAllow ? 'denyWrite' : 'no-allowWrite';
|
|
735
|
+
return { path: rawPath, operation, reason };
|
|
736
|
+
}
|
|
737
|
+
function annotateStderrWithSandboxFailures(command, stderr) {
|
|
738
|
+
if (!config) {
|
|
739
|
+
return stderr;
|
|
740
|
+
}
|
|
741
|
+
const violations = sandboxViolationStore.getViolationsForCommand(command);
|
|
742
|
+
if (violations.length === 0) {
|
|
743
|
+
return stderr;
|
|
744
|
+
}
|
|
745
|
+
let annotated = stderr;
|
|
746
|
+
annotated += EOL + '<sandbox_violations>' + EOL;
|
|
747
|
+
for (const violation of violations) {
|
|
748
|
+
annotated += violation.line + EOL;
|
|
749
|
+
}
|
|
750
|
+
annotated += '</sandbox_violations>';
|
|
751
|
+
return annotated;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Returns glob patterns from Edit/Read permission rules that are not
|
|
755
|
+
* fully supported on Linux. Returns empty array on macOS or when
|
|
756
|
+
* sandboxing is disabled.
|
|
757
|
+
*
|
|
758
|
+
* Patterns ending with /** are excluded since they work as subpaths.
|
|
759
|
+
*/
|
|
760
|
+
function getLinuxGlobPatternWarnings() {
|
|
761
|
+
// Only warn on Linux/WSL (bubblewrap doesn't support globs)
|
|
762
|
+
// macOS supports glob patterns via regex conversion
|
|
763
|
+
if (getPlatform() !== 'linux' || !config) {
|
|
764
|
+
return [];
|
|
765
|
+
}
|
|
766
|
+
const globPatterns = [];
|
|
767
|
+
// Check filesystem paths for glob patterns
|
|
768
|
+
const allPaths = [
|
|
769
|
+
...config.filesystem.denyRead,
|
|
770
|
+
...config.filesystem.allowWrite,
|
|
771
|
+
...config.filesystem.denyWrite,
|
|
772
|
+
];
|
|
773
|
+
for (const path of allPaths) {
|
|
774
|
+
// Strip trailing /** since that's just a subpath (directory and everything under it)
|
|
775
|
+
const pathWithoutTrailingStar = removeTrailingGlobSuffix(path);
|
|
776
|
+
// Only warn if there are still glob characters after removing trailing /**
|
|
777
|
+
if (containsGlobChars(pathWithoutTrailingStar)) {
|
|
778
|
+
globPatterns.push(path);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return globPatterns;
|
|
782
|
+
}
|
|
783
|
+
// ============================================================================
|
|
784
|
+
// Export as Namespace with Interface
|
|
785
|
+
// ============================================================================
|
|
786
|
+
/**
|
|
787
|
+
* Global sandbox manager that handles both network and filesystem restrictions
|
|
788
|
+
* for this session. This runs outside of the sandbox, on the host machine.
|
|
789
|
+
*/
|
|
790
|
+
export const SandboxManager = {
|
|
791
|
+
initialize,
|
|
792
|
+
isSupportedPlatform,
|
|
793
|
+
isSandboxingEnabled,
|
|
794
|
+
checkDependencies,
|
|
795
|
+
getFsReadConfig,
|
|
796
|
+
getFsWriteConfig,
|
|
797
|
+
getNetworkRestrictionConfig,
|
|
798
|
+
getAllowUnixSockets,
|
|
799
|
+
getAllowLocalBinding,
|
|
800
|
+
getIgnoreViolations,
|
|
801
|
+
getEnableWeakerNestedSandbox,
|
|
802
|
+
getProxyPort,
|
|
803
|
+
getSocksProxyPort,
|
|
804
|
+
getLinuxHttpSocketPath,
|
|
805
|
+
getLinuxSocksSocketPath,
|
|
806
|
+
waitForNetworkInitialization,
|
|
807
|
+
wrapWithSandbox,
|
|
808
|
+
spawn,
|
|
809
|
+
reset,
|
|
810
|
+
getSandboxViolationStore,
|
|
811
|
+
annotateStderrWithSandboxFailures,
|
|
812
|
+
getLinuxGlobPatternWarnings,
|
|
813
|
+
getConfig,
|
|
814
|
+
updateConfig,
|
|
815
|
+
};
|
|
816
|
+
//# sourceMappingURL=sandbox-manager.js.map
|