RaBU1234 commited on
Commit
6232643
·
verified ·
1 Parent(s): 683e7da

Update simple-sandbox-manager.js

Browse files
Files changed (1) hide show
  1. simple-sandbox-manager.js +41 -31
simple-sandbox-manager.js CHANGED
@@ -11,12 +11,12 @@ class SimpleSandboxManager extends EventEmitter {
11
  this.basePath = '/tmp/sandboxes';
12
  this.portCounter = 3000;
13
  this.streamClients = new Map();
14
- this.commandData = new Map(); // Store stdout/stderr for later retrieval
15
  }
16
 
17
  async initialize() {
18
  await fs.mkdir(this.basePath, { recursive: true });
19
- console.log('✅ Vercel-Compatible Sandbox Manager initialized');
20
  }
21
 
22
  async createSandbox(sandboxId, options = {}) {
@@ -35,7 +35,7 @@ class SimpleSandboxManager extends EventEmitter {
35
  workspacePath,
36
  port,
37
  exposedPorts: ports,
38
- timeout, // Store timeout for auto-cleanup
39
  createdAt: Date.now(),
40
  lastActivity: Date.now(),
41
  status: 'ready'
@@ -88,25 +88,25 @@ class SimpleSandboxManager extends EventEmitter {
88
  sandbox.lastActivity = Date.now();
89
 
90
  const { background = false, timeout = 300000 } = options;
91
-
92
- // Security whitelist
93
- const allowedCommands = [
94
- 'node', 'npm', 'npx', 'python3', 'python', 'pip',
95
- 'go', 'cat', 'ls', 'mkdir', 'echo', 'touch',
96
- 'rm', 'cp', 'mv', 'pwd', 'which', 'sh', 'bash', 'chmod'
97
- ];
98
-
99
- const baseCommand = command.split(' ')[0];
100
- if (!allowedCommands.includes(baseCommand)) {
101
- throw new Error(`Command not allowed: ${baseCommand}`);
102
- }
103
-
104
  const fullCommand = `${command} ${args.join(' ')}`;
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  if (background) {
107
- return this.startBackgroundProcess(sandboxId, fullCommand, sandbox.workspacePath, sandbox.port);
108
  } else {
109
- return this.startStreamingCommand(sandboxId, fullCommand, sandbox.workspacePath, timeout);
110
  }
111
  }
112
 
@@ -115,7 +115,6 @@ class SimpleSandboxManager extends EventEmitter {
115
  const cmdId = `cmd-${Date.now()}`;
116
  const dataKey = `${sandboxId}:${cmdId}`;
117
 
118
- // Initialize data storage
119
  this.commandData.set(dataKey, {
120
  stdout: '',
121
  stderr: '',
@@ -145,7 +144,7 @@ class SimpleSandboxManager extends EventEmitter {
145
  proc.stdout.on('data', (data) => {
146
  const output = data.toString();
147
  const cmdData = this.commandData.get(dataKey);
148
- cmdData.stdout += output;
149
 
150
  console.log(`[${sandboxId}][stdout]: ${output.trim()}`);
151
  this.sendToStreamClients(sandboxId, cmdId, 'stdout', output);
@@ -154,7 +153,7 @@ class SimpleSandboxManager extends EventEmitter {
154
  proc.stderr.on('data', (data) => {
155
  const output = data.toString();
156
  const cmdData = this.commandData.get(dataKey);
157
- cmdData.stderr += output;
158
 
159
  console.log(`[${sandboxId}][stderr]: ${output.trim()}`);
160
  this.sendToStreamClients(sandboxId, cmdId, 'stderr', output);
@@ -165,25 +164,24 @@ class SimpleSandboxManager extends EventEmitter {
165
  clearTimeout(timeoutId);
166
 
167
  const cmdData = this.commandData.get(dataKey);
168
- cmdData.exitCode = code;
169
 
170
  console.log(`[${sandboxId}] Command exited with code: ${code}`);
171
 
172
  this.sendToStreamClients(sandboxId, cmdId, 'complete', {
173
  exitCode: code,
174
- stdout: cmdData.stdout,
175
- stderr: cmdData.stderr
176
  });
177
 
178
  resolve({
179
  commandId: cmdId,
180
  exitCode: code,
181
- stdout: cmdData.stdout,
182
- stderr: cmdData.stderr,
183
  streaming: true
184
  });
185
 
186
- // Cleanup after 5 minutes
187
  setTimeout(() => this.commandData.delete(dataKey), 5 * 60 * 1000);
188
  });
189
 
@@ -267,13 +265,26 @@ class SimpleSandboxManager extends EventEmitter {
267
  sandbox.lastActivity = Date.now();
268
 
269
  for (const file of files) {
270
- const fullPath = path.join(sandbox.workspacePath, file.path);
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  const dir = path.dirname(fullPath);
272
 
273
  await fs.mkdir(dir, { recursive: true });
274
- await fs.writeFile(fullPath, file.content, 'utf-8');
275
 
276
- console.log(`📝 File written: ${file.path}`);
277
  }
278
  }
279
 
@@ -325,7 +336,6 @@ class SimpleSandboxManager extends EventEmitter {
325
  const now = Date.now();
326
 
327
  for (const [id, sandbox] of this.sandboxes.entries()) {
328
- // Check both inactivity AND timeout
329
  const inactive = (now - sandbox.lastActivity) > (15 * 60 * 1000);
330
  const timedOut = (now - sandbox.createdAt) > sandbox.timeout;
331
 
 
11
  this.basePath = '/tmp/sandboxes';
12
  this.portCounter = 3000;
13
  this.streamClients = new Map();
14
+ this.commandData = new Map();
15
  }
16
 
17
  async initialize() {
18
  await fs.mkdir(this.basePath, { recursive: true });
19
+ console.log('✅ Namespace-Isolated Sandbox Manager initialized');
20
  }
21
 
22
  async createSandbox(sandboxId, options = {}) {
 
35
  workspacePath,
36
  port,
37
  exposedPorts: ports,
38
+ timeout,
39
  createdAt: Date.now(),
40
  lastActivity: Date.now(),
41
  status: 'ready'
 
88
  sandbox.lastActivity = Date.now();
89
 
90
  const { background = false, timeout = 300000 } = options;
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  const fullCommand = `${command} ${args.join(' ')}`;
92
 
93
+ // ✅ NO WHITELIST - Uses Linux namespaces for isolation
94
+ // unshare creates isolated namespace environment:
95
+ // --net: Network namespace (isolated network stack)
96
+ // --pid: PID namespace (can't see/kill external processes)
97
+ // --mount: Mount namespace (isolated filesystem view)
98
+ // --fork: Fork into namespace before executing
99
+ const isolatedCommand = `
100
+ unshare --net --pid --mount --fork sh -c "
101
+ cd ${sandbox.workspacePath}
102
+ ${fullCommand}
103
+ "
104
+ `;
105
+
106
  if (background) {
107
+ return this.startBackgroundProcess(sandboxId, isolatedCommand, sandbox.path, sandbox.port);
108
  } else {
109
+ return this.startStreamingCommand(sandboxId, isolatedCommand, sandbox.path, timeout);
110
  }
111
  }
112
 
 
115
  const cmdId = `cmd-${Date.now()}`;
116
  const dataKey = `${sandboxId}:${cmdId}`;
117
 
 
118
  this.commandData.set(dataKey, {
119
  stdout: '',
120
  stderr: '',
 
144
  proc.stdout.on('data', (data) => {
145
  const output = data.toString();
146
  const cmdData = this.commandData.get(dataKey);
147
+ if (cmdData) cmdData.stdout += output;
148
 
149
  console.log(`[${sandboxId}][stdout]: ${output.trim()}`);
150
  this.sendToStreamClients(sandboxId, cmdId, 'stdout', output);
 
153
  proc.stderr.on('data', (data) => {
154
  const output = data.toString();
155
  const cmdData = this.commandData.get(dataKey);
156
+ if (cmdData) cmdData.stderr += output;
157
 
158
  console.log(`[${sandboxId}][stderr]: ${output.trim()}`);
159
  this.sendToStreamClients(sandboxId, cmdId, 'stderr', output);
 
164
  clearTimeout(timeoutId);
165
 
166
  const cmdData = this.commandData.get(dataKey);
167
+ if (cmdData) cmdData.exitCode = code;
168
 
169
  console.log(`[${sandboxId}] Command exited with code: ${code}`);
170
 
171
  this.sendToStreamClients(sandboxId, cmdId, 'complete', {
172
  exitCode: code,
173
+ stdout: cmdData?.stdout || '',
174
+ stderr: cmdData?.stderr || ''
175
  });
176
 
177
  resolve({
178
  commandId: cmdId,
179
  exitCode: code,
180
+ stdout: cmdData?.stdout || '',
181
+ stderr: cmdData?.stderr || '',
182
  streaming: true
183
  });
184
 
 
185
  setTimeout(() => this.commandData.delete(dataKey), 5 * 60 * 1000);
186
  });
187
 
 
265
  sandbox.lastActivity = Date.now();
266
 
267
  for (const file of files) {
268
+ const filePath = file.path;
269
+ let fileContent = file.content;
270
+
271
+ // Handle object content (JSON files)
272
+ if (typeof fileContent === 'object' && fileContent !== null) {
273
+ fileContent = JSON.stringify(fileContent, null, 2);
274
+ }
275
+
276
+ // Ensure string
277
+ if (typeof fileContent !== 'string') {
278
+ fileContent = String(fileContent);
279
+ }
280
+
281
+ const fullPath = path.join(sandbox.workspacePath, filePath);
282
  const dir = path.dirname(fullPath);
283
 
284
  await fs.mkdir(dir, { recursive: true });
285
+ await fs.writeFile(fullPath, fileContent, 'utf-8');
286
 
287
+ console.log(`📝 File written: ${filePath}`);
288
  }
289
  }
290
 
 
336
  const now = Date.now();
337
 
338
  for (const [id, sandbox] of this.sandboxes.entries()) {
 
339
  const inactive = (now - sandbox.lastActivity) > (15 * 60 * 1000);
340
  const timedOut = (now - sandbox.createdAt) > sandbox.timeout;
341