Hull
Built for code you almost trust

Code became disposable. Trust is not.

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.

Single static binary
Lua · QuickJS · WASM · WebGPU
pledge + unveil (Linux/OpenBSD/cosmo) · Seatbelt (macOS)
Linux · macOS · APE
manifest
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.

Why

Code became disposable. Trust did not.

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.

What

Hardened runtime infrastructure for AI-native systems.

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.

How

Every external capability is declared. The kernel enforces it.

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.

Install & verify

One line to install. One line to verify.

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.

shell copy & paste
# 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).

5.0 MB
Full binary
HTTP · DB · WASM · Lua · JS, statically linked
4.4 MB
Pure compute
No HTTP, no DB. CLI / compute service flavor
<1 ms
Script dispatch overhead
Lua / JS handler entry. Sub-ms on top of compute
0
System packages required
No package manager, no dlopen, no JIT
Scope

What Hull is not.

Before you spend more time on this page, four things Hull does not solve, named plainly, so you can decide whether to keep reading.

  • Not a defence against CPU-level side channels. Spectre-class attacks, cache timing, and other shared-hardware leaks are out of scope. Capability isolation operates above that layer.
  • Not a Lambda or Workers replacement. Hull is one process per app. There's no built-in autoscaler, no multi-tenant isolation; horizontal scaling is your platform's job.
  • macOS kernel sandbox is best-effort. 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.
  • Sealing protects from tamper-after-build, not from a compromised build host. Supply-chain attacks on the toolchain or a poisoned vendored dependency live outside the signed-bundle guarantee. Vendoring helps; build reproducibility helps; sealing alone doesn't.
Core principles

Four properties, enforced from the bottom up.

Fail at load, not at first use

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.

No ambient authority

Scripts cannot reach io, os, load, eval, Function(). The runtime's host environment is sealed.

W^X, no runtime codegen

Pages are writable or executable. Never both. No JIT. No runtime dynamic code. WASM runs from AOT-compiled artifacts embedded at build time.

Compute in WASM and WebGPU

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.

For agents

The runtime agents already live in.

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.

A sandbox for LLM-callable tools

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.

  • Gas-metered Lua/JS · AOT WASM · deny-default everything.
  • Sealed signed binary. The agent can't smuggle native code in.
  • Kernel sandbox engaged at process start. Ambient authority drops away.

A CLI surface for AI coding agents

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 agent routes
$ hull agent db schema
$ hull agent errors
$ hull agent test
$ hull agent deploy
$ hull mcp

hull dev --agent also writes .hull/dev.json and .hull/last_error.json sidecar files for live reload + structured error reporting.

Two patterns the manifest model makes safe.

Agent tools

A bounded host for LLM-callable tools. The agent gets exactly the surface you declared, nothing else.

Constrained AI-generated code

Execute generated handlers behind a manifest the model didn't write. Capability creep is impossible by construction.

Why not …?

Why a new runtime.

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. Permissions can be declared in 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.
  • ~ WasmEdge. Capabilities are WASI imports declared per-module; there's no app-level manifest binding them to host policy.
  • n/a Workers. Runs on Cloudflare's V8 isolate fleet; the "single binary" and "kernel sandbox" categories don't apply.
How it works

One manifest. One handler. One sealed binary.

The diagram below is the runtime. The four steps further down are how you, the developer, make it apply to your app.

Defense-in-depth flow
REQUEST SYSCALL Lua / JS handler runtime sandbox gas-metered Manifest gate modules · hosts fs · env C capability layer hl_cap_* allowlists · realpath Kernel sandbox pledge · unveil seatbelt DENY DENY DENY a denial at any layer is a hard stop, not a warning
1
Declare modules and capabilities
Modules, hosts, paths, env. Nothing else is reachable.
2
Register routes and handlers
HTTP, WebSocket, SSE, timers, or a one-shot app.main.
3
Build & sign
hull build emits a self-contained signed binary.
4
Run anywhere
Kernel sandbox engages, app starts, ambient authority drops away.
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)
Runtime
Lua 5.4
or QuickJS. One per app
Compute
WASM · WebGPU
WAMR AOT · wgpu-native
Kernel
pledge / unveil
seatbelt on macOS
Security model

Defense in depth, designed in.

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.

  • Guest-controlled memory is never executable
    W^X across the runtime. No JIT. WASM AOT artifacts are produced at build time and shipped read-only.
  • No undeclared filesystem, network, env, or process access
    Each capability resolves against the manifest allowlist at the C boundary. Path traversal is rejected via realpath ancestor checks.
  • No runtime package installation
    require and import resolve only against the embedded stdlib registry. Nothing is fetched at run time.
  • No dynamic native loading
    No dlopen, no FFI, no extension modules. The native surface is fixed at build time.
  • Kernel sandboxing on supported platforms
    Linux/Cosmo: pledge + unveil. macOS: sandbox_init with a manifest-derived profile.
