Hull
The execution boundary beneath your agents

Code became disposable. Trust is not.

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.

Single static binary
Lua · QuickJS · WASM · WebGPU
pledge + unveil (Linux/OpenBSD/cosmo) · Seatbelt (macOS)
Local · edge · on-prem · air-gapped
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

The hardened execution layer for AI-generated code.

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.

How

The security contract travels with the application.

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.

Use cases

Code you did not write. Authority you still control.

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.

Run agent-generated tools

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.

Ship internal automation

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.

Deploy beyond the cloud boundary

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.

Works beneath your stack

Your agent platform decides what to run. Hull decides what that code may do.

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.

Responsibility boundary
Agent, model, workflow, or developer tool
Chooses or generates the application code
Signed Hull application
Declares the exact modules, hosts, paths, environment, process, and compute authority
Native capability layer and OS sandbox
Allows declared operations and rejects everything else
Filesystem
Network
Database
Process
GPU / WASM
Cloud agent platforms

Govern identities, collaboration, models, and connected company resources inside their platform.

Hull

Constrains generated code in a signed, independently deployable application wherever it must run.

Your infrastructure

Provides identity, placement, scaling, scheduling, and operations according to your environment.

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.

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.

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

6.5 MB
Full web app
HTTP · DB · WASM · Lua · JS composed in, statically linked
2.1 MB
Pure compute
An app.main build links zero Keel, mbedTLS, SQLite, WASM
<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.

Composable base

The binary is composed to fit. Every piece is signed.

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.

Attack surface is what you use

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.

Composition doesn't weaken trust

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.

Bolt on the heavy things

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.

One database API, four backends, selected by DSN

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.

For agents

The execution boundary beneath your agents.

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.

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.

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. 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
Stdlib

What ships in the box.

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 & authz
Production-grade, audit-converged
  • auth-flows · middleware/totp
  • middleware/oauth (Google + Entra)
  • middleware/audit-log · middleware/session
  • middleware/rbac · pwned · qrcode
HTMX widgets
Server-rendered, structural-only CSS
  • htmx/toast · htmx/confirm (styled <dialog>)
  • htmx/form (errors + auto loading state)
  • htmx/search · htmx/inline-edit
  • htmx/sort · htmx/pagination · htmx/table (schema-driven grid)
  • 8 widgets, ~580 LOC of plain JS total; browser-tested via Playwright + axe-core a11y scan
Files & uploads
Streaming, content-addressed, MIME-validated
  • req:multipart() incremental iterator
  • attachment · blob (SHA-256 CAS) · mime
  • web/attachment-serve with auth-check hook
Web fundamentals
Templates, routing, middleware, observability
  • template (compile-cached, XSS-safe) · validate
  • middleware/csp (nonces) · middleware/csrf · middleware/cors
  • middleware/ratelimit · middleware/idempotency
  • middleware/logger · middleware/health · middleware/etag
Compute & data
WASM + WebGPU + multi-backend SQL
  • compute (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.udf
  • search (SQLite FTS5) · image
Resilience & delivery
Outbox, inbox, timers, transactions
  • middleware/outbox · middleware/inbox
  • middleware/transaction (auto BEGIN IMMEDIATE / COMMIT)
  • app.every(ms, fn) · app.daily("HH:MM", fn)
  • jwt (HS256/384/512, RS256/384/512, PS256, ES256/384)
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.
  • Binary hardening verified at release
    PIE+ASLR, full RELRO, NX stack, stack canaries, FORTIFY_SOURCE, CET/IBT (x86_64) or PAC/BTI (arm64), zeroed call-used registers. Probed per toolchain; release fails if any required protection drops. Plus 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.
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 ships today, and what it doesn’t (yet).

Shipped through v0.10.0
  • Fully-composable SLIM base (v0.9.0). The distributed base drops every reducible subsystem - both runtimes, HTTP, the Keel event loop, TLS, SQLite, WASM, image codecs - and 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.
  • Composable features + extension taxonomy (v0.7.0). Five large optional subsystems - 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.
  • Compiler-free & toolchain-free builds (v0.10.0). 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.
  • Multi-backend database, zero SQLi surface. One 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.
  • 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.
  • Production-grade auth stack (v0.4.0). 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.
  • Streaming multipart + content-addressed blob storage (v0.4.0). 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.
  • HTMX hypermedia profile + widget tier (v0.4.0). 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.
  • Browser-driven E2E suite (v0.4.0). 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.
  • Asymmetric crypto + HMAC vtable. RS256/384/512, PS256, ES256/384 verify via mbedTLS, fronted by an 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.
  • Backend-agnostic database layer. The 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.
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.

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, 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.

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. 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
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" }
  ]
}