sandboxedjs 0.1.4 → 0.1.5
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/README.md +19 -0
- package/bin/sandboxedjs.mjs +83 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -459,6 +459,7 @@ and the usual control keys.
|
|
|
459
459
|
## Command line
|
|
460
460
|
|
|
461
461
|
```bash
|
|
462
|
+
npx sandboxedjs --repl # explore: what runs, what doesn't
|
|
462
463
|
npx sandboxedjs # interactive shell
|
|
463
464
|
npx sandboxedjs -c 'ls -la /etc' # one command
|
|
464
465
|
npx sandboxedjs script.sh # run a script
|
|
@@ -468,6 +469,24 @@ npx sandboxedjs --network -p 3000 # allow outbound, publish a port
|
|
|
468
469
|
|
|
469
470
|
Run `sandboxedjs --help` for the full list.
|
|
470
471
|
|
|
472
|
+
### Trying it out
|
|
473
|
+
|
|
474
|
+
`--repl` drops you into a shell with a summary of what this container can actually do. Every
|
|
475
|
+
line is measured on the spot rather than claimed, so a runtime that is missing says so:
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
sandboxedjs — a Linux-like container inside Node.js
|
|
479
|
+
|
|
480
|
+
node v22.12.0 npm, require, http servers
|
|
481
|
+
python3 MicroPython no C extensions (numpy, pandas)
|
|
482
|
+
ffmpeg 5.1.4 video and audio
|
|
483
|
+
network on npm install reaches the real registry
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
It needs no project and no files — it is meant for finding the edges. Because installing
|
|
487
|
+
packages is most of what people want to test, `--repl` allows outbound access; pass
|
|
488
|
+
`--no-network` to take it away and watch what breaks.
|
|
489
|
+
|
|
471
490
|
## Security
|
|
472
491
|
|
|
473
492
|
The container has no access to your filesystem, environment, or network unless you grant it:
|
package/bin/sandboxedjs.mjs
CHANGED
|
@@ -19,10 +19,13 @@ const USAGE = `sandboxedjs — a Linux-like container inside Node.js
|
|
|
19
19
|
|
|
20
20
|
Usage:
|
|
21
21
|
sandboxedjs [options] start an interactive shell
|
|
22
|
+
sandboxedjs --repl explore the sandbox interactively
|
|
22
23
|
sandboxedjs [options] -c <command> run a command and exit
|
|
23
24
|
sandboxedjs [options] <script.sh> run a shell script and exit
|
|
24
25
|
|
|
25
26
|
Options:
|
|
27
|
+
--repl interactive shell with a summary of what is
|
|
28
|
+
available; enables --network (see --no-network)
|
|
26
29
|
-c, --command <cmd> run <cmd> and exit
|
|
27
30
|
-v, --volume <src:dst> copy a host directory into the container (repeatable)
|
|
28
31
|
-o, --out <src:dst> copy a container path back out on exit (repeatable)
|
|
@@ -32,6 +35,7 @@ Options:
|
|
|
32
35
|
-p, --publish <port> publish a container port on the host (repeatable)
|
|
33
36
|
-H, --hostname <name> container hostname (default sandbox)
|
|
34
37
|
--network allow outbound network access (off by default)
|
|
38
|
+
--no-network deny outbound access, even under --repl
|
|
35
39
|
--allow <host> allow one outbound host (implies --network, repeatable)
|
|
36
40
|
--memory <bytes> RAM reported inside the container
|
|
37
41
|
--cpus <n> CPU count reported inside the container
|
|
@@ -100,9 +104,15 @@ function parseCli(argv) {
|
|
|
100
104
|
case "--hostname":
|
|
101
105
|
opts.hostname = value();
|
|
102
106
|
break;
|
|
107
|
+
case "--repl":
|
|
108
|
+
opts.repl = true;
|
|
109
|
+
break;
|
|
103
110
|
case "--network":
|
|
104
111
|
opts.network = true;
|
|
105
112
|
break;
|
|
113
|
+
case "--no-network":
|
|
114
|
+
opts.noNetwork = true;
|
|
115
|
+
break;
|
|
106
116
|
case "--allow":
|
|
107
117
|
opts.network = true;
|
|
108
118
|
opts.allow.push(value());
|
|
@@ -138,9 +148,78 @@ async function packageVersion() {
|
|
|
138
148
|
}
|
|
139
149
|
}
|
|
140
150
|
|
|
151
|
+
const DIM = "[2m";
|
|
152
|
+
const BOLD = "[1m";
|
|
153
|
+
const RESET = "[0m";
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Report what this container can actually do, before handing over the prompt.
|
|
157
|
+
*
|
|
158
|
+
* Every line is measured rather than asserted: each runtime is asked for its
|
|
159
|
+
* version, and a runtime that is missing says so. The point of `--repl` is to
|
|
160
|
+
* find out what runs here, and a banner that listed capabilities from a
|
|
161
|
+
* hard-coded string would be the one thing that could quietly lie about it.
|
|
162
|
+
*/
|
|
163
|
+
async function printReplBanner(box, opts) {
|
|
164
|
+
const probe = async (command, extract) => {
|
|
165
|
+
try {
|
|
166
|
+
const result = await box.exec(command, { timeoutMs: 60_000 });
|
|
167
|
+
if (result.exitCode !== 0) return null;
|
|
168
|
+
return extract(`${result.stdout}${result.stderr}`)?.trim() ?? null;
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const [node, python, ffmpeg] = await Promise.all([
|
|
175
|
+
probe("node -v", (out) => out.match(/v[\d.]+/)?.[0]),
|
|
176
|
+
probe("python3 -c \"import sys; print(sys.version.split()[0])\"", (out) => out),
|
|
177
|
+
probe("ffmpeg -version", (out) => out.match(/ffmpeg version (\S+)/)?.[1]),
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
const row = (name, value, note) =>
|
|
181
|
+
` ${name.padEnd(10)}${(value ?? "not installed").padEnd(14)}${DIM}${note}${RESET}`;
|
|
182
|
+
|
|
183
|
+
const lines = [
|
|
184
|
+
"",
|
|
185
|
+
`${BOLD}sandboxedjs${RESET} — a Linux-like container inside Node.js`,
|
|
186
|
+
"",
|
|
187
|
+
row("node", node, "npm, require, http servers"),
|
|
188
|
+
row("python3", python && `MicroPython`, "no C extensions (numpy, pandas)"),
|
|
189
|
+
row("ffmpeg", ffmpeg, ffmpeg ? "video and audio" : "npm install @ffmpeg/core"),
|
|
190
|
+
row(
|
|
191
|
+
"network",
|
|
192
|
+
opts.network ? "on" : "off",
|
|
193
|
+
opts.network
|
|
194
|
+
? "npm install reaches the real registry"
|
|
195
|
+
: "outbound denied — drop --no-network to allow it",
|
|
196
|
+
),
|
|
197
|
+
"",
|
|
198
|
+
`${DIM}Try:${RESET}`,
|
|
199
|
+
" cat /etc/os-release && ls /usr/bin | head",
|
|
200
|
+
' node -e "require(\'http\').createServer((_,r)=>r.end(\'hi\')).listen(3000)" &',
|
|
201
|
+
" npm create vite@latest app -- --template react && cd app && npm i && npm run dev",
|
|
202
|
+
" ffmpeg -f lavfi -i testsrc=size=64x64:rate=5:duration=1 out.mp4 && ls -l out.mp4",
|
|
203
|
+
"",
|
|
204
|
+
`${DIM}Known limits: no native binaries, no raw sockets, and esbuild-based`,
|
|
205
|
+
`toolchains (Vite 4 and its contemporaries) cannot start. Current Vite works.${RESET}`,
|
|
206
|
+
"",
|
|
207
|
+
`${DIM}Ctrl-D or \`exit\` to leave.${RESET}`,
|
|
208
|
+
"",
|
|
209
|
+
];
|
|
210
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
211
|
+
}
|
|
212
|
+
|
|
141
213
|
async function main() {
|
|
142
214
|
const opts = parseCli(process.argv.slice(2));
|
|
143
215
|
|
|
216
|
+
/* The REPL exists to answer "what actually runs in here?", and the honest
|
|
217
|
+
* answer involves installing packages — so it opens the network unless the
|
|
218
|
+
* caller says otherwise, and the banner states that plainly rather than
|
|
219
|
+
* leaving it to be discovered. */
|
|
220
|
+
if (opts.repl && !opts.noNetwork) opts.network = true;
|
|
221
|
+
if (opts.noNetwork) opts.network = false;
|
|
222
|
+
|
|
144
223
|
if (opts.help) {
|
|
145
224
|
process.stdout.write(USAGE);
|
|
146
225
|
return 0;
|
|
@@ -244,10 +323,14 @@ async function main() {
|
|
|
244
323
|
return finish(result.exitCode);
|
|
245
324
|
}
|
|
246
325
|
|
|
326
|
+
if (opts.repl) await printReplBanner(box, opts);
|
|
327
|
+
|
|
247
328
|
const session = box.session();
|
|
248
329
|
await new Promise((resolve) => {
|
|
249
330
|
const terminal = new Terminal(session, {
|
|
250
331
|
write: (data) => process.stdout.write(data),
|
|
332
|
+
// The REPL banner already introduced the container, and says more.
|
|
333
|
+
...(opts.repl ? { motd: false } : {}),
|
|
251
334
|
columns: process.stdout.columns ?? 80,
|
|
252
335
|
rows: process.stdout.rows ?? 24,
|
|
253
336
|
onExit: (code) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandboxedjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A Linux-like container that runs entirely inside Node.js — POSIX shell, ~140 coreutils, Node.js and Python runtimes, virtual filesystem and networking. No Docker, no VM, no native modules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|