Run AI-generated applications and tools without giving their code ambient access to your machine, network, credentials, or data. Hull packages each app as a signed static binary and enforces its declared capabilities at the OS boundary.
app.manifest({ modules = { "hull/http-server@1", "hull/http-client@1", "hull/env@1", "hull/json@1", }, hosts = { "api.stripe.com" }, env = { "STRIPE_KEY" }, }) local env = require("hull.env") local http_client = require("hull.http-client") app.get("/charge", function(req, res) -- only declared capabilities exist local key = env.get("STRIPE_KEY") local r = http_client.fetch("https://api.stripe.com/...") res:json(r) end)
Every external capability is declared. Undeclared modules
fail at module load; undeclared hosts, paths, and env
vars fail at the capability boundary. Only the
registration API (app) is intrinsic.
AI generates code endlessly. Sandboxing is decades old; what changed is that the layer asking what is this code allowed to do? is now the scarce one. Hull is that layer.
Your model or agent platform chooses what code to run. Hull decides what that code may do. Lua, JavaScript, and WASM execute under declared capability boundaries in one independently deployable binary. No resident control plane, package manager, or service mesh.
The signed manifest lists files, hosts, env vars, and GPU devices.
Undeclared access fails at load or at the native capability boundary.
pledge +
unveil
(Linux/Cosmo/OpenBSD) or Seatbelt (macOS) backs the policy at
the syscall layer. Signed releases and reproducible builds
make the chain auditable from source to deployed binary.
Hull gives generated software a narrow, reviewable operating envelope. Use it wherever an agent can create or invoke code, but the code must not inherit the authority of the machine running it.
Expose a small callable interface while denying undeclared filesystem, network, environment, and process access. The agent may write the handler; it cannot silently expand the tool's authority.
Package application code, runtime, dependencies, and policy into one signed binary. Run predictable work as deterministic code and call a model only where judgment adds value.
Keep the same signed capability contract on developer machines, edge nodes, customer premises, and isolated networks. Horizontal scaling remains a choice for your platform, not a runtime dependency.
Hull is not an enterprise agent workspace, identity system, model router, or autoscaler. It is the portable execution boundary underneath agents, workflows, MCP clients, and coding tools.
Use Hull with your existing model, orchestrator, and deployment platform, or run it without any of them. The manifest and enforcement stay with the binary wherever it goes.
Govern identities, collaboration, models, and connected company resources inside their platform.
Constrains generated code in a signed, independently deployable application wherever it must run.
Provides identity, placement, scaling, scheduling, and operations according to your environment.
The install script checks the SHA-256 manifest as it
downloads the right binary for your OS and arch.
hull verify-release
then verifies the Ed25519 signature against the public
key embedded in your local binary. No key
distribution needed.
Don't want to pipe a script from a host you haven't verified?
The installer is minisign-signed. Check
install.sh
against
install.sh.minisig
offline before running it (no Hull needed); the key is
cross-checkable on
GitHub.
Linux x86_64 · Linux aarch64 (Graviton, DGX, Ampere, Pi 4+) · macOS arm64 · Cosmopolitan APE fallback.
# Install over TLS. install.sh is minisign-signed (verify it below); # it SHA-256-checks each binary against the manifest as it downloads. $ curl -fsSL https://gethull.dev/install.sh | sh hull installed to ~/.local/bin/hull (v0.10.0) # Don't trust gethull.dev? Verify the installer offline first # (minisign, no Hull needed). Cross-check the key below against # github.com/artalis-io/hull/blob/main/minisign.pub. $ URL=https://gethull.dev $ curl -fsSLO $URL/install.sh $URL/install.sh.minisig $ minisign -Vm install.sh -P RWRqvc6NndheWcLIi3vsL+xoZfZw2j7fybSZ3rHJIOTHA2O3rrr6hD8h Signature and comment signature verified $ sh install.sh # Then verify the release signature (Ed25519, embedded pubkey). $ REL=https://github.com/artalis-io/hull/releases/latest/download $ curl -fsSLO $REL/hull.sha256 $REL/hull.sha256.sig $ hull verify-release hull.sha256 hull.sha256.sig ok. Signature valid $ hull doctor hull v0.10.0 · ca-bundle: embedded · platform: embedded · cc: ok
Prefer manual download? Grab the binary from the
releases page
and verify with
hull verify-release.
No curl | sh required.
No Hull binary on hand?
Verify in the browser
(Ed25519, runs locally, nothing uploaded).
app.main build links zero Keel, mbedTLS, SQLite, WASMdlopen, no JITBefore you spend more time on this page, four things Hull does not solve, named plainly, so you can decide whether to keep reading.
sandbox_init is a private, deprecated Apple API. Linux/Cosmo get the load-bearing isolation; on macOS the C capability layer carries more of the weight.
Module imports and capability checks resolve when the app loads, before any handler runs. An undeclared import is a startup error, not a midnight 500.
Scripts cannot reach io,
os, load,
eval, Function().
The runtime's host environment is sealed.
Pages are writable or executable. Never both. No JIT. No runtime dynamic code. WASM runs from AOT-compiled artifacts embedded at build time.
Pure-function WASM via WAMR. No I/O imports, gas-metered. Parallel workloads dispatch to WebGPU shaders via wgpu-native (Vulkan, Metal, DX12) through the same capability boundary.
The distributed hull is a
minimal base. It drops every reducible subsystem - both
interpreters, the HTTP core, the Keel event loop and server, TLS, SQLite,
WASM, the image codecs - and hull build
composes back only what your app declares, statically, at build
time. Not dlopen, not a
plugin loaded at runtime: whole archives linked in, so the manifest stays
enforceable and the build stays byte-reproducible.
A compute or CLI tool links zero Keel, TLS, SQLite or WASM - there is no unused TLS stack or SQL engine in the binary to audit, exploit, or CVE-inherit. You get exactly one interpreter (Lua or JS), HTTP only if it serves or fetches, TLS only if it speaks TLS, a database only if it opens one. ~2.1 MB for a pure compute tool, versus ~6.5 MB for a full web app - the difference is code that simply is not there.
Every archive hull build
composes is recorded and attested in
package.sig.gethull.composed
and re-verified against the embedded platform key at boot - a
tampered composed archive refuses to run. Static build-time composition
keeps the whole trust chain, and the reproducible build, intact. Modular
does not mean a soft edge.
Large optional subsystems ship as signed, on-demand features:
hull build --with=duckdb | postgres | mysql | gpu | tui.
Each is fetched by
hull feature install <name>
under the same Ed25519 trust chain as
hull update, so DuckDB
OLAP, GPU compute or a terminal UI never bloat the base for apps that
don't ask for them.
The clearest example of the model. hull/db
is one handle-based API; the DSN scheme picks the backend behind a single
vtable. SQLite is in the base;
PostgreSQL and
MySQL/MariaDB are pure-C wire clients
(no libpq, no libmysql - nothing vendored beyond the protocol) composed as
--with features;
DuckDB is the embedded OLAP feature.
Every query is parameter-bound at the C boundary, so the SQL string is
always a literal - the injection surface is zero, by construction,
on all four.
Hull works with any model or orchestrator. It is built for both sides of the agent loop: hosting LLM-callable tools behind a manifest the model can't write to, and giving AI coding agents a structured CLI surface to read, test, and deploy Hull apps.
The runtime is the trust boundary. The manifest is the contract.
The manifest is the tool's allowed surface. An LLM can write the handler but cannot expand the capabilities. Declaring a new host, path, or env var means editing the manifest, which the runtime parses and validates before any user code runs. One Hull binary per tool gives you per-tool capability scoping for free.
hull agent
subcommands return machine-readable JSON for routes,
schema, errors, tests, and deploy status. No
plugins, no separate sidecar process.
hull mcp
exposes the same surface as a Model Context Protocol
server, ready to plug into Claude Code, Codex CLI, or
any MCP-compatible client.
hull dev --agent
also writes .hull/dev.json and .hull/last_error.json sidecar files for live reload + structured error reporting.
A bounded host for LLM-callable tools. The agent gets exactly the surface you declared, nothing else.
Execute generated handlers behind a manifest the model didn't write. Capability creep is impossible by construction.
Other sandboxed runtimes solve parts of this. Hull is the combination. Manifest-declared capabilities, sealed signed bundles, kernel sandboxing, and no runtime codegen in one binary.
This table compares execution runtimes. Agent workspaces and cloud application platforms sit above that layer; Hull can run beneath or independently of them.
Marks reflect default configurations. ~ means partial coverage with caveats noted per row below the table.
| Property | Hull | Deno | CF Workers | WasmEdge |
|---|---|---|---|---|
| Manifest-declared capabilities | ✓ | part | · | part |
| Sealed signed bundle | ✓ | · | · | · |
| Kernel sandbox built-in | ✓ | · | n/a | · |
| No runtime codegen (W^X) | ✓ | · | · | ✓ |
| Single static binary | ✓ | ✓ | n/a | ✓ |
deno.json for Deno Deploy; default deno run uses --allow-* flags at launch, not in the source bundle. Hull's manifest is in the app and signed.The diagram below is the runtime. The four steps further down are how you, the developer, make it apply to your app.
app.main.hull build emits a self-contained signed binary.local db = require("hull.db") local crypto = require("hull.crypto") app.manifest({ modules = { "hull/http-server@1", "hull/db@1", "hull/crypto@1", "hull/web/middleware/session@1", }, hosts = { "api.example.com" }, fs = { read = { "./assets/*" } }, env = { "API_KEY" }, }) app.get("/items", function(req, res) local rows = db.query("SELECT id, name FROM items") res:json({ items = rows }) end)
First-party modules with the same audit posture as
Hull itself. Every module is independently declarable
in manifest.modules;
anything you don’t declare isn’t reachable.
Picked by category below; the full table lives in
README.md.
auth-flows · middleware/totpmiddleware/oauth (Google + Entra)middleware/audit-log · middleware/sessionmiddleware/rbac · pwned · qrcodehtmx/toast · htmx/confirm (styled <dialog>)htmx/form (errors + auto loading state)htmx/search · htmx/inline-edithtmx/sort · htmx/pagination · htmx/table (schema-driven grid)req:multipart() incremental iteratorattachment · blob (SHA-256 CAS) · mimeweb/attachment-serve with auth-check hooktemplate (compile-cached, XSS-safe) · validatemiddleware/csp (nonces) · middleware/csrf · middleware/corsmiddleware/ratelimit · middleware/idempotencymiddleware/logger · middleware/health · middleware/etagcompute (WAMR AOT) · gpu (wgpu-native)db across SQLite, PostgreSQL, MySQL/MariaDB (pure-C wire clients, no libpq/libmysql), and embedded DuckDB (OLAP)db parameterized binding (zero SQLi surface) · db.udfsearch (SQLite FTS5) · imagemiddleware/outbox · middleware/inboxmiddleware/transaction (auto BEGIN IMMEDIATE / COMMIT)app.every(ms, fn) · app.daily("HH:MM", fn)jwt (HS256/384/512, RS256/384/512, PS256, ES256/384)Three layers cooperate. The manifest restricts what code can name. The C capability layer enforces what those names can do. The kernel sandbox enforces what the process can do at all.
A violation at any layer is a hard stop, not a warning.
realpath ancestor checks.require and import resolve only against the embedded stdlib registry. Nothing is fetched at run time.dlopen, no FFI, no extension modules. The native surface is fixed at build time.pledge + unveil. macOS: sandbox_init with a manifest-derived profile.mprotect-RO sealing on boot-built security policy (manifest allowlists, the router's handler vtable, the parsed mbedTLS trust chain), so a heap-write primitive that would otherwise pivot a function pointer or relax a capability gate faults at the write instead.
Every artefact you receive from the Hull ecosystem. The
hull
binary, the platform library inside it, the app a developer ships
you. Is signed by a different key and verified by a different
tool. No single compromise unwinds the chain.
The whole stack is verifiable from a browser tab. You don’t need to have Hull installed to confirm the binary you’re about to install is genuine.
By default, your trust anchor is gethull.dev.
the release and platform pubkeys for that anchor are compiled
into every Hull binary as
HL_RELEASE_PUBKEY_HEX
and HL_PLATFORM_PUBKEY_HEX,
and into the
browser verifier
as JavaScript constants.
Anyone can compare what’s in your local binary to what’s on the live site.
the values are not secret.
If you don’t want that anchor:
HL_*_PUBKEY_HEX
values in
include/hull/{release,signature}.h,
ship the rebuilt binary to your customers. Your customers verify
against your keys, not gethull.dev’s.
The architecture stops working with the upstream supply chain
the moment your fork ships. Intentionally.
--platform-key <file>
to hull verify,
or --pubkey <hex>
to hull verify-release,
to validate against any key you trust. Useful for ad-hoc auditing of
a build that’s NOT yours.
Trust chain end-to-end: customer → platform publisher (you or gethull.dev) → app developer. Pull any link out by replacing its pubkey; the rest still verifies.
hull build composes back only what the app uses, statically. A compute app links zero Keel/TLS/SQLite/WASM (~2.1 MB); a full web app composes them back (~6.5 MB). Composition is verifiable: every composed archive is attested in package.sig.gethull.composed and re-checked against the platform key at boot.
duckdb, postgres, mysql, gpu, tui - ship as signed per-feature archives composed at hull build --with=<name>, installed via hull feature install on the same trust chain as hull update. One clean taxonomy: stdlib / feature / flavor / tool.
hull build emits the app object itself and links - no C compiler needed. Only a linker, and hull tools install zig side-loads a self-contained cross-compiling one that runs on a bare box. The payoff: a cosmo hull + hull tools install cosmocc builds a working binary on stock Windows with no pre-installed toolchain.
hull/db API, DSN-selected across SQLite (in base), PostgreSQL and MySQL/MariaDB (pure-C wire clients, no libpq/libmysql, composed as features), and embedded DuckDB. Every query is parameter-bound at the C boundary - the SQL is always a literal, on all four.
hull update verifies hull.sha256.sig against the embedded release pubkey before rename(2).
hull verify-release + hull verify against the embedded keys.
fs/env/host access checked at a capability boundary the kernel enforces.
pledge + unveil on Linux/Cosmo; Seatbelt SBPL on macOS. Violation = SIGKILL or EPERM.
libhull_platform.a with the gethull platform key. hull build cross-checks the embedded .a against the manifest; hull verify and runtime --verify-sig re-check the signature against the embedded HL_PLATFORM_PUBKEY_HEX. --no-verify-platform is the documented escape valve for dev hulls and forks.
make reproducible-check. Three layers gated: make itself, hull build output, and the make self-build bootstrap chain. Anyone with the source can rebuild the binary they downloaded and prove its bytes match.
hull sbom). Every binary self-describes its actual vendored contents. Submodule SHAs baked in at build time, snapshot versions in a static table, build-flag gating means a compute-only build correctly omits SQLite. Four output formats: human / JSON / CycloneDX 1.5 / SPDX 2.3. Compliance pipelines feed straight in; the static doc is now a live verifiable command. As of v0.1.6 the SBOM is also a signed release artifact (hull.sbom.json / .cdx.json / .spdx.json), covered by every signature layer below.
cosign verify-blob hull.sha256 --certificate hull.sha256.cosign.pem --signature hull.sha256.cosign.sig), verifiable without trusting gethull. (c) SLSA build-provenance attestation per binary (gh attestation verify hull-linux-x86_64 --repo artalis-io/hull), signed via Fulcio short-lived certs. A compromise of any single trust root leaves the other two intact.
hull verify-self). Replaces the manual sha256sum + grep + hull verify-release dance. Resolves the running binary’s path, hashes it, verifies the Ed25519 signature on the manifest, and constant-time compares against the entry for this platform’s asset name. Verbose mismatch output for diagnostics; --manifest / --signature / --asset / --pubkey for explicit / offline / custom-release use.
docs/fork_playbook.md walks through the five steps, the verification checklist, and an honest “why most organisations shouldn’t fork” section enumerating the AGPL §13 propagation, vendored-dep CVE inheritance, support burden, and three lighter-weight alternatives that satisfy most “we need our own trust root” requirements without forking.
hull/web/auth-flows@1 wires welcome / verify / login / lockout / password-reset / magic-link / email-change against an audit-log;
hull/web/middleware/totp@1 for RFC 6238 second factor with multi-key at-rest encryption;
hull/web/middleware/oauth@1 for Authorization Code + PKCE against Google / Microsoft Entra (alg-confusion-safe RS/PS/ES JWT verify, JWKS-pinned via mbedTLS);
hull/web/pwned@1 for HIBP k-anonymity check at password set/change. 13 rounds of parallel-reviewer audits, converged in round 13 with zero findings across three independent reviewers.
req:multipart() / req.multipart() iterator decodes parts incrementally without materializing the full body;
hull/blob@1 stores bytes as SHA-256-keyed CAS so duplicate uploads collapse to one on-disk file;
hull/attachment@1 validates filenames + cross-checks declared MIME against the magic-byte sniffer (hull/mime@1) before reaching storage.
hull init --profile htmx scaffolds a server-rendered htmx app;
eight first-party widgets (toast, confirm, form, search, inline-edit, sort, pagination, table) ship server helpers + structural-only CSS + minimal browser JS via the platform VFS;
csp = "htmx" preset expands at startup to a known-good policy for SSR apps with stdlib JS from /static/. Zero client framework; styled <dialog> replaces the browser’s window.confirm(). htmx/table composes sort + inline-edit per column from a schema array.
tests/e2e_htmx_playwright.sh spins up the example apps and exercises them in headless Chromium via Playwright. 33 assertions per mode covering the widget tier, runtime parity (Lua AND JS variants of hypermedia_photos), CRUD round-trips with CSRF + session, keyboard accessibility, and a per-page @axe-core/playwright WCAG scan that FAILs on critical / serious violations. Two modes: make e2e-htmx-playwright (dev) and make e2e-htmx-playwright-build (against hull build standalone binaries, exercising the embedded-VFS code path). Failure runs upload Playwright traces + final-page screenshots via actions/upload-artifact@v4.
HlAsymBackend vtable. JWT alg is pinned BEFORE key resolution (alg-confusion defeated). crypto.x509_pubkey_pem() extracts SPKI PEM from x509 DER/PEM for JWKS x5c consumption. SHA-NI runtime dispatch on x86 where available.
db capability runs on embedded SQLite (default), plus a pure-C PostgreSQL wire client (SCRAM-SHA-256, TLS, no libpq) and a pure-C MySQL / MariaDB wire client (mysql_native_password + caching_sha2_password over TLS, binary prepared statements, no libmysql), plus an embedded DuckDB OLAP / columnar-analytics backend, that compose in as opt-in features (--with=postgres | mysql | duckdb). One HlDbBackend vtable, DSN-scheme selection; parameterized binding on every backend so SQL injection has no surface; the DB-backed stdlib (session, outbox, inbox, rbac, audit-log, idempotency) and migrations run dialect-portably. Real-server E2E in CI (Postgres 16, MySQL 8): auth + TLS + migrations + db.async + stdlib.
hull is on your machine, defending the binary against an attacker with local write access is the OS’s job (signed system updates, FIM, SELinux/AppArmor). Reproducible builds (make reproducible-check, CI-gated) make the bytes-on-disk cross-checkable against the published source.
The threat model, attack-by-attack mitigations, and the self-sovereignty walkthrough live in docs/security.md.
Hull keeps its surface small on purpose. The CLI is one
binary, and hull build
needs no C compiler: it emits the app object and links it
directly. Cross-compile, or fetch a self-contained toolchain on
demand (hull tools install
zig or cosmocc,
each Ed25519-verified against the same release) and build a
run-anywhere binary from any host, Windows included. The signing
keys are yours.
# Scaffold a new app (install is shown up the page). $ hull init my-tool && cd my-tool # Develop with auto-reload. $ hull dev hull dev: listening on :8080 (auto-reload) # Build a sealed, signed, single-binary release. No C compiler # needed: hull emits the app object and links it directly. $ hull build . hull build: my-tool 6.5 MB signed ed25519 # One binary for every OS? The APE flavor fetches a verified # toolchain on demand and builds from any host, Windows included. $ hull tools install cosmocc $ hull build . hull build: my-tool.com APE Linux · macOS · Windows · BSD # Run on Linux, macOS, Windows, or any APE host. $ ./my-tool
# Machine-readable introspection. No plugins, no MCP servers required. $ hull agent routes my-tool { "app": "my-tool", "runtime": "lua", "modules": ["hull/http-server@1", "hull/db@1", "hull/web/middleware/session@1"], "routes": [ { "method": "GET", "path": "/items", "handler": "app.lua:32" }, { "method": "POST", "path": "/items", "handler": "app.lua:48" } ], "middleware": [ { "method": "*", "pattern": "/api/*", "name": "auth.session" } ] }