Skip to content

Repository files navigation

bee — eBPF-Enforced Sandbox Harness for Coding Agents

bee is a Rust library and CLI that provides kernel-enforced, capability-scoped sandboxing for AI coding agents. It uses eBPF LSM hooks (via Aya) to enforce per-tool-call and per-subagent policy — file access control, process-exec allowlisting, and network egress filtering — without containers, VMs, or namespace overhead.

See specs/001-ebpf-agent-sandbox/ for the full spec, plan, research, data model, and contracts.

Design principles (constitution)

  1. Deny-by-default & fail-closed — zero capabilities to start; refuse rather than degrade silently.
  2. Capability attenuation — a subagent policy is provably a subset of its parent.
  3. Kernel enforcement is authoritative — user space compiles/loads/observes; the kernel decides.
  4. Policy-as-data — declarative, diffable, reviewable TOML is the source of truth.
  5. Library-first, runtime-free core — the CLI is a thin wrapper; the core needs no async runtime.

Workspace

Crate Role Status
Five packages, one host executable (ADR-0002):
Cargo.toml           # workspace root *and* the `bee` application package
src/                 # the harness library (episodes, tools, REPL core, render pipeline)
src/app/             # the application layer: commands, config resolution, session construction
crates/
  core/  common/  userspace/  ebpf/
Package Role Status
bee (root) The application and sole host executable, plus the harness library it drives ✅ host-tested; enforcement behind features
crates/common #[repr(C)] map layouts + verifier-safe matcher primitives (no_std) ✅ implemented + tested
crates/core Policy types, TOML parsing, compiler/glob-lowering, attenuation validator, audit types ✅ implemented + tested
crates/userspace Support detection, cgroup lifecycle, hardened launcher (incl. process hardening, FR-011), engine gating ✅ host logic tested; ⏳ eBPF attach behind --features enforce
crates/ebpf LSM programs (file_open, bprm_check_security, socket_connect), built as bee-lsm ⏳ requires nightly bpf toolchain + BPF-LSM kernel

The four crates/ packages stay separate because their runtime, kernel, no_std, and BPF-target constraints earn it. The harness library does not: it has exactly one consumer.

Build & test

User-space crates build on stable Rust:

cargo build
cargo test          # ~47 unit/integration/property tests (no kernel needed)
cargo clippy --workspace --all-targets

Try the CLI (works without a special kernel):

./target/debug/bee                                                    # help — bare `bee` runs nothing
./target/debug/bee check                                              # kernel support gates
./target/debug/bee validate --policy policies/cargo-test.toml         # prove policy is runnable
./target/debug/bee validate --policy policies/subagent.toml \
                            --parent policies/parent.toml             # attenuation check

The two primary journeys

# Headless: one agent episode, a batch of them, or a concurrent batch.
./target/debug/bee run --scenario <scenario.toml> --provider <provider.toml>

# Interactive: chat with a sandboxed agent, inline or full-screen.
./target/debug/bee repl --policy <policy.toml> --provider <provider.toml>

Both resolve the same effective configuration — explicit flags, then --config <file>, then .bee/config.toml, then ~/.config/bee/config.toml — and both refuse to start rather than run unenforced by accident. A session with no policy needs an explicit --host, which says out loud that tools will run as hardened host processes with no kernel scope. See ADR-0001 and ADR-0002.

bee exec --policy P -- COMMAND runs a single host command inside a scope. It is a diagnostic — a way to prove a policy enforces with no agent in the picture — and needs a BPF-LSM kernel, so it fails closed on an ordinary host. bee metrics reports recorded usage, cost, and latency.

Full-screen TUI (optional)

bee repl runs the classic inline REPL by default. Build with the tui feature and pass --tui for a full-screen chat surface with model-owned live panels (008-grid-tui):

cargo build --release --features tui
./target/release/bee repl --provider <provider.toml> --tui        # --no-tui forces inline

The agent can address a rendered widget to a named side panel with render_to("metrics", widget) (and render_to_ttl / remove_panel / clear_panels to manage them); an untargeted render(widget) still flows inline. --tui degrades honestly — piped output, TERM=dumb, or a terminal below 40×10 fall back to the inline REPL with a one-line note. The feature is off by default, so the standard build pulls in no terminal backend.

Assistant replies render as markdown once the message finishes streaming — headings, emphasis, lists, links and code, styled through the active theme's semantic roles rather than a second palette. A skill's instructions render the same way when you /skill <name>, and the agent can emit a block itself with markdown(source) in a render script.

Keys: / and the mouse wheel scroll the conversation (they move the cursor first when the input holds a Shift+Enter newline), PgUp/PgDn page it, gg/G jump to the ends, Ctrl-P/Ctrl-N walk input history, Tab focus, p panel overlay on narrow terminals, y yank, ? help, q quit. Mouse reporting is on so the wheel works, which means drag-to-select is the terminal's Shift-click gesture there.

Security analysis tooling (opt-in)

Tools for using an agent to review code, rather than only to write it. All default-off: a build that selects none of them has the dependency graph it had before they existed.