Trust chain

Three signatures. One trust root you can replace.

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.

Tier 1 Release
hull.sha256.sig
Signed object
SHA-256 manifest of the four release binaries
Signer key
Release key, offline at gethull.dev
Verifier
hull update, hull verify-release, browser

Stops a hijacked CDN, a swapped binary on a stale mirror, a downgrade attack pointing you at an older signed release.

Tier 2 Platform
package.sig · inner
Signed object
The Hull platform library + integrity canary, per architecture
Signer key
Platform key, offline at gethull.dev (or your fork)
Verifier
hull verify, browser

Stops a developer shipping “Hull-built” binaries with a custom runtime that pretends to enforce the sandbox. Canary in the binary lets the verifier prove the real platform is present.

Tier 3 App developer
package.sig · outer
Signed object
App binary hash + manifest + every embedded source file’s hash
Signer key
The app developer’s own key
Verifier
hull verify, browser, runtime --verify-sig

Stops a tampered app (modified bytes, swapped manifest, expanded capabilities) being passed off as the original. You hold the developer’s pubkey separately as your identity anchor.

Tier 4 Source-to-binary re-derivation
make reproducible-check
Signed object
None. This is a re-derivation, not a signature. Build twice, hashes match.
Signer key
None. The auditor is whoever has the source and the compiler.
Verifier
make reproducible-check, Linux CI-gated

Closes the one attack the signature chain alone cannot catch: a compromise of the signer’s own machine. The developer signs a malicious binary; the signature is “valid.” But anyone with the source can rebuild and compare hashes: if they don’t match, the signing process was compromised. Three layers gated: make itself, hull build output, and the make self-build bootstrap chain.

Trust anchor

One pinned key, two safe replacement paths.

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:

  1. Fork the source. Hull is AGPL-3.0. Generate your own release + platform keys, replace the two 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.
  2. Override per-run. Pass --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.

Honest scorecard

What v0.1 ships, and what it doesn’t (yet).

Ships in v0.1
  • Three-layer Ed25519 signature scheme. Release, platform, app developer. Cryptographic primitives wired and verifiable.
  • Signed release manifest. Every hull update verifies hull.sha256.sig against the embedded release pubkey before rename(2).
  • Browser verifier (this site). Drop a manifest, a built app, a binary. Runs locally in your browser via vendored TweetNaCl. No upload, no telemetry.
  • CLI verifier. hull verify-release + hull verify against the embedded keys.
  • Manifest-declared capabilities. Every fs/env/host access checked at a capability boundary the kernel enforces.
  • Kernel sandbox. pledge + unveil on Linux/Cosmo; Seatbelt SBPL on macOS. Violation = SIGKILL or EPERM.
  • Signed platform-sig chain (v0.1.3). Release CI signs the per-arch SHA-256 manifest of 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.
  • Reproducible builds (Linux + macOS CI-gated). Same source + same hull version produces a byte-identical binary, verified per commit via 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.
  • Live SBOM (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.
  • Three independent trust roots, any one sufficient. (a) Three-layer Ed25519 signature chain (gethull release key, gethull platform key, your developer key). (b) Sigstore + Rekor transparency log entry per release (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.
  • One-command self-verify (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.
  • Self-sovereignty path, documented. AGPL source. Fork, rotate the two embedded pubkeys, ship your own trust root. 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.
Not yet. Honest gaps
  • Post-install binary integrity is out of scope. The signature chain protects the bytes from publisher to disk. Once 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.
  • No key transparency log. If a publisher’s key is compromised and rotated, current users have no automatic notification. Re-pinning is manual until we wire something like Sigsum.
  • No timestamp authority. Signatures don’t carry a verified timestamp, so a stale-but-once-valid release can’t be distinguished from a current one by signature alone. Mitigated today by version pinning and SHA-256 manifests.
  • Developer-pubkey distribution is out-of-band. You verify a developer’s app against their pubkey, but the pubkey itself comes from their repo / website / personal page. The same TLS-trust problem most ecosystems have. No PKI yet.
  • install.sh is SHA-256 only. First install trusts 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.

Developer experience

From scratch to a sealed binary in four commands.

Hull keeps its surface small on purpose. The CLI is one binary. The toolchain is embedded. The signing keys are yours.

shell
# 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
shell what an AI coding agent sees
# 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" }
  ]
}