Governed execution, built on one primitive: an action can only touch the world through capabilities the warden grants, mediates, and records. Least-privilege, audit, masking, record/replay, approval, revocation, kill — none of these are subsystems; they are rules applied at that single chokepoint.
┌─────────────────────────────────────────┐
session ──► policy gate ──►│ Ctx::invoke │──► capability ──► world
(identity) allow/deny/ │ interceptor chain: log · mask · meter │ fs.read
escalate ──► │ every call recorded (hash-chained) │ exec (hash-pinned)
approver │ killed? → refused │ sign (vaulted key)
(quorum) └─────────────────────────────────────────┘ pty (your shell)
The shape is the point: a sans-IO kernel (warden-core, zero dependencies) whose behavior lives
entirely in extension seams — Capability + its Broker, Policy (with Approver resolving its
escalations), Interceptor and Recorder (one observer axis, two powers), and Runtime. Six
kernel seams, four concerns — not a flat list of equal peers (Transport is a host concern, not a
kernel seam; see the design doc). Everything else is a pluggable implementation; adding a feature
means writing a plugin, not editing the kernel (see Plugins).
The most complete thing built on it so far is kedi, a governed web terminal — your real shell in the browser at native-feeling latency, with every session recordable, replayable, and killable. See crates/kedi.
|
Tip
|
New here? Start with the design. docs/DESIGN.md explains the
whole thing from the one idea outward — the chokepoint, the seams and what to plug where, the
event stream, the run loop, the plugin model, and kedi as the worked example. Two diagrams:
the runtime flow (a call crossing the door) and
the plugin model (how a |
Prebuilt binary (no Rust toolchain needed — kedi is one self-contained file):
# grab the binary for your platform from the latest release, then:
chmod +x kedi-* && mv kedi-* kedi
./kedi --open # starts a loopback background server + opens the tabkedi --open self-detaches a loopback-only server and opens http://localhost:8788 in a
Chromium-family browser; run it again and it just re-opens the tab (one server, singleton). Quit it
explicitly from the ⏻ button in the header (or POST /shutdown) — nothing lingers unless you
launched it. On Linux, packaging/install.sh puts kedi on your PATH and drops a desktop icon
(Exec=kedi --open) so it launches like any app.
See Releases for kedi-linux-x86_64 /
kedi-macos-aarch64.
From source (for building on it / hacking warden):
git clone https://github.com/kannonski/warden && cd warden
cargo run -p kedi -- --host kedi.localhostThe 30-second tour: double-click to draw a terminal (it’s your real $SHELL, native-fast), flip on
recording, open the audit panel to watch the live event feed, set your identity to root and see
policy refuse it, kill a session from the console, then scrub the whole thing back on the replay
timeline — over a hash-chained record that turns red if a single byte is doctored. Loopback-only by
design; recording is opt-in. Full guide: crates/kedi/README.
|
The sans-IO kernel: the seam traits plus the mediation flow ( |
|
The composition layer. An open, type-keyed extension registry (a point is any trait; new points
cost nothing) and a two-phase plugin loader that assembles a |
|
Capability-type implementations. |
|
The |
|
Secrets as capabilities. The action requests an operation over a secret ( |
|
The wasm |
|
The |
|
The remote axis over QUIC. Wardens dial OUT and register a name (no inbound ports); a client asks for a warden by name; the gateway opens a fresh bidi stream on that warden’s connection and splices the two. The warden still runs and enforces end-to-end; the gateway only moves bytes. |
|
Composition root (policy, quorum approver, interceptors, catalog) + demo binary. CLI:
|
|
The governed web terminal. Browser ↔ kedi is QUIC via WebTransport (HTTP/3); each pane is a
warden |
wit/action/warden.wit is the headless action contract; guest/ is the demo action (a Rust
component built against it). wit/app/app.wit is the interactive kedi:app contract for WASM-TUI
plugins (a pane hosting a ratatui-style app as a governed capability — see
the plugin design); guest-app/ is the hello proof.
A feature is a plugin, not an edit to the kernel or a monolithic composition root. An extension
point is any Send + Sync + 'static trait; the registry is open, so a plugin can even define a new
point that other plugins contribute to. Plugins load in two phases — contribute (only writes, so
load order never matters) then assemble (read the complete registry, add derived points) — and a
plugin declares what it provides/requires, validated at load.
Both consumers (kedi and the demo bin) are composed this way. The common case is one line:
use warden_host::{load, plugin, Manifest};
let warden = load(vec![
plugin(Manifest::new("audit").provides(&["recorder"]), |reg| {
reg.add::<dyn Recorder>(Arc::new(MyRecorder));
}),
// …a policy plugin, a capability broker plugin, a runtime plugin, …
])?.warden;For a cross-layer feature (say, DLP = a Detector point + an Interceptor + a Policy sharing
state), implement the Plugin trait directly so one object owns all its contributions.
Numbers from the built-in benchmarks (release build, Linux, loopback; run them yourself with
cargo test -p kedi --release <name> — --ignored --nocapture):
keystroke → echo round-trip (engine, browser excluded) |
p50 38µs · p99 <1ms |
|
bulk output through the governed pipeline |
~400 MiB/s |
|
audit cost |
×2 bytes, ~37 MiB/s drain |
hex-JSON amplification; the async recorder lags a 32 MiB burst by ~1.7s (documented trade-off) |
The engine adds microseconds; the gap to a native terminal is the browser’s render path, and kedi ships the two available levers (desynchronized canvas, sync render on interactive writes).
(cd guest && cargo build --release --target wasm32-wasip2) # the demo action (once)
cargo run # the demos: fs.read, the same session on the wasm runtime, a hash-pinned
# exec held for quorum approval (and an intern rejected),
# sign-without-seeing-the-key, a sandboxed component guest holding
# capability handles, the same session over QUIC, the remote axis through a
# gateway, and record -> verify -> rewind -> tamper-evidence
cargo test
cargo run -- replay /tmp/warden-session.jsonl [at] # verify + replay any record file
# direct, over QUIC (two terminals):
cargo run -- serve 127.0.0.1:4747
cargo run -- connect 127.0.0.1:4747 guest-demo --as you@here sign=deploy-key fs.read=/some/file
cargo run -- kill 127.0.0.1:4747 1000 # sever a live session mid-flight
# the remote axis — a gateway, a warden dialing out, a client routing by name (three terminals):
cargo run -- gateway 127.0.0.1:4748
cargo run -- tunnel 127.0.0.1:4748 prod-1 # warden dials out, no inbound ports
cargo run -- rconnect 127.0.0.1:4748 prod-1 guest-demo --as you@here sign=deploy-key fs.read=/some/file
# kedi — the governed web terminal (Chromium-based browser):
cargo run -p kedi -- --host kedi.localhost # then open http://kedi.localhost:8788A working spike, kept deliberately honest:
-
Wire security is spike-grade: self-signed certs, skip-verify clients, loopback binds. Identity is claimed, not authenticated — auth on the wire (mTLS/OIDC) is the next tier.
-
The wasm runtimes drive sync wasmtime and
block_onthe async chokepoint inside their host callbacks; a native async-wasmtime host is a later tier (the rest of the kernel is async). -
The record is tamper-evident, not tamper-proof: catching tail truncation requires anchoring the chain head externally.
-
Rewind is reconstruction of observed state, not undo of side effects.
-
The kill switch severs a session’s access to the world (every call refused at the chokepoint); preempting pure CPU inside a wasm guest is the epoch-interruption tier, later.
-
In-memory vault, demo approver; the product versions (real KMS/HSM, a quorum approver that parks for humans —
Approver::decideis already async) live behind the same seams.
Dual-licensed under either of
-
Apache License, Version 2.0 (LICENSE-APACHE)
-
MIT license (LICENSE-MIT)
at your option.