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
View install script
> powershell -c "irm cruft.sh/install.ps1 | iex"
View install script
$ npm i -g cruftjs
View on npm
// 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.

View →

Threads that share a heap

Worker compartments operate over one shared garbage-collected heap instead of copying megabytes per worker.

View →

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.

View →

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.

Typical setup
app.ts→ tsc / esbuild / swc→ dist/app.js→ engine
Cruft
app.ts→ engine types erased while parsing

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.

CruftNodeDenoBun
Engine Cruft Core V8V8JavaScriptCore
TypeScript Erased at parse, no transpile Erased at parseTranspiled (swc)Transpiled
Sound typed language CruftScript NoneNoneNone
In-process sandbox Compartment: capabilities + uncatchable timeout vm (not a security boundary) Process permission flags None
Worker memory One shared heap Isolate per workerIsolate per workerIsolate 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