Skip to content
hexproofdevPublic

About

Unprivileged, self-applied Landlock + seccomp confinement wrapper — no root, no daemon, one static binary. It restricts itself, then execve()s your command; the child inherits every restriction irrevocably.

Topics

Resources

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

oring

Run an untrusted process inside a kernel-enforced cage — no root, no daemon, one static binary.

oring restricts itself, then hands control to your command. It applies Landlock (filesystem tiers, per-direction TCP, IPC scope), installs a seccomp-bpf denylist, sets NO_NEW_PRIVS, builds a clean environment, and execve()s the target. The child inherits every restriction irrevocably and can never widen it — so a compromise is contained by the kernel, not by trust.

oring --profile balanced --workdir /work/repo    -- cargo test   # build/test; TCP :443 to any host, not a firewall
oring --profile strict   --workdir /work/scratch -- ./sample     # untrusted binary; no TCP egress (UDP/DNS open), W^X
oring --policy /etc/oring/rig.json               -- ./fuzzer     # explicit JSON policy
oring --profile balanced --workdir /work/repo    --explain       # preview the plan, run nothing

oring is honest about what it is not: not an orchestrator, a network-policy engine, or a VM boundary, and it does not author host MAC — those stack underneath it. The residuals it cannot close are written down plainly in docs/DESIGN.md §2, not glossed over.

Profiles

Two baked-in profiles, tested against the shipping binary on a real kernel. balanced is for a coding agent that builds code — toolchain caches granted, workdir write_exec, :443 egress to any IP (port-only, not a domain/IP firewall). strict is for an untrusted binary — nothing granted, workdir write_noexec (W^X), deny-all TCP egress (UDP/DNS still open), JIT blocked. Both block reading outside their tiers (SSH keys, shell history), writing system paths, the privilege-escalation syscalls, and abstract AF_UNIX connect(). Anything in between is a small JSON --policy (schema in docs/DESIGN.md §7).

Wrapping an agent: balanced grants the toolchain caches but deliberately not an agent's own state directory (~/.claude, ~/.config), and the child's environment is an allowlist (secrets withheld unless you --env-keep NAME them). So -- claude won't start as-is; decide the credential trade on purpose — and know the honest limits. Grant ~/.claude so the agent can run and the API key inside it is readable by the (possibly prompt-injected) agent: confinement buys blast-radius reduction, not credential protection. Two residuals oring cannot close for you: egress is port-only to any IP (DNS/UDP open), so a host firewall or filtering proxy is a required companion control, not optional; and the agent has full write over its workdir — .git/hooks, Makefile, build.rs, package.json scripts all run on the next git/build/test you run outside the cage, so treat a touched workdir as untrusted and run the first build inside oring. Full story in docs/DESIGN.md §2/§9.

How it works

  • Ring 1 — Landlock: four filesystem tiers, per-direction TCP allowlists, IPC scoping. Hard floor kernel 6.15 / ABI V7 — below it oring refuses rather than run a partial cage. The V9 RESOLVE_UNIX right (walls connect() to a pathname AF_UNIX socket like docker.sock or ssh-agent) is optional hardening behind --features resolve-unix; without it that residual is reported loudly at seal.
  • Ring 2 — seccomp: a denylist removing the known escape launchpads (io_uring, userfaultfd, bpf, ptrace, the mount and namespace family), with a mandatory x32-ABI guard so the filter can't be re-entered through the compat ABI.

When a denial leaves you with a bare EACCES, --explain shows the effective policy and --denials <pid> reconstructs what the kernel blocked.

Build

Linux ≥ 6.15 with Landlock enabled (grep landlock /sys/kernel/security/lsm), x86_64.

git clone https://github.com/hexproofdev/oring && cd oring
make build      # static-musl binary with an embedded auditable SBOM
make check      # the gate: fmt + clippy + test   (make help lists the rest)

oring depends on the published landlock crate — nothing vendored. The optional V9 resolve-unix hardening builds against an out-of-tree fork (upstream rust-landlock#135); see .cargo/config.toml.example.

Alternatives

For a batteries-included agent sandbox, reach for one of these — they do things oring deliberately doesn't:

  • sandbox-runtime (Anthropic) — cross-platform (macOS/Linux/Windows), domain-level network egress through a proxy, and masks files like .git/hooks inside your workdir. Needs Node, bubblewrap, and socat.
  • clampdown — container-based, with a credential-brokering auth proxy and per-API egress. Needs a container runtime and a privileged sidecar.

oring is the minimal end of that spectrum: one unprivileged static binary, no runtime, no daemon, no user namespaces (Landlock, not bubblewrap), and a confinement small enough to formally verify. It runs where those can't — distroless images, userns-disabled kernels, a bare CI step — and works as a defense-in-depth layer inside them.

Status

v0.1.0. Design and threat model: docs/DESIGN.md. How it's proven: docs/VERIFICATION.md. Attack catalogue: docs/ATTACKS.md. Release history: CHANGELOG.md. Contributors and agents start with CLAUDE.md. Found a way past the cage? See SECURITY.md — that's exactly what we want to hear.

BSD-3-Clause — see LICENSE.

About

Unprivileged, self-applied Landlock + seccomp confinement wrapper — no root, no daemon, one static binary. It restricts itself, then execve()s your command; the child inherits every restriction irrevocably.

Topics

Resources

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages