sandboxbox 1.2.1 โ†’ 2.0.0

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/container.js DELETED
@@ -1,608 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Bubblewrap Container Runner - Playwright + True Isolation
5
- *
6
- * This uses bubblewrap for truly rootless container operation
7
- * Perfect for Playwright with 8ms startup and zero privileged setup
8
- *
9
- * Requirements:
10
- * - bubblewrap (bwrap) - install with: apt-get install bubblewrap
11
- * - No root access needed after installation
12
- */
13
-
14
- import { readFileSync, writeFileSync, existsSync, mkdirSync, cpSync } from 'fs';
15
- import { execSync, spawn } from 'child_process';
16
- import { join, resolve, dirname } from 'path';
17
- import { fileURLToPath } from 'url';
18
- import { bubblewrap } from './lib/bubblewrap.js';
19
-
20
- const __filename = fileURLToPath(import.meta.url);
21
- const __dirname = dirname(__filename);
22
-
23
- class BubblewrapContainer {
24
- constructor(options = {}) {
25
- this.sandboxDir = options.sandboxDir || './sandboxbox-sandbox';
26
- this.alpineRoot = bubblewrap.getAlpineRoot();
27
- this.verbose = options.verbose !== false;
28
- this.env = { ...process.env };
29
- this.workdir = '/workspace';
30
- }
31
-
32
- /**
33
- * Check if bubblewrap is available
34
- */
35
- checkBubblewrap() {
36
- if (!bubblewrap.isAvailable()) {
37
- throw new Error(bubblewrap.findBubblewrap().message);
38
- }
39
- return true;
40
- }
41
-
42
- /**
43
- * Set up Alpine Linux rootfs with Playwright support
44
- */
45
- async setupAlpineRootfs() {
46
- console.log('๐Ÿ”๏ธ Setting up Alpine Linux rootfs for Playwright...\n');
47
-
48
- await bubblewrap.ensureAlpineRoot();
49
- console.log('โœ… Alpine rootfs ready!\n');
50
- }
51
-
52
- /**
53
- * Install required packages in Alpine with Playwright compatibility fixes
54
- */
55
- async setupAlpinePackages() {
56
- console.log('๐Ÿ“ฆ Installing packages in Alpine with Playwright compatibility...');
57
-
58
- // Create a temporary setup script addressing glibc/musl issues
59
- const setupScript = `
60
- #!/bin/sh
61
- set -e
62
-
63
- # Setup repositories
64
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/main' > /etc/apk/repositories
65
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/community' >> /etc/apk/repositories
66
-
67
- # Update package index
68
- apk update
69
-
70
- # Install Node.js and required tools for Playwright on Alpine
71
- # NOTE: Using Alpine's system Chromium to avoid glibc/musl compatibility issues
72
- apk add --no-cache \\
73
- nodejs \\
74
- npm \\
75
- chromium \\
76
- nss \\
77
- freetype \\
78
- harfbuzz \\
79
- ttf-freefont \\
80
- ca-certificates \\
81
- wget \\
82
- curl \\
83
- git \\
84
- bash \\
85
- xvfb \\
86
- mesa-gl \\
87
- libx11 \\
88
- libxrandr \\
89
- libxss \\
90
- libgcc \\
91
- libstdc++ \\
92
- expat \\
93
- dbus
94
-
95
- # Create workspace directory
96
- mkdir -p /workspace
97
-
98
- # Install Claude Code CLI
99
- npm install -g @anthropic-ai/claude-code
100
-
101
- # Create Playwright config for Alpine
102
- mkdir -p /workspace/playwright-config
103
-
104
- # Create Playwright configuration for Alpine (addresses sandbox conflicts)
105
- cat > /workspace/playwright.config.js << 'EOF'
106
- export default defineConfig({
107
- use: {
108
- // Required: Chromium's sandbox conflicts with bubblewrap's sandbox
109
- chromiumSandbox: false,
110
- headless: true,
111
- // Use Alpine's system Chromium
112
- executablePath: '/usr/bin/chromium-browser',
113
- },
114
- projects: [
115
- {
116
- name: 'chromium',
117
- use: {
118
- executablePath: '/usr/bin/chromium-browser',
119
- },
120
- },
121
- ],
122
- // Skip browser downloads since we use system Chromium
123
- webServer: {
124
- command: 'echo "Skipping browser download"',
125
- },
126
- });
127
- EOF
128
-
129
- # Create test script that handles Chromium sandbox issues
130
- cat > /workspace/run-playwright.sh << 'EOF'
131
- #!/bin/sh
132
- set -e
133
-
134
- # Environment variables for Playwright on Alpine with bubblewrap
135
- export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
136
- export PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser
137
- export DISPLAY=:99
138
-
139
- # Start virtual display for headless operation
140
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
141
- XVFB_PID=$!
142
-
143
- # Give Xvfb time to start
144
- sleep 2
145
-
146
- # Cleanup function
147
- cleanup() {
148
- if [ ! -z "$XVFB_PID" ]; then
149
- kill $XVFB_PID 2>/dev/null || true
150
- fi
151
- }
152
-
153
- # Trap cleanup
154
- trap cleanup EXIT INT TERM
155
-
156
- echo "๐ŸŽญ Running Playwright in Alpine with bubblewrap isolation..."
157
- echo "๐Ÿ“ Workspace: $(pwd)"
158
- echo "๐ŸŒ Display: $DISPLAY"
159
- echo "๐Ÿš€ Chromium: $(which chromium-browser)"
160
-
161
- # Run Playwright tests
162
- exec "$@"
163
- EOF
164
-
165
- chmod +x /workspace/run-playwright.sh
166
-
167
- echo "โœ… Alpine setup complete with Playwright compatibility fixes"
168
- echo "๐Ÿ“‹ Installed: Node.js, Chromium, Xvfb, fonts, libraries"
169
- echo "๐ŸŽฏ Created: Playwright config and wrapper scripts"
170
- echo "โš ๏ธ Note: Chromium-only (Firefox/WebKit need glibc - use Ubuntu)"
171
- `;
172
-
173
- const scriptPath = join(this.sandboxDir, 'setup-alpine.sh');
174
- writeFileSync(scriptPath, setupScript, { mode: 0o755 });
175
-
176
- try {
177
- // Run setup inside Alpine using bubblewrap
178
- const bwrapCmd = [
179
- 'bwrap',
180
- '--ro-bind', `${this.alpineRoot}`, '/',
181
- '--proc', '/proc',
182
- '--dev', '/dev',
183
- '--tmpfs', '/tmp',
184
- '--tmpfs', '/var/tmp',
185
- '--tmpfs', '/run',
186
- '--bind', `${this.sandboxDir}`, '/host',
187
- '--share-net',
188
- '--die-with-parent',
189
- '--new-session',
190
- scriptPath
191
- ];
192
-
193
- console.log('๐Ÿ”ง Running Alpine setup...');
194
- execSync(bwrapCmd.join(' '), { stdio: 'inherit' });
195
-
196
- } catch (error) {
197
- throw new Error(`Alpine setup failed: ${error.message}`);
198
- }
199
- }
200
-
201
- /**
202
- * Run Playwright tests in bubblewrap sandbox with proper sandbox conflict handling
203
- */
204
- async runPlaywright(options = {}) {
205
- this.checkBubblewrap();
206
-
207
- const {
208
- projectDir = '.',
209
- testCommand = 'npx playwright test',
210
- mountProject = true,
211
- headless = true
212
- } = options;
213
-
214
- console.log('๐ŸŽญ Starting Playwright in bubblewrap sandbox...\n');
215
- console.log('๐Ÿ”ง Addressing Alpine/Playwright compatibility issues...\n');
216
-
217
- // Resolve project directory
218
- const resolvedProjectDir = resolve(projectDir);
219
-
220
- // First, try full namespace isolation
221
- try {
222
- console.log('๐ŸŽฏ Attempting full namespace isolation...');
223
- return await this.runPlaywrightWithNamespaces(options);
224
- } catch (error) {
225
- console.log(`โš ๏ธ Namespace isolation failed: ${error.message}`);
226
- console.log('๐Ÿ”„ Falling back to basic isolation mode...\n');
227
- return await this.runPlaywrightBasic(options);
228
- }
229
- }
230
-
231
- /**
232
- * Run simple container test without Playwright (for testing purposes)
233
- */
234
- async runSimpleTest(options = {}) {
235
- const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la /workspace' } = options;
236
- const resolvedProjectDir = resolve(projectDir);
237
-
238
- console.log('๐Ÿงช Running simple container test...\n');
239
-
240
- // Try basic isolation first
241
- try {
242
- console.log('๐ŸŽฏ Attempting basic isolation...');
243
- return await this.runBasicTest(options);
244
- } catch (error) {
245
- console.log(`โš ๏ธ Basic test failed: ${error.message}`);
246
- console.log('๐Ÿ”„ Running without isolation...\n');
247
- return this.runWithoutIsolation(options);
248
- }
249
- }
250
-
251
- /**
252
- * Run basic test in container
253
- */
254
- async runBasicTest(options = {}) {
255
- const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la /workspace' } = options;
256
- const resolvedProjectDir = resolve(projectDir);
257
-
258
- // Simplified bubblewrap command
259
- const bwrapCmd = [
260
- bubblewrap.findBubblewrap(),
261
- '--bind', resolvedProjectDir, '/workspace',
262
- '--chdir', '/workspace',
263
- '--tmpfs', '/tmp',
264
- '/bin/sh', '-c', testCommand
265
- ];
266
-
267
- console.log(`๐Ÿš€ Running: ${testCommand}`);
268
- console.log(`๐Ÿ“ Project directory: ${resolvedProjectDir}`);
269
- console.log(`๐ŸŽฏ Sandbox isolation: basic mode\n`);
270
-
271
- return this.executeCommand(bwrapCmd, resolvedProjectDir);
272
- }
273
-
274
- /**
275
- * Run without any isolation (last resort)
276
- */
277
- async runWithoutIsolation(options = {}) {
278
- const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la' } = options;
279
- const resolvedProjectDir = resolve(projectDir);
280
-
281
- console.log(`๐Ÿš€ Running without isolation: ${testCommand}`);
282
- console.log(`๐Ÿ“ Project directory: ${resolvedProjectDir}`);
283
- console.log(`๐ŸŽฏ Sandbox isolation: none\n`);
284
-
285
- try {
286
- execSync(testCommand, { stdio: 'inherit', cwd: resolvedProjectDir });
287
- console.log('\nโœ… Test completed successfully!');
288
- return 0;
289
- } catch (error) {
290
- throw new Error(`Test failed: ${error.message}`);
291
- }
292
- }
293
-
294
- /**
295
- * Run Playwright with full namespace isolation (ideal mode)
296
- */
297
- async runPlaywrightWithNamespaces(options = {}) {
298
- const { projectDir = '.', testCommand = 'npx playwright test', mountProject = true } = options;
299
- const resolvedProjectDir = resolve(projectDir);
300
-
301
- // Build bubblewrap command with proper namespace isolation
302
- const bwrapCmd = [
303
- bubblewrap.findBubblewrap(),
304
-
305
- // Core filesystem - read-only Alpine rootfs
306
- '--ro-bind', `${this.alpineRoot}`, '/',
307
- '--proc', '/proc',
308
- '--dev', '/dev',
309
-
310
- // Critical: Bind mount /dev/dri for GPU acceleration (Chrome needs this)
311
- '--dev-bind', '/dev/dri', '/dev/dri',
312
-
313
- // Temporary directories (fresh for each run)
314
- '--tmpfs', '/tmp',
315
- '--tmpfs', '/var/tmp',
316
- '--tmpfs', '/run',
317
- '--tmpfs', '/dev/shm', // Chrome shared memory
318
-
319
- // Mount project directory
320
- ...(mountProject ? [
321
- '--bind', resolvedProjectDir, '/workspace',
322
- '--chdir', '/workspace'
323
- ] : []),
324
-
325
- // Mount X11 socket for display
326
- '--bind', '/tmp/.X11-unix', '/tmp/.X11-unix',
327
-
328
- // Host directory access
329
- '--bind', this.sandboxDir, '/host',
330
-
331
- // Networking (required for Playwright)
332
- '--share-net',
333
-
334
- // Process isolation - critical for security
335
- '--unshare-pid',
336
- '--unshare-ipc',
337
- '--unshare-uts',
338
- '--unshare-cgroup', // Prevent process group interference
339
-
340
- // Safety features
341
- '--die-with-parent',
342
- '--new-session',
343
- '--as-pid-1', // Make bash PID 1 in the namespace
344
-
345
- // Set hostname for isolation
346
- '--hostname', 'playwright-sandbox',
347
-
348
- // Environment variables for Playwright on Alpine
349
- '--setenv', 'PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
350
- '--setenv', 'PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser',
351
- '--setenv', 'DISPLAY=:99', // Virtual display
352
- '--setenv', 'CI=true',
353
- '--setenv', 'NODE_ENV=test',
354
-
355
- // Chrome/Chromium specific variables
356
- '--setenv', 'CHROMIUM_FLAGS=--no-sandbox --disable-dev-shm-usage --disable-gpu',
357
- '--setenv', 'CHROME_BIN=/usr/bin/chromium-browser',
358
-
359
- // Preserve important user environment
360
- ...Object.entries(this.env)
361
- .filter(([key]) => !['PATH', 'HOME', 'DISPLAY'].includes(key))
362
- .flatMap(([key, value]) => ['--setenv', key, value])
363
- ];
364
-
365
- // Use the wrapper script that handles Xvfb and Chromium sandbox issues
366
- const wrappedCommand = `/workspace/run-playwright.sh ${testCommand}`;
367
- const fullCmd = [...bwrapCmd, '/bin/sh', '-c', wrappedCommand];
368
-
369
- console.log(`๐Ÿš€ Running: ${testCommand}`);
370
- console.log(`๐Ÿ“ Project directory: ${resolvedProjectDir}`);
371
- console.log(`๐ŸŽฏ Sandbox isolation: full bubblewrap namespace isolation\n`);
372
-
373
- return this.executeCommand(fullCmd, resolvedProjectDir);
374
- }
375
-
376
- /**
377
- * Run Playwright with basic isolation (fallback mode for limited environments)
378
- */
379
- async runPlaywrightBasic(options = {}) {
380
- const { projectDir = '.', testCommand = 'npx playwright test', mountProject = true } = options;
381
- const resolvedProjectDir = resolve(projectDir);
382
-
383
- console.log('๐ŸŽฏ Running in basic isolation mode (limited features)...');
384
-
385
- // Simplified bubblewrap command without namespaces
386
- const bwrapCmd = [
387
- bubblewrap.findBubblewrap(),
388
-
389
- // Basic filesystem
390
- '--bind', resolvedProjectDir, '/workspace',
391
- '--chdir', '/workspace',
392
- '--tmpfs', '/tmp',
393
- '--share-net', // Keep network access
394
-
395
- // Essential environment variables
396
- '--setenv', 'PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
397
- '--setenv', 'PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser',
398
- '--setenv', 'CHROMIUM_FLAGS=--no-sandbox --disable-dev-shm-usage --disable-gpu',
399
-
400
- // Run command directly without wrapper script
401
- '/bin/sh', '-c', testCommand
402
- ];
403
-
404
- console.log(`๐Ÿš€ Running: ${testCommand}`);
405
- console.log(`๐Ÿ“ Project directory: ${resolvedProjectDir}`);
406
- console.log(`๐ŸŽฏ Sandbox isolation: basic mode (limited namespaces)\n`);
407
-
408
- return this.executeCommand(bwrapCmd, resolvedProjectDir);
409
- }
410
-
411
- /**
412
- * Execute bubblewrap command with proper error handling
413
- */
414
- executeCommand(fullCmd, resolvedProjectDir) {
415
- try {
416
- // Execute with spawn for better control
417
- const child = spawn(fullCmd[0], fullCmd.slice(1), {
418
- stdio: 'inherit',
419
- cwd: resolvedProjectDir,
420
- env: this.env
421
- });
422
-
423
- return new Promise((resolve, reject) => {
424
- child.on('close', (code) => {
425
- if (code === 0) {
426
- console.log('\nโœ… Playwright tests completed successfully!');
427
- resolve(code);
428
- } else {
429
- console.log(`\nโŒ Playwright tests failed with exit code: ${code}`);
430
- reject(new Error(`Playwright tests failed with exit code: ${code}`));
431
- }
432
- });
433
-
434
- child.on('error', (error) => {
435
- reject(new Error(`Failed to start Playwright: ${error.message}`));
436
- });
437
- });
438
-
439
- } catch (error) {
440
- throw new Error(`Playwright execution failed: ${error.message}`);
441
- }
442
- }
443
-
444
- /**
445
- * Run interactive shell in sandbox
446
- */
447
- async runShell(options = {}) {
448
- this.checkBubblewrap();
449
-
450
- const { projectDir = '.' } = options;
451
- const resolvedProjectDir = resolve(projectDir);
452
-
453
- const bwrapCmd = [
454
- 'bwrap',
455
- '--ro-bind', `${this.alpineRoot}`, '/',
456
- '--proc', '/proc',
457
- '--dev', '/dev',
458
- '--tmpfs', '/tmp',
459
- '--bind', resolvedProjectDir, '/workspace',
460
- '--chdir', '/workspace',
461
- '--share-net',
462
- '--unshare-pid',
463
- '--die-with-parent',
464
- '--new-session',
465
- '--hostname', 'claudebox-sandbox',
466
- '--setenv', 'PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
467
- '--setenv', 'PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser',
468
- '/bin/bash'
469
- ];
470
-
471
- console.log('๐Ÿš Starting interactive shell in sandbox...\n');
472
-
473
- try {
474
- execSync(bwrapCmd.join(' '), { stdio: 'inherit' });
475
- } catch (error) {
476
- throw new Error(`Shell execution failed: ${error.message}`);
477
- }
478
- }
479
-
480
- /**
481
- * Build container from Dockerfile
482
- */
483
- async buildFromDockerfile(dockerfilePath, options = {}) {
484
- this.checkBubblewrap();
485
-
486
- console.log('๐Ÿณ Building container with bubblewrap isolation...\n');
487
-
488
- const content = readFileSync(dockerfilePath, 'utf-8');
489
- const lines = content.split('\n')
490
- .filter(line => line.trim() && !line.trim().startsWith('#'));
491
-
492
- let workdir = '/workspace';
493
- const buildCommands = [];
494
-
495
- for (const line of lines) {
496
- const trimmed = line.trim();
497
- if (trimmed.startsWith('FROM')) {
498
- console.log(`๐Ÿ“ฆ FROM ${trimmed.substring(5).trim()}`);
499
- console.log(' โœ… Using Alpine Linux base image\n');
500
- } else if (trimmed.startsWith('WORKDIR')) {
501
- workdir = trimmed.substring(9).trim().replace(/['"]/g, '');
502
- console.log(`๐Ÿ“ WORKDIR ${workdir}\n`);
503
- } else if (trimmed.startsWith('RUN')) {
504
- const command = trimmed.substring(4).trim();
505
- buildCommands.push(command);
506
- console.log(`โš™๏ธ RUN ${command.substring(0, 60)}${command.length > 60 ? '...' : ''}`);
507
- console.log(' ๐Ÿ“ Added to build script\n');
508
- } else if (trimmed.startsWith('CMD')) {
509
- console.log(`๐ŸŽฏ CMD ${trimmed.substring(4).trim()}`);
510
- console.log(' ๐Ÿ“ Default command recorded\n');
511
- }
512
- }
513
-
514
- // Create build script
515
- const buildScript = buildCommands.join('\n');
516
- const scriptPath = join(this.sandboxDir, 'build.sh');
517
- writeFileSync(scriptPath, buildScript, { mode: 0o755 });
518
-
519
- console.log('โœ… Container build complete!');
520
- console.log(`๐Ÿ“ Build script: ${scriptPath}`);
521
- console.log(`๐ŸŽฏ To run: node bubblewrap-container.js --run\n`);
522
-
523
- return { buildScript: scriptPath, workdir };
524
- }
525
- }
526
-
527
- // Main execution
528
- async function main() {
529
- const args = process.argv.slice(2);
530
-
531
- if (args.includes('--help') || args.includes('-h')) {
532
- console.log(`
533
- Bubblewrap Container Runner - Playwright + True Isolation
534
-
535
- Usage:
536
- node bubblewrap-container.js <command> [options]
537
-
538
- Commands:
539
- setup Set up Alpine Linux rootfs with Playwright
540
- build <dockerfile> Build from Dockerfile
541
- run [project-dir] Run Playwright tests
542
- shell [project-dir] Start interactive shell
543
-
544
- Examples:
545
- node bubblewrap-container.js setup
546
- node bubblewrap-container.js build Dockerfile.claudebox
547
- node bubblewrap-container.js run ./my-project
548
- node bubblewrap-container.js shell ./my-project
549
-
550
- Requirements:
551
- - bubblewrap (bwrap): sudo apt-get install bubblewrap
552
- - No root privileges needed after installation
553
- `);
554
- process.exit(0);
555
- }
556
-
557
- const container = new BubblewrapContainer({ verbose: true });
558
-
559
- try {
560
- if (args[0] === 'setup') {
561
- await container.setupAlpineRootfs();
562
-
563
- } else if (args[0] === 'build') {
564
- const dockerfile = args[1] || './Dockerfile.claudebox';
565
- if (!existsSync(dockerfile)) {
566
- throw new Error(`Dockerfile not found: ${dockerfile}`);
567
- }
568
- await container.buildFromDockerfile(dockerfile);
569
-
570
- } else if (args[0] === 'run') {
571
- const projectDir = args[1] || '.';
572
-
573
- // First try simple test to verify container works
574
- console.log('๐Ÿงช Testing container functionality...\n');
575
- try {
576
- await container.runSimpleTest({ projectDir });
577
- console.log('โœ… Container test successful!\n');
578
-
579
- // Now try Playwright
580
- console.log('๐ŸŽญ Running Playwright tests...\n');
581
- await container.runPlaywright({ projectDir });
582
- } catch (error) {
583
- console.log(`โš ๏ธ Container test failed: ${error.message}`);
584
- console.log('๐Ÿšซ Skipping Playwright tests due to container issues\n');
585
- throw error;
586
- }
587
-
588
- } else if (args[0] === 'shell') {
589
- const projectDir = args[1] || '.';
590
- await container.runShell({ projectDir });
591
-
592
- } else {
593
- console.error('โŒ Unknown command. Use --help for usage.');
594
- process.exit(1);
595
- }
596
-
597
- } catch (error) {
598
- console.error('โŒ Error:', error.message);
599
- process.exit(1);
600
- }
601
- }
602
-
603
- // Run if called directly
604
- if (import.meta.url === `file://${process.argv[1]}`) {
605
- main().catch(console.error);
606
- }
607
-
608
- export default BubblewrapContainer;
package/debug-cli.js DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- console.log('๐Ÿš€ Debug CLI starting...');
4
- console.log('Args:', process.argv.slice(2));
5
- console.log('Platform:', process.platform);
6
- console.log('CWD:', process.cwd());
7
-
8
- // Test basic output
9
- console.log('โœ… Basic output works');
10
-
11
- // Test if script continues
12
- setTimeout(() => {
13
- console.log('โœ… Script completed after timeout');
14
- process.exit(0);
15
- }, 1000);