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.
Native runtimes (Bun, Node.js, Python, Rust, Go) get resource ceilings, a filesystem allowlist,
and a 22-syscall seccomp-BPF escape/tamper blocklist — all on Linux. What they do
not get is deny-by-default confinement: a full syscall allowlist
or a network jail. That 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 creds + signing |
✗ no | ✓ yes | Guest never opens a socket; no DSN, key, or SigV4 signature crosses the WASI boundary. Postgres, DynamoDB, S3, and origin-pinned HTTP all run host-side. |
| 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 |
Linux | Linux + WASI | On Linux every worker gets a deny-EPERM blocklist of 22 escape/tamper syscalls (ptrace, mount, bpf, kexec, module load, setns, keyctl, clock/host mutation) applied in pre_exec — a blocklist over the escape surface, not a full allowlist jail. No-op on macOS/BSD. |
| Network egress | ✗ no | ✓ deny-default | Native egress is unrestricted — control it with a network policy or firewall. |
runtime = "wasm". Native runtimes are for code you trust, hardened
against faults and resource abuse — not confined against a hostile author.
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.
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:
request ↓ guard_in fail-closed ↓ WASI capability check deny-by-default fs: no grant → denied net: no grant → denied ↓ your .wasm runs sub-ms spawn ↓ host safety profile always-on rlimits · PDEATHSIG · no-new-privs · seccomp (Linux) ↓ guard_out redact / block ↓ response
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 — a Postgres DSN or AWS keys — live host-side and never enter guest memory; for DynamoDB and S3 the SigV4 signature is computed host-side too.
In scope for reports: sandbox / capability escapes, auth
bypass (JWT/JWKS authorizers, the per-caller X-Api-Key gate + token-bucket
rate limiting, 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).
deploy_refuses_when_no_auth_configured ·
wasm_guest_brokers_pg_through_capability_grants ·
worker_env_is_scrubbed_of_daemon_secrets — the fail-closed deploy gate, the
full broker chain (guest → host → Postgres, creds host-side), and worker env scrubbed to an
allowlist so a resource secret lives only in the daemon, in CI.
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.
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.
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.
Release binaries carry keyless GitHub build-provenance attestations (SLSA, via Actions
OIDC — no signing keys held). Verify: gh attestation verify <file> --repo 24X7/riz.
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.
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. Deploy, config-reload, and auth-denial events emit on a
dedicated riz.audit tracing target, scrubbed of secret material
(RUST_LOG=riz.audit=info). Off Linux, an unenforceable filesystem sandbox
warns loudly instead of running silently unconfined.
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.
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.