The JavaScript runtime and
application stack for your agents.
Cruft runs untrusted JavaScript (plugins, dependencies, a model's output) in isolated compartments with timeouts it can't escape, across threads that share a heap. All of npm, none of the ambient trust.
$ curl -fsSL https://cruft.sh/install | bash
> powershell -c "irm cruft.sh/install.ps1 | iex"
$ npm i -g cruftjs
// isolation is a runtime primitive, not a library const sandbox = new Compartment({ globals: { fetch }, // only what you hand it timeout_ms: 50, // enforced by the runtime }); sandbox.evaluate(pluginSource); // can't catch its way out
Compartments run untrusted code (a plugin, a dependency, a model's output) in a separate realm with its own globalThis, capabilities only by explicit grant, and a timeout it cannot escape. Because each is a realm over one shared, garbage-collected heap, you can run thousands without the per-worker VM cost, hundreds of MiB where Node's worker_threads need gigabytes.
import { openPostgres } from "cruft:orm"; // plain JS or TS const db = openPostgres(); db.exec("CREATE TABLE users (id int, name text, age int)"); db.insertInto("users") .values([{ id: 1, name: "Ada", age: 36 }]) .run(); const adults = db.from("users") .where("age", ">", 18) .all(); // rows validated at the crossing
crizzle: one sound ORM over embedded cruft:postgres and cruft:sqlite. A cell that drifts from its column's declared type halts the crossing rather than silently returning garbage.
// one binary: engine, JIT, GC, TLS, HTTP/2, crypto, two databases import { createServer } from "node:http"; // Cruft's own compat surface, not node / libuv + C++ import { open } from "cruft:sqlite"; // embedded engine, not libsqlite const db = open("app.db"); const token = crypto.randomUUID(); // WebCrypto, built in createServer((req, res) => res.end(token)).listen(3000); // $ cruft server.js — no npm install, no bundler, no toolchain
Batteries, not dependencies: the parser, JIT, GC, TLS, HTTP/2, two database engines, and the package manager are all Cruft's own Rust in one binary, nothing vendored on the hot path, nothing to assemble. The code that runs your code is code the project can account for.
boundary default = secure type User = { id: number, name: string } compartment Core boundary(secure) { // no `any`, no implicit escapes; the checker proves it export function main(): string boundary(secure) { const u: User = { id: 7, name: "Ada" } return "hi " + u.name } }
CruftScript: a sound, statically typed language with TypeScript ergonomics. Unlike TypeScript, its types are kept and enforced at the boundary where untyped data enters, any and unproven casts are rejected by the checker, not deferred to runtime.
Why Cruft
Run code you don't trust, at scale, without trading away the ecosystem.
Isolation that actually holds
Compartments run untrusted code with timeouts it can't catch its way out of, and capabilities it was never handed.
Threads that share a heap
Worker compartments operate over one shared garbage-collected heap instead of copying megabytes per worker.
The ecosystem comes with it
node:* builtins, fetch, Streams, WebCrypto, Temporal, Intl, and an embedded SQLite. You don't trade the ecosystem for the safety.
import { Worker } from "node:worker_threads"; // 1024 workers over ONE shared heap, // not 1024 copies of the VM. const w = new Worker("./task.ts"); w.on("message", (m) => handle(m));
Threads that share a heap
Worker compartments operate over one shared, garbage-collected heap instead of copying megabytes per worker. 1024 workers use ~220 MB where Node, Bun, and Deno use ~5.4–6.3 GB.
The ecosystem comes with it
You don't trade the ecosystem for the safety.
node:*, unmodified
fs, http, crypto, path, os, streams, TLS, sockets: the Node platform, re-implemented.
The web platform
fetch, Streams, WebCrypto, URL, structuredClone, Temporal, ICU-backed Intl.
Embedded SQLite + Postgres
cruft:sqlite and cruft:postgres ship in the binary, with the sound cruft:orm (Crizzle) over both. This page's content is queried from it.
No build step, no transpiler
TypeScript's type syntax is erased while Cruft parses the file. There is no separate tool, no dist/, no source maps. A .ts file's first call costs the same as a .js file's.
Every file serving this page, the presto engine, the Markdown renderer, this site, is a .ts file running exactly like that. No build directory exists.
How Cruft compares
Where Cruft sits next to the runtimes you know.
| Cruft | Node | Deno | Bun | |
|---|---|---|---|---|
| Engine | Cruft Core | V8 | V8 | JavaScriptCore |
| TypeScript | Erased at parse, no transpile | Erased at parse | Transpiled (swc) | Transpiled |
| Sound typed language | CruftScript | None | None | None |
| In-process sandbox | Compartment: capabilities + uncatchable timeout | vm (not a security boundary) |
Process permission flags | None |
| Worker memory | One shared heap | Isolate per worker | Isolate per worker | Isolate per worker |
| Embedded database | SQLite + Postgres + sound ORM | node:sqlite (experimental) |
Via npm / FFI | bun:sqlite |
| npm packages | Built in | Built in | npm: specifiers |
Built in |
Cruft's TypeScript approach is deliberately the same erase model as Node. The runtime that is written from scratch, the first-class compartments, the shared-heap workers, and the built-in database with a sound ORM are where it stands apart.
Getting started
One binary, no toolchain to assemble. Run a file, install from npm, and the compartments, databases, and TLS are already built in.
$ cruft app.ts # just run it $ cruft install # pulls from npm