sandboxedjs 0.1.7 → 0.1.8
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 +95 -0
- package/bin/sandboxedjs.mjs +8 -4
- package/dist/index.cjs +1401 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +1401 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Node 18.17+. Everything is pure JavaScript and WebAssembly — no compilation st
|
|
|
45
45
|
| **Coreutils** | 154 programs — `ls cat cp mv rm mkdir grep sed awk find head tail sort uniq wc cut tr tee xargs chmod chown ln du df ps tar gzip base64 sha256sum diff curl wget` and the rest — plus 54 shell builtins |
|
|
46
46
|
| **Node.js** | Real Node semantics via Nodepod — `require`, npm packages, `http`, `fs`, streams, `worker_threads` |
|
|
47
47
|
| **Python** | MicroPython on WebAssembly, mounted on the *same* filesystem |
|
|
48
|
+
| **WebAssembly** | Any `wasm32-wasi` binary is an executable — [drop it in `$PATH` and run it](#running-compiled-software) |
|
|
48
49
|
| **FFmpeg** | `ffmpeg` and `ffprobe` (FFmpeg 5.1) reading and writing container files directly — [optional install](#video-and-audio) |
|
|
49
50
|
| **`/proc`** | Live and synthesised — `ps`, `top`, `free` and `uptime` all read the same source |
|
|
50
51
|
| **Networking** | Virtual interfaces, `/etc/hosts` resolution, in-container HTTP servers, an optional bridge to a real host port |
|
|
@@ -259,6 +260,29 @@ await box.waitForPort(3000);
|
|
|
259
260
|
`yarn` and `pnpm` map onto the same installer. `apt`/`apt-get` reports the built-in package set
|
|
260
261
|
rather than pretending to download Debian archives.
|
|
261
262
|
|
|
263
|
+
#### Global installs
|
|
264
|
+
|
|
265
|
+
`npm install -g` works the way it does on a real system. The package goes to
|
|
266
|
+
`/usr/local/lib/node_modules`, every binary its `bin` field declares is linked into
|
|
267
|
+
`/usr/local/bin`, and that directory is already on `$PATH`:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
$ npm i -g prettier
|
|
271
|
+
/usr/local/bin/prettier -> /usr/local/lib/node_modules/prettier
|
|
272
|
+
|
|
273
|
+
$ prettier --version
|
|
274
|
+
3.6.2
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Install hooks (`preinstall`, `install`, `postinstall`) run, because for a good number of packages
|
|
278
|
+
the hook is what generates the file `bin` points at. A hook that fails is reported and skipped
|
|
279
|
+
rather than failing the install — the usual cause is a `node-gyp` build that was never going to
|
|
280
|
+
work here, and the package's JavaScript is often fine without it. `--ignore-scripts` opts out, and
|
|
281
|
+
`npm rebuild [-g] [package]` re-runs the hooks and redoes the links without touching the network.
|
|
282
|
+
|
|
283
|
+
`npm uninstall -g`, `npm ls -g`, `npm root -g`, `npm prefix -g` and `npm bin -g` all operate on
|
|
284
|
+
the same prefix.
|
|
285
|
+
|
|
262
286
|
#### npx
|
|
263
287
|
|
|
264
288
|
`npx` works the way you expect: it runs a local binary if there is one, and otherwise installs
|
|
@@ -417,6 +441,72 @@ The bundled standard library includes `json`, `re`, `os`, `sys`, `math`, `random
|
|
|
417
441
|
MicroPython, not CPython — there is no `numpy`, and `pip` can install pure-Python wheels only
|
|
418
442
|
(and only with outbound networking enabled).
|
|
419
443
|
|
|
444
|
+
### Running compiled software
|
|
445
|
+
|
|
446
|
+
The container has no CPU, so it cannot execute machine code — but it can execute WebAssembly, and
|
|
447
|
+
most compiled software can be built for it. Anything targeting `wasm32-wasi` is a program here,
|
|
448
|
+
with no registration, wrapper or configuration:
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
$ cp jq.wasm /usr/local/bin/jq && chmod +x /usr/local/bin/jq
|
|
452
|
+
$ echo '{"name":"ada"}' | jq .name
|
|
453
|
+
"ada"
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
The kernel decides what a file is by its magic bytes, exactly as `binfmt_misc` does on Linux, so a
|
|
457
|
+
`.wasm` file is dispatched through the same lookup as `grep` or a shell script. The name on `$PATH`
|
|
458
|
+
does not have to end in `.wasm`, `argv[0]` is the name it was invoked as, and the module runs
|
|
459
|
+
against the container's real filesystem — `path_open("data.csv")` opens the file the shell just
|
|
460
|
+
created, and what it writes is there for `cat` afterwards.
|
|
461
|
+
|
|
462
|
+
To produce a binary that works, target WASI. From Go:
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
GOOS=wasip1 GOARCH=wasm go build -o mytool
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
From Rust: `cargo build --target wasm32-wasip1`. From C or C++: the `wasi-sdk` clang. Zig:
|
|
469
|
+
`-target wasm32-wasi`.
|
|
470
|
+
|
|
471
|
+
`wasm info <file>` reports what a module was built for before you try to run it:
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
$ wasm info mytool
|
|
475
|
+
flavour: wasi
|
|
476
|
+
imports: wasi_snapshot_preview1
|
|
477
|
+
threads: no
|
|
478
|
+
entry: _start
|
|
479
|
+
runnable: yes
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**What does not run, and how it tells you.** Every executable format is recognised by name, so a
|
|
483
|
+
failure says what the file actually is instead of `Exec format error`:
|
|
484
|
+
|
|
485
|
+
| File | What happens |
|
|
486
|
+
|---|---|
|
|
487
|
+
| `wasm32-wasi` module | Runs |
|
|
488
|
+
| ELF / Mach-O / PE binary | Named by architecture, with a note that a WebAssembly build would work |
|
|
489
|
+
| Emscripten or wasm-bindgen `.wasm` | Points at the JavaScript loader generated beside it |
|
|
490
|
+
| WASI preview 2 component | Points at `jco transpile` |
|
|
491
|
+
| `GOOS=js` build | Points at `GOOS=wasip1` |
|
|
492
|
+
| Threaded module (shared memory) | Refused up front rather than failing deep inside startup |
|
|
493
|
+
|
|
494
|
+
The same honesty applies to npm. A package whose `bin` is a compiled executable, or whose real
|
|
495
|
+
payload is one of several platform-gated `optionalDependencies`, is diagnosed at install time
|
|
496
|
+
rather than at first run:
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
$ npm i -g opencode-ai
|
|
500
|
+
npm warn opencode-ai: 'opencode' points at ./bin/opencode.exe, which is a compiled
|
|
501
|
+
npm warn executable, not JavaScript.
|
|
502
|
+
npm warn Its real binary comes from 12 platform packages (opencode-linux-arm64, …),
|
|
503
|
+
npm warn each of which is native machine code for one OS and CPU.
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Limits worth knowing: no threads (`wasi-threads` modules are refused), no sockets (`sock_*`
|
|
507
|
+
returns `ENOTSUP`), and `poll_oneoff` supports clock subscriptions only. Standard input is read to
|
|
508
|
+
completion before a module starts, because WASI's read is synchronous.
|
|
509
|
+
|
|
420
510
|
### Filesystem from the host
|
|
421
511
|
|
|
422
512
|
```ts
|
|
@@ -677,6 +767,11 @@ Honest list of what does not work:
|
|
|
677
767
|
pipeline as WebAssembly and runs fine, dev server included, as do Express, Koa, Fastify-style
|
|
678
768
|
apps and plain `http` servers.
|
|
679
769
|
- **Python is MicroPython**, so C extensions (`numpy`, `pandas`, `cryptography`) are unavailable.
|
|
770
|
+
- **No machine code.** ELF, Mach-O and PE binaries cannot run, and neither can npm packages that
|
|
771
|
+
are a wrapper around one. A `wasm32-wasi` build of the same tool does run — see
|
|
772
|
+
[Running compiled software](#running-compiled-software) — but nothing converts a native binary
|
|
773
|
+
into one for you.
|
|
774
|
+
- **No WebAssembly threads.** Modules built with shared memory are refused at startup.
|
|
680
775
|
- **No real sockets.** HTTP servers work through the request proxy; raw TCP/UDP does not.
|
|
681
776
|
- **No real processes.** Processes are cooperative async tasks: `kill -9` cannot interrupt a
|
|
682
777
|
tight synchronous loop, and `SIGSTOP` only marks state.
|
package/bin/sandboxedjs.mjs
CHANGED
|
@@ -184,8 +184,9 @@ async function printReplBanner(box, opts) {
|
|
|
184
184
|
"",
|
|
185
185
|
`${BOLD}sandboxedjs${RESET} — a Linux-like container inside Node.js`,
|
|
186
186
|
"",
|
|
187
|
-
row("node", node, "npm, require, http servers"),
|
|
187
|
+
row("node", node, "npm, npm -g, require, http servers"),
|
|
188
188
|
row("python3", python && `MicroPython`, "no C extensions (numpy, pandas)"),
|
|
189
|
+
row("wasm", "wasi p1", "wasm32-wasi binaries run as commands"),
|
|
189
190
|
row("ffmpeg", ffmpeg, ffmpeg ? "video and audio" : "npm install @ffmpeg/core"),
|
|
190
191
|
row(
|
|
191
192
|
"network",
|
|
@@ -198,11 +199,14 @@ async function printReplBanner(box, opts) {
|
|
|
198
199
|
`${DIM}Try:${RESET}`,
|
|
199
200
|
" cat /etc/os-release && ls /usr/bin | head",
|
|
200
201
|
' node -e "require(\'http\').createServer((_,r)=>r.end(\'hi\')).listen(3000)" &',
|
|
202
|
+
" npm i -g prettier && prettier --version",
|
|
201
203
|
" npm create vite@latest app -- --template react && cd app && npm i && npm run dev",
|
|
202
|
-
"
|
|
204
|
+
" wasm info tool.wasm && chmod +x tool.wasm && ./tool.wasm",
|
|
203
205
|
"",
|
|
204
|
-
`${DIM}
|
|
205
|
-
|
|
206
|
+
`${DIM}Software runs here if it is JavaScript or WebAssembly. Any wasm32-wasi`,
|
|
207
|
+
"build is a command: drop it in /usr/local/bin, chmod +x, call it by name.",
|
|
208
|
+
"Compiled native binaries (ELF, Mach-O, PE) cannot run and say so by name —",
|
|
209
|
+
`so can npm CLIs that are only a wrapper around one. No raw sockets.${RESET}`,
|
|
206
210
|
"",
|
|
207
211
|
`${DIM}Ctrl-D or \`exit\` to leave.${RESET}`,
|
|
208
212
|
"",
|