type IpcSide = "parent" | "child"; /** One end of a channel, as a process sees it. */ interface IpcEndpoint { send(message: unknown): void; disconnect(): void; } /** How a process reaches the host's channels, wherever the process runs. */ interface IpcTransport { attach(id: string, side: IpcSide, onMessage: (message: unknown) => void, onDisconnect: () => void): IpcEndpoint; } /** The handle a pod's process manager returns. */ interface ChildHandle { pid: number; state: "starting" | "running" | "exited"; exitCode: number | undefined; on(event: "stdout" | "stderr" | "exit" | "rawmode" | "fd", listener: (...args: any[]) => void): unknown; exec(): void; sendStdin(data: string): void; /** * Close the child's input. * * Without this a child that reads stdin to EOF — every filter, and every * tool a library pipes into, `xsel` and `base64` alike — waits forever for * an end that never comes, and the parent waits on its exit. */ endStdin?(): void; /** * Write to, or close, a descriptor above 2 that the parent asked for with * `stdio[n] = "pipe"`. Output on those descriptors arrives as `fd` events * carrying `(fd, text)`. * * Chromium's `--remote-debugging-pipe` is the reason: Playwright speaks the * DevTools protocol over fds 3 and 4, and a child without them cannot be * driven at all. */ writeFd?(fd: number, data: string): void; endFd?(fd: number): void; kill(signal?: string): void; } interface ChildSpawnConfig { command: string; args?: string[]; cwd?: string; env?: Record; parentPid?: number; /** * The child was given the parent's streams (`stdio: "inherit"`). * * Its input is then the parent's terminal rather than a pipe that will end, * which is the difference between a program that waits for what the user * types and one that reads to end-of-input and stops. */ inheritStdio?: boolean; /** * The child was told to ignore its input (`stdio: "ignore"`). * * It then has no input at all, so its stdin is closed at once rather than * left open on a parent that will never write to it. */ stdinIgnored?: boolean; /** Descriptors above 2 the parent opened as pipes, e.g. `[3, 4]`. */ extraPipes?: number[]; } type SpawnChild = (config: ChildSpawnConfig) => ChildHandle; /** * Run a child to completion without returning to the event loop. * * Supplied only by a pod that can actually block — one whose guest runs on its * own thread. Where it is absent the synchronous entry points keep reporting * that they are unavailable, which is the honest answer for an in-realm pod. */ type SyncSpawn = (request: { command: string; args: string[]; cwd: string; env?: Record; input?: string; inheritStdio?: boolean; }) => { status: number | null; stdout: string; stderr: string; signal: string | null; error?: { code?: string; message: string; }; }; declare function createChildProcessModule(spawnChild: SpawnChild, defaultCwd: () => string, syncSpawn?: SyncSpawn, defaultEnv?: () => Record, lifecycle?: { referenceChanged?: (delta: number) => void; stdout?: (text: string) => void; stderr?: (text: string) => void; /** Where `fork` channels live; without one a child simply has none. */ ipc?: IpcTransport; }): Record; /** * The outbound network policy, as plain functions every client consults. * * The container has several ways out — `curl` and `wget` in the shell, `http`, * `https`, `fetch` and `WebSocket` in a Node program, sockets in Python — and * they used to decide separately. Only the shell asked: a Node program's * `fetch("https://…")` reached the internet from a container booted with * outbound access off. One policy, applied at each exit, is what makes * `network: { allowOutbound: false }` mean what it says. * * Loopback is not "outbound" at all. `127.0.0.1` inside the container is the * container, so those requests are routed to its own servers and never handed * to the host's network stack, whatever the policy allows. */ interface OutboundPolicy { /** Whether requests may leave the container at all. */ allowOutbound: boolean; /** When outbound is on, the hosts it may reach (subdomains included). `null` means any. */ allowedHosts: string[] | null; /** * A URL that performs this container's outbound requests on its behalf. * * Carried with the policy rather than beside it, because every exit has to * honour it: `curl`, the Python egress and a guest's own `fetch` all leave * the same way, and a proxy that covered only some of them would be a * setting whose meaning depended on which language the guest was written in. */ proxy?: string; } /** * Outbound TCP: names resolved by the host, connections dialled by the host. * * Until now a guest socket could reach only loopback. Everything that speaks a * protocol other than HTTP -- Postgres, Redis, SMTP, an LLM gateway over a raw * stream -- was therefore unreachable, and so was every HTTP client that opens * its own socket rather than going through the egress. Extensions do not help: * no wheel can create a connection the container cannot make. * * Two things are needed, and both belong to the host. * * **Names.** Emscripten's own `getaddrinfo` invents an address per hostname and * keeps the table inside the guest's JavaScript module, where the kernel cannot * see it -- so a later `connect` arrived as an address nobody could map back to * a name. Resolution is therefore a host operation: the host allocates the * address, remembers which name it stands for, and recognises it on connect. A * guest still sees ordinary addresses, `getaddrinfo` still returns tuples, and * reverse lookup answers. * * **The connection.** Only the host can open a socket. In Node that is * `node:net`; in a browser there is no such thing, and a page cannot be given * one, so a browser host supplies no dialer and outbound connects fail with a * message that says to use the HTTP egress instead. * * The outbound policy applies here as it does at every other exit, and it is * applied to the *name* the guest asked for, not to the address it was handed. */ /** A real connection the host owns, as this module needs to use it. */ interface HostTcpConnection { write(bytes: Uint8Array): void; /** Half-close: the guest has finished writing. */ end(): void; close(): void; onData(handler: (bytes: Uint8Array) => void): void; onClose(handler: () => void): void; /** The address the host actually connected to, for `getpeername`. */ readonly remoteAddress: string; readonly remotePort: number; readonly localPort: number; } /** What a host must provide for a guest to reach the network. */ interface TcpDialer { (host: string, port: number): Promise; } declare class OutboundTcp { private readonly policy; private readonly dialer; private readonly byName; private readonly byAddress; private next; constructor(policy: () => OutboundPolicy, dialer: TcpDialer | null); /** Whether this address was handed out by `resolve`. */ knows(address: string): boolean; hostnameFor(address: string): string | undefined; /** * The address for `hostname`, allocating one on first use. * * Refusal happens here as well as at connect, because a name that cannot be * reached should fail as a resolution failure -- which is what every client * reports as "unknown host" rather than as a mid-connection error. */ resolve(hostname: string): string; /** Open a connection to a resolved address, or to a literal one. */ connect(address: string, port: number, local: { address: string; port: number; }): Promise<{ connection: VirtualTcpConnection; host: HostTcpConnection; }>; private allowed; } type FileKind = "file" | "directory" | "symlink" | "chardev" | "blockdev" | "fifo" | "socket"; /** Render as `drwxr-xr-x`, honouring setuid/setgid/sticky. */ declare function formatMode(mode: number): string; /** Zero-padded octal permissions, as `stat -c %a`/`%04a` would show. */ declare function octalMode(mode: number, width?: number): string; /** * Apply a `chmod` spec to an existing mode. Accepts octal (`755`, `0644`) and * the symbolic grammar (`u+rwx,go-w`, `a=r`, `+X`, `u+s`, `o+t`). * * @param isDir whether the target is a directory — needed for the `X` flag. */ declare function applyChmod(spec: string, current: number, isDir: boolean, umask?: number): number; /** Parse the `umask` builtin's argument. */ declare function parseUmask(spec: string): number; /** * The `Stats` object handed back by `Vfs.stat`. Shaped like `fs.Stats` so it * feels familiar, but with a real `st_mode` that carries the file-type bits * (which the underlying volume stores separately). */ interface StatInit { mode: number; size: number; uid: number; gid: number; ino: number; nlink: number; atimeMs: number; mtimeMs: number; ctimeMs: number; birthtimeMs?: number; dev?: number; rdev?: number; blksize?: number; } declare class Stats { readonly mode: number; readonly size: number; readonly uid: number; readonly gid: number; readonly ino: number; readonly nlink: number; readonly dev: number; readonly rdev: number; readonly blksize: number; readonly atimeMs: number; readonly mtimeMs: number; readonly ctimeMs: number; readonly birthtimeMs: number; constructor(init: StatInit); get blocks(): number; get atime(): Date; get mtime(): Date; get ctime(): Date; get birthtime(): Date; get kind(): FileKind; isFile(): boolean; isDirectory(): boolean; isSymbolicLink(): boolean; isCharacterDevice(): boolean; isBlockDevice(): boolean; isFIFO(): boolean; isSocket(): boolean; /** Permission bits only, with the type bits masked off. */ get perms(): number; } interface DirEntry { name: string; kind: FileKind; } /** * The container's virtual filesystem. * * Real file content lives in the RuntimePod's `MemoryVolume`, deliberately the * *same* volume the Node.js worker processes see — so a file written by `echo` * is readable by `require('fs')` inside a spawned script, and vice versa. * * On top of that volume this layer adds the parts a Linux userland expects and * the raw volume does not have: file-type bits in `st_mode`, permission and * ownership checks, an `O_*` open/fd table, and pluggable *virtual providers* * that synthesise `/proc`, `/sys` and `/dev` on demand. */ /** The identity a filesystem operation runs as. */ interface Cred { uid: number; gid: number; groups: number[]; umask: number; } declare const ROOT_CRED: Cred; declare function makeCred(uid: number, gid: number, groups?: number[], umask?: number): Cred; /** A file that does not live in the volume — `/proc/uptime`, `/dev/null`, … */ interface VirtualNode { kind: FileKind; /** Permission bits only; the type bits are added from `kind`. */ mode: number; uid?: number; gid?: number; size?: number; mtimeMs?: number; /** Symlink target, when `kind === "symlink"`. */ target?: string; read?(): Uint8Array | string; write?(data: Uint8Array, append: boolean): void; /** Directory listing, when `kind === "directory"`. */ list?(): string[]; } /** * Supplies a subtree of synthetic files. `resolve` receives the path *relative* * to `root` ("" means the mount point itself) and returns null for misses. */ interface VirtualProvider { root: string; resolve(rel: string): VirtualNode | null; /** * When true (the default) the provider owns its whole subtree and a miss is * `ENOENT` — that is what `/proc` wants, so a dead pid does not resolve to a * stale on-disk file. `/dev` and `/sys` set this to false so that synthetic * nodes overlay a real directory users can still write into. */ exclusive?: boolean; } interface WriteOptions { mode?: number; append?: boolean; cred?: Cred; /** Skip the permission check — used by kernel-internal writes. */ privileged?: boolean; } interface ResolveOptions { cred?: Cred; /** Follow a symlink in the final position. Off for `lstat`, `rm`, `chmod -h`. */ followFinal?: boolean; } declare class Vfs { readonly volume: RuntimeVolume; private readonly providers; private nextVirtualIno; private readonly virtualInos; constructor(volume: RuntimeVolume); addProvider(provider: VirtualProvider): void; removeProvider(root: string): void; /** The mount points currently served synthetically. */ get virtualRoots(): string[]; private lookupVirtual; /** True when a miss at `abs` must be ENOENT rather than a volume lookup. */ private isUnderProvider; /** Synthetic children a non-exclusive provider contributes to a directory. */ private virtualChildren; private virtualIno; /** True when `cred` may perform `mode` (R_OK/W_OK/X_OK) on a stat result. */ permitted(st: Stats, mode: number, cred: Cred): boolean; private require; /** * Walk `abs` component by component, following symlinks and checking search * (`+x`) permission on every directory along the way, exactly like `namei`. * * Returns the fully resolved absolute path. Does *not* require the final * component to exist — callers decide whether a miss is fatal. */ resolvePath(abs: string, opts?: ResolveOptions): string; /** lstat that returns null instead of throwing, for internal probing. */ private tryLstat; private readlinkRaw; /** stat(2) — follows symlinks. */ stat(abs: string, opts?: { cred?: Cred; }): Stats; /** lstat(2) — does not follow a symlink in the final position. */ lstat(abs: string): Stats; private statFromVirtual; exists(abs: string, cred?: Cred): boolean; lexists(abs: string): boolean; access(abs: string, mode?: number, cred?: Cred): void; realpath(abs: string, cred?: Cred): string; readFile(abs: string, cred?: Cred): Uint8Array; readText(abs: string, cred?: Cred): string; readdir(abs: string, cred?: Cred): string[]; /** True when the volume itself has a real directory at `abs`. */ private volumeHasDir; readdirWithTypes(abs: string, cred?: Cred): DirEntry[]; readlink(abs: string, cred?: Cred): string; writeFile(abs: string, data: Uint8Array | string, opts?: WriteOptions): void; appendFile(abs: string, data: Uint8Array | string, opts?: WriteOptions): void; truncate(abs: string, len?: number, cred?: Cred): void; mkdir(abs: string, opts?: { mode?: number; recursive?: boolean; cred?: Cred; }): void; private mkdirOne; rmdir(abs: string, cred?: Cred): void; unlink(abs: string, cred?: Cred): void; /** Recursive delete, the engine behind `rm -r`. */ rmrf(abs: string, cred?: Cred): void; private requireParentWrite; rename(from: string, to: string, cred?: Cred): void; copyFile(from: string, to: string, cred?: Cred): void; symlink(target: string, linkPath: string, cred?: Cred): void; link(existing: string, newPath: string, cred?: Cred): void; chmod(abs: string, mode: number, cred?: Cred, follow?: boolean): void; chown(abs: string, uid: number, gid: number, cred?: Cred, follow?: boolean): void; utimes(abs: string, atimeMs: number, mtimeMs: number, cred?: Cred): void; /** `touch` semantics: create when missing, otherwise bump the timestamps. */ touch(abs: string, cred?: Cred, timeMs?: number): void; /** Depth-first walk yielding absolute paths. Symlinks are not followed. */ walk(abs: string, opts?: { includeSelf?: boolean; cred?: Cred; maxDepth?: number; }): Generator; /** Recursive copy used by `cp -r` and the container's `copyIn` helper. */ copyTree(from: string, to: string, cred?: Cred): void; /** Free/used byte accounting for `df` and `du`. */ usage(abs?: string): { files: number; dirs: number; bytes: number; }; } interface VirtualSocketAddress { address: string; port: number; } /** One endpoint of a host-owned in-memory TCP stream. */ declare class VirtualTcpConnection { private readonly incoming; private peer; private readClosed; private writeClosed; private closed; readonly local: VirtualSocketAddress; readonly remote: VirtualSocketAddress; constructor(local: VirtualSocketAddress, remote: VirtualSocketAddress); pairWith(peer: VirtualTcpConnection): void; get eof(): boolean; get readable(): boolean; get writable(): boolean; read(maxLength: number): Uint8Array; write(bytes: Uint8Array): number; waitForChange(): Promise; shutdown(read: boolean, write: boolean): void; close(): void; } interface VirtualTcpAccepted { connection: VirtualTcpConnection; peer: VirtualSocketAddress; } /** A host-owned TCP listener with a POSIX-style accept backlog. */ declare class VirtualTcpListener { readonly port: number; readonly address: string; readonly backlog: number; private readonly pending; private readonly waiters; private closed; constructor(port: number, address?: string, backlog?: number); enqueue(connection: VirtualTcpConnection): void; accept(): VirtualTcpAccepted; get readable(): boolean; get isClosed(): boolean; waitForChange(): Promise; close(): void; private wake; } /** Shared port authority for all in-container TCP listeners. */ declare class VirtualTcpNetwork { private readonly occupied?; private readonly listeners; /** The host's outbound stack, when this host can open sockets at all. */ outbound: OutboundTcp | null; /** * Ports held by in-container servers that are not sockets. * * A JavaScript HTTP server in the container is registered with the request * router and never binds one of these sockets, so a guest connecting to its * port found nothing listening. Before sockets could leave the container * this did not arise -- every guest client went out through the egress -- * and afterwards `urllib` dialling a container server got ECONNREFUSED. */ loopbackHttp: ((port: number, connection: VirtualTcpConnection) => boolean) | null; constructor(occupied?: ((port: number) => boolean) | undefined); listen(port: number, address?: string, backlog?: number): VirtualTcpListener; close(listener: VirtualTcpListener): void; connect(port: number, localPort?: number, localAddress?: string): VirtualTcpConnection; /** A connection to a port an in-container server holds without a socket. */ private connectToNonSocketServer; hasListener(port: number): boolean; /** Ports the container itself listens on, which no guest asked for. */ private readonly internal; /** * Ports a guest is serving on. * * The container's own plumbing listens too — the HTTP egress takes a port * like any server — and those are not the guest's. Reported, they reach * every consumer of "what is running here": a port picker offers one, a * preview opens it, and the reader is looking at an internal endpoint * answering that it wanted an absolute URL, with nothing to say what it is * or why they are there. */ ports(): number[]; /** Keep `port` out of {@link ports}: it belongs to the container, not a guest. */ markInternal(port: number): void; closeAll(): void; } /** * Clean-room contracts between SandboxedJS and its JavaScript runtime. * * These deliberately describe only behavior SandboxedJS consumes. Runtime * implementations may use Web Workers in browsers or worker_threads on Node. */ interface VolumeStats { totalBytes: number; fileCount: number; /** New runtime spelling. */ directoryCount?: number; /** Compatibility spelling used by existing volume implementations. */ dirCount?: number; } interface VolumeStat { mode: number; size: number; uid: number; gid: number; ino: number; nlink: number; atimeMs: number; mtimeMs: number; ctimeMs: number; birthtimeMs: number; isFile(): boolean; isDirectory(): boolean; isSymbolicLink(): boolean; } interface RuntimeVolume { readFileSync(path: string): Uint8Array; writeFileSync(path: string, data: string | Uint8Array): void; appendFileSync(path: string, data: string | Uint8Array): void; readdirSync(path: string): string[]; lstatSync(path: string): VolumeStat; readlinkSync(path: string): string; mkdirSync(path: string, options?: { mode?: number; }): void; rmdirSync(path: string): void; unlinkSync(path: string): void; renameSync(from: string, to: string): void; symlinkSync(target: string, path: string): void; linkSync(existing: string, path: string): void; truncateSync(path: string, length?: number): void; chmodSync(path: string, mode: number): void; lchmodSync(path: string, mode: number): void; chownSync(path: string, uid: number, gid: number): void; lchownSync(path: string, uid: number, gid: number): void; utimesSync(path: string, atime: Date, mtime: Date): void; getStats(): VolumeStats; } interface RuntimeProcessResult { exitCode: number; stdout: string; stderr: string; } interface RuntimeProcess { readonly completion: Promise; /** * `output` and `error` carry stdout and stderr; `exit` the code. `rawmode` * reports the program turning terminal raw mode on or off, which a terminal * needs so that it stops echoing input the program is drawing itself. */ /** * `raw-output` is optional: a process that sets `rawOutput` emits stdout * there as written, bytes included, alongside the decoded `output`. */ on(event: "output" | "raw-output" | "error" | "exit" | "rawmode", listener: (...args: any[]) => void): this; readonly rawOutput?: boolean; /** Input for the program. Bytes are delivered as bytes: a pipe may carry binary framing. */ write(data: string | Uint8Array): void; kill(signal?: string): void; } interface RuntimeHttpResponse { statusCode?: number; statusMessage?: string; headers?: Record; body?: string | Uint8Array | ArrayBuffer; } interface RuntimePackageInstaller { install(name: string, version?: string, options?: Record): Promise; installFromManifest(path: string, options?: Record): Promise; /** Create a view that installs into another project root. */ forCwd?(cwd: string): RuntimePackageInstaller; } /** Where bytes written by an upgraded server inside the container come out. */ interface RuntimeSocketPeer { data(bytes: Uint8Array): void; close(): void; } /** The caller's end of a connection opened with {@link RuntimePod.connect}. */ interface RuntimeConnection { send(bytes: Uint8Array): void; close(): void; } /** The process-manager protocol a container's kernel bridge substitutes for. */ interface RuntimeProcessManager { spawn(config: ChildSpawnConfig): ChildHandle; } interface RuntimePod { readonly volume: RuntimeVolume; readonly packages: RuntimePackageInstaller; readonly instanceId: string; readonly processManager: RuntimeProcessManager; /** Shared host-owned TCP authority used by Node and Python servers. */ readonly sockets?: VirtualTcpNetwork; readonly proxy: { activePorts(instanceId?: string): number[]; }; spawn(command: string, args?: string[], options?: Record): Promise; request(port: number, init?: Record): Promise; /** * Open a connection that upgrades out of HTTP, or null if nothing takes one. * * Optional because a pod that only ever answers requests is still a usable * pod — a caller treats the absence as "no WebSocket here" rather than as a * broken implementation. */ connect?(port: number, init: Record, peer: RuntimeSocketPeer): RuntimeConnection | null; /** * Bind a port to a server implemented outside the JavaScript runtime. * * The pod owns the port table, and a Python process cannot register with it * the way a Node server does — it has no `http.createServer`. This is the * seam that lets one exist without a second, divergent port table, so * `box.request()`, the preview router and `ss` all see the same listeners. * * Returns the function that unbinds it. Optional: a pod that cannot host * foreign servers simply does not offer one. */ serveExternal?(port: number, owner: string, handler: (request: { method: string; path: string; headers: Record; body: Uint8Array; }) => Promise): () => void; /** * Apply the container's outbound policy to programs this pod runs. * * Optional so a pod written elsewhere still satisfies the contract, but a pod * without it cannot keep a program's own `fetch` inside the policy — only * the shell's `curl` would honour it. */ setNetworkPolicy?(policy: OutboundPolicy): void; snapshot(options?: Record): unknown; restore(snapshot: unknown, options?: Record): Promise; teardown(): void; } export { type Cred as C, type DirEntry as D, type IpcTransport as I, type OutboundPolicy as O, type RuntimeVolume as R, type SpawnChild as S, Vfs as V, type WriteOptions as W, VirtualTcpNetwork as a, type RuntimeHttpResponse as b, type SyncSpawn as c, type VolumeStat as d, type VolumeStats as e, type RuntimePod as f, type RuntimePackageInstaller as g, type ChildSpawnConfig as h, type ChildHandle as i, type RuntimeProcess as j, type RuntimeSocketPeer as k, type RuntimeConnection as l, ROOT_CRED as m, type RuntimeProcessManager as n, type RuntimeProcessResult as o, Stats as p, type VirtualNode as q, type VirtualProvider as r, applyChmod as s, createChildProcessModule as t, formatMode as u, makeCred as v, octalMode as w, parseUmask as x };