sandboxedjs 0.1.10 → 0.1.11
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 +0 -155
- package/bin/sandboxedjs.mjs +4 -10
- package/dist/index.cjs +129 -4702
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -595
- package/dist/index.d.ts +2 -595
- package/dist/index.js +130 -4685
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,8 +45,6 @@ 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) |
|
|
49
|
-
| **x86-64 Linux** | Native ELF binaries run on an emulated CPU, with a Linux syscall layer over the same filesystem |
|
|
50
48
|
| **FFmpeg** | `ffmpeg` and `ffprobe` (FFmpeg 5.1) reading and writing container files directly — [optional install](#video-and-audio) |
|
|
51
49
|
| **`/proc`** | Live and synthesised — `ps`, `top`, `free` and `uptime` all read the same source |
|
|
52
50
|
| **Networking** | Virtual interfaces, `/etc/hosts` resolution, in-container HTTP servers, an optional bridge to a real host port |
|
|
@@ -261,29 +259,6 @@ await box.waitForPort(3000);
|
|
|
261
259
|
`yarn` and `pnpm` map onto the same installer. `apt`/`apt-get` reports the built-in package set
|
|
262
260
|
rather than pretending to download Debian archives.
|
|
263
261
|
|
|
264
|
-
#### Global installs
|
|
265
|
-
|
|
266
|
-
`npm install -g` works the way it does on a real system. The package goes to
|
|
267
|
-
`/usr/local/lib/node_modules`, every binary its `bin` field declares is linked into
|
|
268
|
-
`/usr/local/bin`, and that directory is already on `$PATH`:
|
|
269
|
-
|
|
270
|
-
```
|
|
271
|
-
$ npm i -g prettier
|
|
272
|
-
/usr/local/bin/prettier -> /usr/local/lib/node_modules/prettier
|
|
273
|
-
|
|
274
|
-
$ prettier --version
|
|
275
|
-
3.6.2
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Install hooks (`preinstall`, `install`, `postinstall`) run, because for a good number of packages
|
|
279
|
-
the hook is what generates the file `bin` points at. A hook that fails is reported and skipped
|
|
280
|
-
rather than failing the install — the usual cause is a `node-gyp` build that was never going to
|
|
281
|
-
work here, and the package's JavaScript is often fine without it. `--ignore-scripts` opts out, and
|
|
282
|
-
`npm rebuild [-g] [package]` re-runs the hooks and redoes the links without touching the network.
|
|
283
|
-
|
|
284
|
-
`npm uninstall -g`, `npm ls -g`, `npm root -g`, `npm prefix -g` and `npm bin -g` all operate on
|
|
285
|
-
the same prefix.
|
|
286
|
-
|
|
287
262
|
#### npx
|
|
288
263
|
|
|
289
264
|
`npx` works the way you expect: it runs a local binary if there is one, and otherwise installs
|
|
@@ -442,96 +417,6 @@ The bundled standard library includes `json`, `re`, `os`, `sys`, `math`, `random
|
|
|
442
417
|
MicroPython, not CPython — there is no `numpy`, and `pip` can install pure-Python wheels only
|
|
443
418
|
(and only with outbound networking enabled).
|
|
444
419
|
|
|
445
|
-
### Running compiled software
|
|
446
|
-
|
|
447
|
-
Three kinds of program run here. JavaScript and WebAssembly run natively. Native **x86-64 Linux
|
|
448
|
-
binaries run on an emulated CPU** — the container ships an x86-64 interpreter and a Linux syscall
|
|
449
|
-
layer that reads and writes the same filesystem as everything else.
|
|
450
|
-
|
|
451
|
-
```
|
|
452
|
-
$ file ./mytool
|
|
453
|
-
./mytool: ELF 64-bit LSB executable, x86-64, statically linked
|
|
454
|
-
$ ./mytool --version
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
A statically linked binary needs nothing but the file. Build one with:
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o mytool
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
Rust: `cargo build --target x86_64-unknown-linux-musl`. C: `musl-gcc -static`.
|
|
464
|
-
|
|
465
|
-
`x86 info` reports what a binary needs before you run it, and `x86 trace` prints every syscall,
|
|
466
|
-
which is how you find out where something stalls:
|
|
467
|
-
|
|
468
|
-
```
|
|
469
|
-
$ x86 info ./mytool
|
|
470
|
-
machine: x86-64
|
|
471
|
-
linkage: static
|
|
472
|
-
runnable: yes
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
#### Dynamically linked binaries
|
|
476
|
-
|
|
477
|
-
A binary linked against glibc needs its loader and libraries — and those are **not simulated**.
|
|
478
|
-
They are ordinary x86-64 code, and a machine that can run the program can run them too. Copy them
|
|
479
|
-
in from any Linux system and the binary works:
|
|
480
|
-
|
|
481
|
-
```
|
|
482
|
-
/lib64/ld-linux-x86-64.so.2
|
|
483
|
-
/lib/x86_64-linux-gnu/libc.so.6
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
That is a couple of megabytes, not a distribution. If they are missing, the failure names the exact
|
|
487
|
-
file it wanted rather than failing vaguely.
|
|
488
|
-
|
|
489
|
-
#### WebAssembly
|
|
490
|
-
|
|
491
|
-
Anything targeting `wasm32-wasi` is also a program, with no emulation and no speed penalty:
|
|
492
|
-
|
|
493
|
-
```
|
|
494
|
-
$ cp jq.wasm /usr/local/bin/jq && chmod +x /usr/local/bin/jq
|
|
495
|
-
$ echo '{"name":"ada"}' | jq .name
|
|
496
|
-
"ada"
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
The kernel decides what a file is by its magic bytes, exactly as `binfmt_misc` does on Linux, so
|
|
500
|
-
every one of these is dispatched through the same lookup as `grep` or a shell script. The name on
|
|
501
|
-
`$PATH` need not match the format, `argv[0]` is the name it was invoked as, and all of them see the
|
|
502
|
-
container's real filesystem — a binary's `open("data.csv")` opens the file the shell just created.
|
|
503
|
-
|
|
504
|
-
`wasm info <file>` reports what a module was built for.
|
|
505
|
-
|
|
506
|
-
#### What does not run, and how it tells you
|
|
507
|
-
|
|
508
|
-
| File | What happens |
|
|
509
|
-
|---|---|
|
|
510
|
-
| Static x86-64 Linux ELF | Runs, emulated |
|
|
511
|
-
| Dynamic x86-64 Linux ELF | Runs once its loader and libraries are present; names them if not |
|
|
512
|
-
| `wasm32-wasi` module | Runs natively |
|
|
513
|
-
| ELF for another architecture | Named by architecture and refused |
|
|
514
|
-
| Mach-O / PE binary | Named as macOS or Windows; another OS is not emulated |
|
|
515
|
-
| Emscripten or wasm-bindgen `.wasm` | Points at the JavaScript loader beside it |
|
|
516
|
-
| WASI preview 2 component | Points at `jco transpile` |
|
|
517
|
-
| `GOOS=js` build | Points at `GOOS=wasip1` |
|
|
518
|
-
|
|
519
|
-
The same honesty applies to npm. A package whose `bin` is a compiled executable, whose payload
|
|
520
|
-
lives in platform-gated `optionalDependencies`, or which declares a `cpu` constraint, is diagnosed
|
|
521
|
-
at install time rather than at first run.
|
|
522
|
-
|
|
523
|
-
#### Limits of the emulator
|
|
524
|
-
|
|
525
|
-
- **Speed.** An interpreter, not a JIT — expect it to be one to two orders of magnitude slower
|
|
526
|
-
than native. Fine for a CLI, wrong for a compiler.
|
|
527
|
-
- **Threads are cooperative.** `clone` creates a real thread with its own registers and stack, but
|
|
528
|
-
only one runs at a time, and switches happen at `futex`, `sched_yield` and `nanosleep`. A thread
|
|
529
|
-
that spins on memory without entering the kernel keeps the machine to itself.
|
|
530
|
-
- **Integer and the common vector instructions only.** Moves, bitwise operations, byte compares
|
|
531
|
-
and scalar floating point are implemented; the wider AVX paths are not. An unimplemented
|
|
532
|
-
instruction names itself and dumps the registers instead of misbehaving quietly.
|
|
533
|
-
- **No sockets, no signals, no `fork`/`exec`.** A guest cannot start another process.
|
|
534
|
-
|
|
535
420
|
### Filesystem from the host
|
|
536
421
|
|
|
537
422
|
```ts
|
|
@@ -792,13 +677,6 @@ Honest list of what does not work:
|
|
|
792
677
|
pipeline as WebAssembly and runs fine, dev server included, as do Express, Koa, Fastify-style
|
|
793
678
|
apps and plain `http` servers.
|
|
794
679
|
- **Python is MicroPython**, so C extensions (`numpy`, `pandas`, `cryptography`) are unavailable.
|
|
795
|
-
- **The x86-64 emulator is an interpreter**, so native binaries run one to two orders of magnitude
|
|
796
|
-
slower than they would on a real CPU, its threads are cooperative rather than parallel, and it
|
|
797
|
-
implements the integer and common vector instructions rather than all of AVX. See
|
|
798
|
-
[Running compiled software](#running-compiled-software).
|
|
799
|
-
- **macOS and Windows binaries cannot run.** Another instruction set is a matter of emulation;
|
|
800
|
-
another operating system is not.
|
|
801
|
-
- **No WebAssembly threads.** Modules built with shared memory are refused at startup.
|
|
802
680
|
- **No real sockets.** HTTP servers work through the request proxy; raw TCP/UDP does not.
|
|
803
681
|
- **No real processes.** Processes are cooperative async tasks: `kill -9` cannot interrupt a
|
|
804
682
|
tight synchronous loop, and `SIGSTOP` only marks state.
|
|
@@ -871,39 +749,6 @@ await box.exec("greet there | tr a-z A-Z"); // → HELLO THERE
|
|
|
871
749
|
|
|
872
750
|
It becomes a real file in `/usr/bin`, so `which greet`, `man greet` and shebang dispatch all work.
|
|
873
751
|
|
|
874
|
-
### Adding an executable format
|
|
875
|
-
|
|
876
|
-
The kernel identifies executables by magic bytes and dispatches to a registered handler, the way
|
|
877
|
-
`binfmt_misc` does. WebAssembly is one such handler; so is each native format. Registering your own
|
|
878
|
-
is the supported way to teach the container a new kind of binary — including plugging in an
|
|
879
|
-
emulator:
|
|
880
|
-
|
|
881
|
-
```ts
|
|
882
|
-
import { createContainer, type ExecFormat } from "sandboxedjs";
|
|
883
|
-
|
|
884
|
-
const box = await createContainer();
|
|
885
|
-
|
|
886
|
-
const upx: ExecFormat = {
|
|
887
|
-
name: "upx",
|
|
888
|
-
priority: 5, // consulted before the ELF handler
|
|
889
|
-
matches: (head) => head.subarray(0, 4).toString() === "UPX!",
|
|
890
|
-
describe: () => "UPX-compressed executable",
|
|
891
|
-
run: async (ctx, path) => {
|
|
892
|
-
/* decompress, then hand off however you like */
|
|
893
|
-
return 0;
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
box.kernel.formats.register(upx);
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
A handler with no `run` is still useful: `explain(head, path)` returns the lines printed in place
|
|
901
|
-
of `Exec format error`, which is how ELF, Mach-O and PE files report what they are.
|
|
902
|
-
|
|
903
|
-
The x86-64 handler in `src/runtime/x86/` is the worked example: it matches on `\x7fELF` and its
|
|
904
|
-
`run` boots an interpreter, a Linux syscall layer and a process image. A handler for another
|
|
905
|
-
architecture would sit alongside it without the kernel changing at all.
|
|
906
|
-
|
|
907
752
|
## Examples
|
|
908
753
|
|
|
909
754
|
See [`examples/`](./examples): a REPL, an Express API, a React app, a Python data pipeline, an
|
package/bin/sandboxedjs.mjs
CHANGED
|
@@ -184,10 +184,8 @@ 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,
|
|
187
|
+
row("node", node, "npm, 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"),
|
|
190
|
-
row("x86-64", "emulated", "static Linux ELF binaries run too"),
|
|
191
189
|
row("ffmpeg", ffmpeg, ffmpeg ? "video and audio" : "npm install @ffmpeg/core"),
|
|
192
190
|
row(
|
|
193
191
|
"network",
|
|
@@ -200,15 +198,11 @@ async function printReplBanner(box, opts) {
|
|
|
200
198
|
`${DIM}Try:${RESET}`,
|
|
201
199
|
" cat /etc/os-release && ls /usr/bin | head",
|
|
202
200
|
' node -e "require(\'http\').createServer((_,r)=>r.end(\'hi\')).listen(3000)" &',
|
|
203
|
-
" npm i -g prettier && prettier --version",
|
|
204
201
|
" npm create vite@latest app -- --template react && cd app && npm i && npm run dev",
|
|
205
|
-
"
|
|
206
|
-
" x86 info ./linux-binary && ./linux-binary # native ELF, emulated",
|
|
202
|
+
" ffmpeg -f lavfi -i testsrc=size=64x64:rate=5:duration=1 out.mp4 && ls -l out.mp4",
|
|
207
203
|
"",
|
|
208
|
-
`${DIM}
|
|
209
|
-
|
|
210
|
-
"ones also need their loader and libc copied in. macOS and Windows binaries",
|
|
211
|
-
`cannot run and say so by name. No raw sockets.${RESET}`,
|
|
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}`,
|
|
212
206
|
"",
|
|
213
207
|
`${DIM}Ctrl-D or \`exit\` to leave.${RESET}`,
|
|
214
208
|
"",
|