threat model · isolation boundary · supply chain · disclosure

The boundary,
stated precisely.

The sandbox page shows what riz confines. This one draws the line the other way: what each layer protects, and exactly where the confinement stops. Every protection below maps to a test that runs in CI — and every gap is named, not glossed.

what runs where

Strong isolation is a WASM property —
not a whole-runtime one.

Native runtimes (Bun, Node.js, Python, Rust, Go) get resource ceilings and, on Linux, a filesystem allowlist. They do not get a syscall or network jail. Deny-by-default confinement is the runtime = "wasm" path. Read each row as "what holds, and where it doesn't."

Defense layer Native
bun·node·python·rust·go
WASM
runtime="wasm"
Boundary / limit
OS process + kill-on-drop ✓ yes ✓ yes Each handler is its own process group; the child is SIGKILLed with the host.
Always-on rlimits
CORE=0 · NOFILE · FSIZE · NPROC
✓ yes ✓ yes Resource / DoS ceilings — not a confinement boundary.
prctl pair
PDEATHSIG · NO_NEW_PRIVS
Linux only Linux only Blocks setuid/exec privilege escalation. A no-op on macOS/BSD.
Opt-in rlimits
RLIMIT_AS · RLIMIT_CPU
opt-in opt-in Per-function memory / CPU caps. Off unless configured.
Landlock FS allowlist Linux ≥5.13 Linux ≥5.13 No filesystem confinement on macOS/BSD; best-effort below kernel 5.13.
WASI deny-by-default
filesystem + network
✗ no ✓ yes No fs, no sockets unless granted. Native code is not WASI-confined.
Capability broker
host-held credentials
✗ no ✓ yes Guest never opens a socket; the DSN never crosses the WASI boundary.
Guards in / out
fail-closed policy .wasm
✓ yes ✓ yes One policy module wraps every runtime identically; a policy that can't run denies.
Syscall filter
seccomp-bpf
✗ no ✓ via WASI Native code runs at the OS user's full syscall surface. riz ships no seccomp filter.
Network egress ✗ no ✓ deny-default Native egress is unrestricted — control it with a network policy or firewall.
The one-line version: to run untrusted or model-generated code with a real capability boundary, use runtime = "wasm". Native runtimes are for code you trust, hardened against faults and resource abuse — not confined against a hostile author.
proof child_inherits_always_on_caps · child_with_allowlist_can_read_allowed_path_only · one_wasm_guard_protects_every_runtime — the profile, the Landlock allowlist, and the cross-runtime guard, in CI.
the walls, and their edges

Three walls for WASM —
each with a named limit.

A runtime = "wasm" request passes the WASI capability check, runs, then leaves through the always-on host profile — wrapped on both sides by fail-closed guards. The walls are real; so are their edges:

Landlock needs kernel ≥5.13 and degrades best-effort below it — and does nothing on macOS/BSD.
rlimits are resource ceilings, not a confinement boundary — they bound blast radius, they don't contain intent.
No built-in container / user namespace. Compose your own (container, VM, dedicated OS user) for defense in depth.
the wallsper request
requestguard_in            fail-closedWASI capability check   deny-by-default
  fs: no grant → denied
  net: no grant → denied
  ↓
your .wasm runs       sub-ms spawnhost safety profile   always-on
  rlimits · PDEATHSIG · no-new-privs
  ↓
guard_out           redact / blockresponse
threat model

Assets, boundaries,
and what's in scope.

The assets riz protects: the host and other tenants' data, the network, and any credentials it holds. The boundaries that protect them: the WASI line, the OS process boundary, and the auth surface on /deploy and /_riz/*.

/deploy fails closed: with neither a deploy key nor a CIDR allowlist configured, it refuses every request (503) rather than exposing a hot-swap RCE. Credentials for a broker resource live host-side and never enter guest memory.

In scope for reports: sandbox / capability escapes, auth bypass (JWT/JWKS authorizers, bearer-gated /_riz/*), static-serving path traversal, the process safety profile, and POST /deploy. Out of scope: misconfigurations riz already rejects at startup, and non-HTTP/WS AWS event sources (out of project scope by design).

trust boundariescredentials stay host-side
Host / OS user
assets: fs · network · DB creds · co-tenant data
riz process
/deploy → key or CIDR, else 503
/_riz/* → bearer-gated
handler process
rlimits · prctl · Landlock
WASI sandbox no fs · no net
guest .wasm
names a grant → broker runs it host-side
the DSN never crosses the WASI line
proof deploy_refuses_when_no_auth_configured · wasm_guest_brokers_pg_through_capability_grants — the fail-closed deploy gate and the full broker chain (guest → host → Postgres, creds host-side), in CI.
supply chain

The dependency set is
gated, not assumed.

Every push runs cargo-deny against deny.toml: RUSTSEC advisories, an SPDX license allowlist, banned/wildcard crates, and registry provenance. A newly-published CVE can fail CI with no code change — that's the point. The fix is a dependency bump; where no fixed release exists, a dated, documented exception in deny.toml, reviewed on its expiry.

advisory gate

cargo-deny in CI. Advisories with a fix are resolved by bumping — recent examples: wasmtime-wasi (a WASI FilePerms bypass on the sandbox's own filesystem boundary), anyhow, crossbeam-epoch.

SBOM

A CycloneDX SBOM (riz.cdx.json) is generated each build and published as an artifact, so a consumer can diff the exact dependency set that produced a release.

build provenance

Release binaries carry keyless GitHub build-provenance attestations (SLSA, via Actions OIDC — no signing keys held). Verify: gh attestation verify <file> --repo 24X7/riz.

on the roadmap

Detached signatures, a curl … | sh installer that verifies before it runs, and an external security review. Named here so the gap is visible, not implied away.

source deny.toml · ci.yml — the gate and the SBOM job are in the repo, not a claim on a page.
production hardening

Degrade, don't fall over —
and prove each hardening.

The request path has no reachable panic on runtime data, queues are bounded with backpressure, and bad input sheds one request rather than the fleet. Each line below is a fix with a named test.

Auth & edge. Reflected-origin credentialed CORS is rejected at config time; the JWKS authorizer is cached per issuer so a stream of invalid tokens can't amplify into an IdP fetch each; the authorizer returns an error on every hostile token instead of panicking the request task.

Supervision & ops. A config hot-swap rebuilds the pool so a concurrency change actually resizes admission; a readiness probe sheds traffic the instant a drain starts; and the metrics endpoint exposes saturation — utilization and load-shed counts, so overload shows up before requests start dropping. Off Linux, an unenforceable filesystem sandbox warns loudly instead of running silently unconfined.

proof validate_rejects_wildcard_origin_with_credentials · jwks_authorizer_cache_fetches_once_within_cooldown · authorizer_rejects_arbitrary_tokens_without_panic · hot_swap_resizes_concurrency · ready_when_serving_draining_after_shutdown_signal · metrics_emit_saturation_reliability_and_build_info · allowed_paths_sandbox_enforcement_matches_platform — seven fixes, seven tests, in CI.
source SAFETY.md · PRODUCTION-READINESS.md · METRICS.md — the NASA Power-of-10 policy (253 ratchet sites to zero), the roadmap, and the metrics design.
disclosure

Found something?
Tell us privately.

Please don't open a public issue for a security problem. Report via GitHub's Security → "Report a vulnerability" (private advisory), or email chris@riz.dev. Include impact, a minimal riz.toml + handler to reproduce, and the riz version / OS / runtime. We aim to acknowledge within 3 business days and coordinate disclosure with you.