cargo build --features sec,astgrep-rust,astgrep-python   # everything, with two grammars
cargo build --features findings                          # just the ledger
cargo build --features astgrep,astgrep-rust              # just structural search, one grammar
Feature Tool What it is
astgrep + astgrep-<lang> ast_grep Structural search over the parse tree, so a match in a comment or a string literal is not a match. One feature per grammar — each is compiled C, so a build pays only for the languages it scans.
findings record_finding, list_findings The durable finding ledger.
cvss cvss Severity computed from a vector, never asserted by the model.
scanners scan External scanner adapters — Opengrep for pattern rules, CodeQL for whole-program analysis. Implies findings.
gitlog git_log Repository history via gixlog for the commits touching a path, blame for the commit that introduced one line. Read-only; runs in a scope-joined child like every other file tool, because .git is a directory of files.
sec Umbrella for all of the above. Grammars stay explicit.

Two tiers, one rule. When the value is the engine, bee links the crate and runs it as a scope-joined child. When the value is a curated rule corpus, bee drives the third-party binary instead of reimplementing it — under an inode-pinned exec.allow grant, with argv built from typed inputs. The model chooses what to scan; never how the scanner is configured.

# policy — the capability
[exec]
allow = ["!/home/you/.local/bin/opengrep"]      # `!` pins the inode

# config — how the granted binary is configured (operator-only)
[security.scanners.opengrep]
rules = "/etc/bee/opengrep-rules"               # `auto` is refused: a scanning scope has no egress

CodeQL is the same shape, plus a version pin. The grant names the bundle's own CLI; the pin is verified by running it before anything is analysed, so a bundle that is not the one the operator provisioned is an explicit unavailability rather than a thin set of results.

[exec]
allow = ["!/opt/codeql-bundle/codeql/codeql"]

[security.scanners.codeql]
bundle_version = "codeql-bundle-v2.26.1"        # or "2.26.1" — either spelling of the same pin

Databases are built one way, --build-mode=none, so actions, csharp, java, javascript, python, and ruby are analysable and everything else is declined by name. Extracting a compiled language means intercepting the build's process spawns, which would mean admitting every compiler and linker that build happens to invoke — an unbounded widening of the very scope bee exists to hold. Half-support would be worse than none: a thin database yields few findings, which reads exactly like clean code.

Findings outlive the session. They land in .bee/findings/ledger.jsonl — append-only, one JSON object per line, meant to be committed and reviewed in a pull request. Re-running merges rather than duplicating, and identity excludes the line number so code movement does not mint a duplicate.

./target/debug/bee findings list
./target/debug/bee findings adjudicate <id> --state false-positive --note "sanitised upstream"

Adjudication is CLI-only and has no tool equivalent: an agent can record what it saw, and only a person can rule on it. A later automated rediscovery appends a sighting and cannot clear the verdict.

Every tool answers in three states — completed, could-not-run, or failed. A missing binary, an ungranted scanner, a substituted binary, a timeout, or an unparseable report each say so explicitly. None of them can render as "scanned, found nothing", because a security tool that reports no issues when it did not run is worse than one that was never installed.

MSRV note. The application package declares Rust 1.88 (ast-grep-core's floor). The embeddable crates — bee-core, bee-common, bee-userspace — keep 1.85.

Kernel requirements (for enforcement)

The enforce feature and the crates/ebpf package require:

  • Linux ≥ 5.7 with CONFIG_BPF_LSM=y and CONFIG_DEBUG_INFO_BTF=y,
  • bpf in the active LSM list — check cat /sys/kernel/security/lsm for a bpf token; if absent, add lsm=...,bpf to the kernel cmdline and reboot (CONFIG_BPF_LSM=y alone is not sufficient),
  • cgroup v2 unified hierarchy, and
  • the nightly bpf toolchain: rustup toolchain install nightly --component rust-src and cargo install bpf-linker (LLVM version matched to the pinned nightly — the top build-break risk).

bee check verifies these gates and fails closed (exit 65) when enforcement is unavailable, so bee never runs with silently-absent enforcement.

Caveats worth knowing

  • Policy preparation has two stages. bee-core compiles author intent into a backend-neutral CompiledPolicy; bee-userspace must then produce an eBPF EnforcementPlan before any kernel state is touched. bee validate runs both stages, so success means the policy is runnable by this backend.
  • Current eBPF glob support is exact/subtree plus postfix (*.ext). Segment (**/name) and bounded-star patterns are normalized by the core but rejected during enforcement planning, as are inode-pinned executables and enabled exfiltration detection. Nothing is silently weakened.
  • Path matching sees resolved paths. LSM hooks fire after path resolution, so enforcement is TOCTOU-resistant, but a rule written against a symlink's name won't match its target, and name-based rules don't catch hard links / alternate bind mounts to the same inode. Inode-pinned rules are not yet implemented and are rejected during enforcement planning.
  • Capabilities to load LSM programs: CAP_BPF + CAP_PERFMON (possibly + CAP_MAC_ADMIN) or CAP_SYS_ADMIN. The exact minimal set is kernel-version-dependent — verify empirically per target.

Not in the MVP

Exfiltration detection (US3), the Landlock+seccomp degraded fallback, macOS/Windows, and behavior when bee runs inside an existing container are out of scope for the MVP (see the spec's assumptions).

License

MIT OR Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages