sandboxbox 1.0.5 → 1.0.7
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/bin/bwrap +50 -0
- package/cli.js +9 -3
- package/package.json +2 -2
- package/scripts/build.js +201 -2
- package/test-cli.js +0 -0
package/bin/bwrap
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# Minimal bubblewrap fallback for SandboxBox
|
3
|
+
# This provides basic namespace isolation functionality
|
4
|
+
|
5
|
+
# Handle --version flag for compatibility
|
6
|
+
if [[ "$1" == "--version" ]]; then
|
7
|
+
echo "bubblewrap 0.11.0 (minimal fallback for SandboxBox)"
|
8
|
+
exit 0
|
9
|
+
fi
|
10
|
+
|
11
|
+
# Handle --help flag
|
12
|
+
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
|
13
|
+
echo "bubblewrap - minimal fallback version"
|
14
|
+
echo ""
|
15
|
+
echo "⚠️ This is a minimal fallback for SandboxBox"
|
16
|
+
echo "💡 For full functionality, install bubblewrap:"
|
17
|
+
echo " sudo apt-get install bubblewrap"
|
18
|
+
echo ""
|
19
|
+
echo "Usage: bwrap [options] -- command [args]"
|
20
|
+
exit 0
|
21
|
+
fi
|
22
|
+
|
23
|
+
echo "⚠️ Using minimal bubblewrap fallback"
|
24
|
+
echo "💡 For full functionality, install bubblewrap:"
|
25
|
+
echo " sudo apt-get install bubblewrap"
|
26
|
+
echo ""
|
27
|
+
|
28
|
+
# Filter out bubblewrap-specific options that unshare doesn't support
|
29
|
+
ARGS=()
|
30
|
+
for arg in "$@"; do
|
31
|
+
case "$arg" in
|
32
|
+
--ro-bind|--bind|--dev-bind|--proc|--tmpfs|--symlink|--dir|--file|--setenv|--die-with-parent|--new-session|--share-net|--unshare-net|--unshare-pid|--unshare-ipc|--unshare-uts|--unshare-cgroup|--unshare-user)
|
33
|
+
# Skip bubblewrap-specific options
|
34
|
+
;;
|
35
|
+
*)
|
36
|
+
ARGS+=("$arg")
|
37
|
+
;;
|
38
|
+
esac
|
39
|
+
done
|
40
|
+
|
41
|
+
# Basic namespace isolation using unshare
|
42
|
+
exec unshare \
|
43
|
+
--pid \
|
44
|
+
--mount \
|
45
|
+
--uts \
|
46
|
+
--ipc \
|
47
|
+
--net \
|
48
|
+
--fork \
|
49
|
+
--mount-proc \
|
50
|
+
"${ARGS[@]}"
|
package/cli.js
CHANGED
@@ -10,9 +10,9 @@
|
|
10
10
|
* npx sandboxbox shell <project> # Interactive shell
|
11
11
|
*/
|
12
12
|
|
13
|
-
//
|
14
|
-
console.log('🚀 SandboxBox starting...');
|
13
|
+
// Optional debug output
|
15
14
|
if (process.env.DEBUG) {
|
15
|
+
console.log('🔧 Debug: SandboxBox CLI starting...');
|
16
16
|
console.log(`🔧 Debug: Platform: ${process.platform}`);
|
17
17
|
console.log(`🔧 Debug: Node.js: ${process.version}`);
|
18
18
|
console.log(`🔧 Debug: Args: ${process.argv.slice(2).join(' ')}`);
|
@@ -192,7 +192,13 @@ async function main() {
|
|
192
192
|
case 'run':
|
193
193
|
const projectDir = commandArgs[0] || '.';
|
194
194
|
console.log(color('blue', '🚀 Running Playwright tests...'));
|
195
|
-
|
195
|
+
console.log(color('yellow', `Project directory: ${projectDir}`));
|
196
|
+
|
197
|
+
if (!(await checkBubblewrap())) {
|
198
|
+
console.log(color('red', '❌ Cannot run tests without bubblewrap'));
|
199
|
+
process.exit(1);
|
200
|
+
}
|
201
|
+
|
196
202
|
runScript('./container.js', ['run', projectDir]);
|
197
203
|
break;
|
198
204
|
|
package/package.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "sandboxbox",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.7",
|
4
4
|
"description": "Zero-privilege container runner with Playwright support",
|
5
5
|
"type": "module",
|
6
6
|
"main": "index.js",
|
7
7
|
"bin": {
|
8
|
-
"sandboxbox": "./
|
8
|
+
"sandboxbox": "./cli.js"
|
9
9
|
},
|
10
10
|
"scripts": {
|
11
11
|
"install": "node scripts/build.js",
|
package/scripts/build.js
CHANGED
@@ -59,10 +59,146 @@ async function downloadAndBuild() {
|
|
59
59
|
// System bwrap not found, continue with build
|
60
60
|
}
|
61
61
|
|
62
|
-
//
|
62
|
+
// Try to download pre-built binary first
|
63
|
+
if (await downloadPreBuiltBinary(binaryPath)) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
// Build from source like SQLite does as last resort
|
63
68
|
await buildFromSource(binaryPath);
|
64
69
|
}
|
65
70
|
|
71
|
+
async function downloadPreBuiltBinary(binaryPath) {
|
72
|
+
console.log('📥 Trying pre-built bubblewrap binary...');
|
73
|
+
|
74
|
+
const arch = process.arch === 'x64' ? 'x86_64' : process.arch;
|
75
|
+
const possibleUrls = [
|
76
|
+
// Alpine packages (HTTPS) - use the actual available version
|
77
|
+
`https://dl-cdn.alpinelinux.org/alpine/v3.20/main/${arch}/bubblewrap-0.10.0-r0.apk`,
|
78
|
+
// Try some common locations for pre-built binaries
|
79
|
+
`https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}-${arch}.tar.xz`,
|
80
|
+
`https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}.tar.gz`,
|
81
|
+
];
|
82
|
+
|
83
|
+
for (const url of possibleUrls) {
|
84
|
+
try {
|
85
|
+
console.log(`📦 Trying: ${url.split('/').pop()}`);
|
86
|
+
|
87
|
+
const response = await new Promise((resolve, reject) => {
|
88
|
+
https.get(url, (res) => {
|
89
|
+
if (res.statusCode === 200) {
|
90
|
+
resolve(res);
|
91
|
+
} else {
|
92
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
93
|
+
}
|
94
|
+
}).on('error', reject);
|
95
|
+
});
|
96
|
+
|
97
|
+
if (url.endsWith('.apk')) {
|
98
|
+
// Handle Alpine package
|
99
|
+
console.log('📦 Alpine package found, extracting...');
|
100
|
+
return await extractAlpinePackage(url, binaryPath);
|
101
|
+
} else {
|
102
|
+
// Handle tarball
|
103
|
+
return await extractTarball(url, binaryPath);
|
104
|
+
}
|
105
|
+
} catch (error) {
|
106
|
+
console.log(`❌ Failed: ${error.message}`);
|
107
|
+
continue;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
console.log('❌ No pre-built binaries available');
|
112
|
+
return false;
|
113
|
+
}
|
114
|
+
|
115
|
+
async function extractAlpinePackage(url, binaryPath) {
|
116
|
+
const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'apk-extract-'));
|
117
|
+
|
118
|
+
try {
|
119
|
+
const apkPath = path.join(tmpDir, 'bubblewrap.apk');
|
120
|
+
|
121
|
+
// Download APK
|
122
|
+
await new Promise((resolve, reject) => {
|
123
|
+
const file = fs.createWriteStream(apkPath);
|
124
|
+
https.get(url, (response) => {
|
125
|
+
response.pipe(file);
|
126
|
+
file.on('finish', resolve);
|
127
|
+
}).on('error', reject);
|
128
|
+
});
|
129
|
+
|
130
|
+
// Extract APK (tar.gz format)
|
131
|
+
execSync(`tar -xzf "${apkPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
|
132
|
+
|
133
|
+
// Find the binary
|
134
|
+
const possiblePaths = [
|
135
|
+
path.join(tmpDir, 'usr', 'bin', 'bwrap'),
|
136
|
+
path.join(tmpDir, 'bin', 'bwrap'),
|
137
|
+
];
|
138
|
+
|
139
|
+
for (const possiblePath of possiblePaths) {
|
140
|
+
if (fs.existsSync(possiblePath)) {
|
141
|
+
fs.copyFileSync(possiblePath, binaryPath);
|
142
|
+
fs.chmodSync(binaryPath, 0o755);
|
143
|
+
console.log('✅ Extracted pre-built binary from Alpine package');
|
144
|
+
return true;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
throw new Error('Binary not found in package');
|
149
|
+
} finally {
|
150
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
async function extractTarball(url, binaryPath) {
|
155
|
+
const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'tar-extract-'));
|
156
|
+
|
157
|
+
try {
|
158
|
+
const tarballPath = path.join(tmpDir, 'bubblewrap.tar');
|
159
|
+
|
160
|
+
// Download tarball
|
161
|
+
await new Promise((resolve, reject) => {
|
162
|
+
const file = fs.createWriteStream(tarballPath);
|
163
|
+
https.get(url, (response) => {
|
164
|
+
response.pipe(file);
|
165
|
+
file.on('finish', resolve);
|
166
|
+
}).on('error', reject);
|
167
|
+
});
|
168
|
+
|
169
|
+
// Extract with available tools
|
170
|
+
if (tarballPath.endsWith('.xz')) {
|
171
|
+
try {
|
172
|
+
execSync(`tar -xf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
|
173
|
+
} catch (e) {
|
174
|
+
throw new Error('xz extraction failed - need xz-utils');
|
175
|
+
}
|
176
|
+
} else {
|
177
|
+
execSync(`tar -xzf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
|
178
|
+
}
|
179
|
+
|
180
|
+
// Find the binary
|
181
|
+
const possiblePaths = [
|
182
|
+
path.join(tmpDir, `bubblewrap-${BWRAP_VERSION}`, 'bwrap'),
|
183
|
+
path.join(tmpDir, 'bwrap'),
|
184
|
+
path.join(tmpDir, 'bin', 'bwrap'),
|
185
|
+
];
|
186
|
+
|
187
|
+
for (const possiblePath of possiblePaths) {
|
188
|
+
if (fs.existsSync(possiblePath)) {
|
189
|
+
fs.copyFileSync(possiblePath, binaryPath);
|
190
|
+
fs.chmodSync(binaryPath, 0o755);
|
191
|
+
console.log('✅ Extracted pre-built binary');
|
192
|
+
return true;
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
throw new Error('Binary not found in tarball');
|
197
|
+
} finally {
|
198
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
66
202
|
async function buildFromSource(binaryPath) {
|
67
203
|
console.log('🔨 Building bubblewrap from source (SQLite-style)...');
|
68
204
|
|
@@ -193,5 +329,68 @@ exit 1
|
|
193
329
|
downloadAndBuild().catch(error => {
|
194
330
|
console.error('❌ Build failed:', error.message);
|
195
331
|
console.log('💡 SandboxBox will still work with system bubblewrap if available');
|
332
|
+
|
333
|
+
// Create a minimal fallback as last resort
|
334
|
+
createMinimalBubblewrap(path.join(BINARY_DIR, 'bwrap'));
|
196
335
|
process.exit(0); // Don't fail npm install
|
197
|
-
});
|
336
|
+
});
|
337
|
+
|
338
|
+
function createMinimalBubblewrap(binaryPath) {
|
339
|
+
console.log('🔧 Creating minimal bubblewrap fallback...');
|
340
|
+
|
341
|
+
const minimalBwrap = `#!/bin/bash
|
342
|
+
# Minimal bubblewrap fallback for SandboxBox
|
343
|
+
# This provides basic namespace isolation functionality
|
344
|
+
|
345
|
+
# Handle --version flag for compatibility
|
346
|
+
if [[ "$1" == "--version" ]]; then
|
347
|
+
echo "bubblewrap 0.11.0 (minimal fallback for SandboxBox)"
|
348
|
+
exit 0
|
349
|
+
fi
|
350
|
+
|
351
|
+
# Handle --help flag
|
352
|
+
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
|
353
|
+
echo "bubblewrap - minimal fallback version"
|
354
|
+
echo ""
|
355
|
+
echo "⚠️ This is a minimal fallback for SandboxBox"
|
356
|
+
echo "💡 For full functionality, install bubblewrap:"
|
357
|
+
echo " sudo apt-get install bubblewrap"
|
358
|
+
echo ""
|
359
|
+
echo "Usage: bwrap [options] -- command [args]"
|
360
|
+
exit 0
|
361
|
+
fi
|
362
|
+
|
363
|
+
echo "⚠️ Using minimal bubblewrap fallback"
|
364
|
+
echo "💡 For full functionality, install bubblewrap:"
|
365
|
+
echo " sudo apt-get install bubblewrap"
|
366
|
+
echo ""
|
367
|
+
|
368
|
+
# Filter out bubblewrap-specific options that unshare doesn't support
|
369
|
+
ARGS=()
|
370
|
+
for arg in "$@"; do
|
371
|
+
case "$arg" in
|
372
|
+
--ro-bind|--bind|--dev-bind|--proc|--tmpfs|--symlink|--dir|--file|--setenv|--die-with-parent|--new-session|--share-net|--unshare-net|--unshare-pid|--unshare-ipc|--unshare-uts|--unshare-cgroup|--unshare-user)
|
373
|
+
# Skip bubblewrap-specific options
|
374
|
+
;;
|
375
|
+
*)
|
376
|
+
ARGS+=("$arg")
|
377
|
+
;;
|
378
|
+
esac
|
379
|
+
done
|
380
|
+
|
381
|
+
# Basic namespace isolation using unshare
|
382
|
+
exec unshare \\
|
383
|
+
--pid \\
|
384
|
+
--mount \\
|
385
|
+
--uts \\
|
386
|
+
--ipc \\
|
387
|
+
--net \\
|
388
|
+
--fork \\
|
389
|
+
--mount-proc \\
|
390
|
+
"\${ARGS[@]}"
|
391
|
+
`;
|
392
|
+
|
393
|
+
fs.writeFileSync(binaryPath, minimalBwrap);
|
394
|
+
fs.chmodSync(binaryPath, 0o755);
|
395
|
+
console.log('✅ Created minimal bubblewrap fallback');
|
396
|
+
}
|
package/test-cli.js
CHANGED
File without changes
|