Hardened, capability-secure runtime infrastructure for AI-native systems. AI generates code endlessly. Hull constrains what it can actually do, at a kernel-enforced boundary the script cannot cross.
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.
Lua, JavaScript, and WASM execute under declared, kernel-enforced capability boundaries. SQLite storage. Single static binary, around five megabytes. Runs on every desktop OS from one file. No daemon, no service mesh, no installer.
The app’s manifest lists files, hosts, env vars, and GPU
devices. Undeclared access fails at load.
pledge +
unveil
(Linux/Cosmo/OpenBSD) or Seatbelt (macOS) backs the policy at
the syscall layer. Signed releases and reproducible builds
make the whole chain auditable end-to-end.
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.
Linux x86_64 · Linux aarch64 (Graviton, DGX, Ampere, Pi 4+) · macOS arm64 · Cosmopolitan APE fallback.
# 1. Install. install.sh verifies the SHA-256 manifest as it downloads. $ curl -fsSL https://gethull.dev/install.sh | sh hull installed to ~/.local/bin/hull (v0.2.0) # 2. 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 # 3. Inspect the toolchain. $ hull doctor hull v0.2.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).
dlopen, 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.
Hull 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.
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)
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.
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 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 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.
https://gethull.dev/install.sh via TLS plus the SHA-256 it checks against. The Ed25519 enforcement kicks in on the first hull update. Bootstrap-of-trust problem; the same problem all signed-update schemes have.
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. The toolchain is embedded. 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. $ hull build . hull build: my-tool 5.0 MB signed ed25519 # Run on Linux, macOS, 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" } ] }