sandboxedjs 0.1.23 → 0.1.25
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 +55 -49
- package/dist/index.cjs +968 -5523
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -33
- package/dist/index.d.ts +31 -33
- package/dist/index.js +969 -5524
- package/dist/index.js.map +1 -1
- package/package.json +1 -13
- package/dist/integrations/server.cjs +0 -24
- package/dist/integrations/server.cjs.map +0 -1
- package/dist/integrations/server.d.cts +0 -2
- package/dist/integrations/server.d.ts +0 -2
- package/dist/integrations/server.js +0 -3
- package/dist/integrations/server.js.map +0 -1
- package/dist/integrations/vite.cjs +0 -22
- package/dist/integrations/vite.cjs.map +0 -1
- package/dist/integrations/vite.d.cts +0 -1
- package/dist/integrations/vite.d.ts +0 -1
- package/dist/integrations/vite.js +0 -3
- package/dist/integrations/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@ A Linux-like container that runs entirely inside a Node.js process. No Docker, n
|
|
|
4
4
|
modules — a virtual filesystem, a POSIX shell, 154 Unix programs, and both Node.js and Python
|
|
5
5
|
runtimes, all in-process.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
The Node.js runtime is its own: a module engine that loads both CommonJS and ES modules, an
|
|
8
|
+
in-memory volume, an npm-registry installer and a virtual HTTP stack, with no native modules and
|
|
9
|
+
no dependency on the host's `node_modules`.
|
|
9
10
|
|
|
10
11
|
```ts
|
|
11
12
|
import { createContainer } from "sandboxedjs";
|
|
@@ -43,7 +44,7 @@ Node 18.17+. Everything is pure JavaScript and WebAssembly — no compilation st
|
|
|
43
44
|
| **Filesystem** | Full FHS tree (`/etc`, `/usr`, `/var`, `/home`, …), permissions, ownership, symlinks, hard links, `umask`, sticky bits |
|
|
44
45
|
| **Shell** | POSIX `sh` (54 builtins) — pipelines, redirection, here-docs, globbing, brace/parameter/arithmetic/command expansion, functions, `if`/`for`/`while`/`case`/`select`, job control, traps, arrays, `[[ ]]`, `(( ))` |
|
|
45
46
|
| **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
|
-
| **Node.js** |
|
|
47
|
+
| **Node.js** | Its own module engine — `require` *and* `import`, live bindings, `exports` maps, top-level `await`, npm packages, `http`, `fs`, streams, `child_process` |
|
|
47
48
|
| **Python** | CPython 3.13 (Pyodide) on the *same* filesystem, with the real standard library |
|
|
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 |
|
|
@@ -219,10 +220,11 @@ The iframe is cross-origin, so your IDE cannot read its DOM. You can still:
|
|
|
219
220
|
#### In a pure browser IDE
|
|
220
221
|
|
|
221
222
|
`expose()` opens a real `node:http` listener, so it needs a Node host — an Electron app, or a web
|
|
222
|
-
IDE with a Node backend. With no backend at all,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
IDE with a Node backend. With no backend at all, an iframe needs a real URL to load, and giving
|
|
224
|
+
it one means a service worker that intercepts requests and routes them to `request()`. That is
|
|
225
|
+
not built yet; see [Running in a browser](#running-in-a-browser). Until it is, you can still
|
|
226
|
+
drive an in-container server programmatically through `request()` and render the result
|
|
227
|
+
yourself.
|
|
226
228
|
|
|
227
229
|
### npm and npx
|
|
228
230
|
|
|
@@ -517,10 +519,14 @@ so it can read and write any path in the container regardless of `user`:
|
|
|
517
519
|
await box.exec("node -e \"require('fs').readFileSync('/root/secret')\"", { user: "agent" }); // succeeds
|
|
518
520
|
```
|
|
519
521
|
|
|
520
|
-
Node code runs
|
|
521
|
-
uids. Treat `user` as a way to model *ordinary* multi-user behaviour, not as a
|
|
522
|
-
boundary for JavaScript you do not trust. If untrusted JavaScript must not see
|
|
523
|
-
it out of the container rather than relying on file modes.
|
|
522
|
+
Node code runs on the JavaScript runtime, which owns its own volume and has no notion of
|
|
523
|
+
container uids. Treat `user` as a way to model *ordinary* multi-user behaviour, not as a
|
|
524
|
+
privilege boundary for JavaScript you do not trust. If untrusted JavaScript must not see
|
|
525
|
+
something, keep it out of the container rather than relying on file modes.
|
|
526
|
+
|
|
527
|
+
The runtime also shares the host's JavaScript realm today rather than running in a worker, so a
|
|
528
|
+
program inside the container can reach host globals. Isolating it in a worker is planned; until
|
|
529
|
+
then, do not treat the container as a boundary against hostile code.
|
|
524
530
|
|
|
525
531
|
### And it is not a VM
|
|
526
532
|
|
|
@@ -579,9 +585,8 @@ resolve their implementation at call time (`node:zlib`/`node:crypto` on Node,
|
|
|
579
585
|
`CompressionStream`/`crypto.subtle` plus a JS MD5 in a browser), so nothing pulls a `node:`
|
|
580
586
|
builtin in when the module loads and a bundler will not fail on it.
|
|
581
587
|
|
|
582
|
-
**One call, either side.** `createContainer()`
|
|
583
|
-
|
|
584
|
-
and you do not pass a pod:
|
|
588
|
+
**One call, either side.** `createContainer()` boots its own runtime. There is no host to pick
|
|
589
|
+
and no pod to pass:
|
|
585
590
|
|
|
586
591
|
```ts
|
|
587
592
|
import { createContainer } from "sandboxedjs";
|
|
@@ -592,40 +597,32 @@ await box.exec("ls -la /"); // shell + coreutils
|
|
|
592
597
|
await box.exec("node app/index.js");
|
|
593
598
|
```
|
|
594
599
|
|
|
595
|
-
**
|
|
596
|
-
running inside the page through a service worker, and a browser will not register one from
|
|
597
|
-
`node_modules` — it has to come from your own origin at `/__sw__.js`. One line:
|
|
598
|
-
|
|
599
|
-
```ts
|
|
600
|
-
// vite.config.ts
|
|
601
|
-
import sandboxedjs from "sandboxedjs/vite";
|
|
602
|
-
export default defineConfig({ plugins: [sandboxedjs()] });
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
For anything else, `sandboxedjs/server` exports `serveSW()` (Fetch-style hosts) and
|
|
606
|
-
`serveSWNode()` (Express, Fastify, `node:http`). Skip it with
|
|
607
|
-
`createContainer({ browser: { serviceWorker: false } })`, which also disables preview iframes.
|
|
600
|
+
**Not done — three things stand between this and a browser IDE.**
|
|
608
601
|
|
|
609
|
-
|
|
610
|
-
own
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
602
|
+
- *A service worker.* A preview iframe needs a real URL, and giving it one means a worker on your
|
|
603
|
+
own origin that intercepts requests and routes them into the container. `request()` works
|
|
604
|
+
everywhere and is unaffected; only the iframe needs this. Earlier versions re-exported one from
|
|
605
|
+
the package this was built on, at `sandboxedjs/vite` and `sandboxedjs/server`. Both are gone
|
|
606
|
+
along with that dependency, and a replacement is not written yet.
|
|
607
|
+
- *Worker isolation.* The runtime executes in whichever realm you boot it from, which in a browser
|
|
608
|
+
is the main thread — so a long build blocks the UI, and container code can reach page globals.
|
|
609
|
+
`comlink` is a dependency in anticipation of this and is not used yet.
|
|
610
|
+
- *esbuild.* The runtime cannot execute any build of esbuild itself: one dlopens a compiled addon,
|
|
611
|
+
the other drives a Go program through facilities the sandbox does not have. On Node it borrows
|
|
612
|
+
the host's `esbuild-wasm`, which is why Vite's dev server works there. In a browser there is
|
|
613
|
+
nothing to borrow, so anything routed through esbuild fails until esbuild-wasm runs *inside*
|
|
614
|
+
the sandbox.
|
|
620
615
|
|
|
621
|
-
|
|
622
|
-
|
|
616
|
+
Everything else is already browser-shaped. Nothing in the runtime imports a `node:` builtin except
|
|
617
|
+
through an explicitly Node-only path that returns `null` elsewhere, compression and hashing pick
|
|
618
|
+
their implementation at call time, and the module engine, volume, HTTP stack and npm installer are
|
|
619
|
+
built on `fetch`, `acorn`, `resolve.exports`, `@noble/hashes` and `pako`.
|
|
623
620
|
|
|
624
621
|
**What does not work in a browser:**
|
|
625
622
|
|
|
626
623
|
- `copyIn()` / `copyOut()` — they read and write the host filesystem, which does not exist.
|
|
627
|
-
- `expose()` — it opens a real `node:http` listener. Use
|
|
628
|
-
|
|
624
|
+
- `expose()` — it opens a real `node:http` listener. Use `request()` to reach an in-container
|
|
625
|
+
server instead of a host port.
|
|
629
626
|
- `python3` — see below.
|
|
630
627
|
- The `sandboxedjs` CLI, obviously.
|
|
631
628
|
|
|
@@ -736,11 +733,18 @@ Two caveats worth knowing:
|
|
|
736
733
|
|
|
737
734
|
Honest list of what does not work:
|
|
738
735
|
|
|
739
|
-
- **
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
736
|
+
- **Compiled native addons.** A `.node` file cannot be loaded, so a package that ships one has to
|
|
737
|
+
have a JavaScript or WebAssembly build to fall back on. `rollup` and `esbuild` do, and the
|
|
738
|
+
runtime redirects those two names to `@rollup/wasm-node` and `esbuild-wasm` automatically when
|
|
739
|
+
they are installed. **Vite 8 does not work** for this reason: it builds on Rolldown, whose only
|
|
740
|
+
sandbox-viable binding needs WASI and `worker_threads`. Vite 7 runs, dev server included, as do
|
|
741
|
+
Express, Koa, Fastify-style apps and plain `http` servers.
|
|
742
|
+
- **`child_process` is asynchronous only.** `spawn`, `exec` and `execFile` run through the kernel,
|
|
743
|
+
so a child sees the same filesystem and coreutils as the shell. `execSync`, `spawnSync` and
|
|
744
|
+
`execFileSync` throw `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM`: blocking the JavaScript thread on
|
|
745
|
+
another process is not expressible here.
|
|
746
|
+
- **No `net`, `tls`, `worker_threads` or `vm`.** `http` and `https` are served by a virtual stack
|
|
747
|
+
that `request()` talks to directly, so servers work; raw sockets do not.
|
|
744
748
|
- **Python is CPython via Pyodide.** The standard library is the real one. A C
|
|
745
749
|
extension works only if it has been built for WebAssembly — Pyodide ships
|
|
746
750
|
many, including `numpy`, but an arbitrary wheel from PyPI will not install.
|
|
@@ -772,6 +776,9 @@ Honest list of what does not work:
|
|
|
772
776
|
| `timezone` | `string` | `"UTC"` | |
|
|
773
777
|
| `timeoutMs` | `number` | none | Default limit for `exec` |
|
|
774
778
|
| `onStdout` / `onStderr` | `(chunk: string) => void` | — | Container-wide output taps |
|
|
779
|
+
| `onServerReady` | `(port, url) => void` | — | Fires when something inside starts listening |
|
|
780
|
+
| `pod` | `RuntimePod` | booted for you | Share or substitute the JavaScript runtime |
|
|
781
|
+
| `python` | `PythonOptions` | jsDelivr | Where to load Pyodide from |
|
|
775
782
|
|
|
776
783
|
### `Container`
|
|
777
784
|
|
|
@@ -825,5 +832,4 @@ agent sandbox, and a browser terminal.
|
|
|
825
832
|
|
|
826
833
|
## License
|
|
827
834
|
|
|
828
|
-
MIT
|
|
829
|
-
redistributing it.
|
|
835
|
+
MIT, with no dependency carrying a stricter licence.
|