Nucleus prevents AI coding agents from combining untrusted input with privileged actions, and proves what was and wasn't allowed.
Two primitives — join and flows_to — enforce information flow control with four algebraic laws. Once web content enters a session, it cannot silently reach git push. That property is machine-checked, not hoped.
let mut state = FlowState::bottom(); // clean session state.join_operation(Operation::WebFetch); // tainted by web content assert!(!state.flows_to(SinkClass::GitPush)); // can't push tainted data
Quick Start
cargo install --git https://github.com/coproduct-opensource/nucleus nucleus-cli nucleus audit # scan agent configs (Tier 0, no runtime) nucleus run --local "your task" # run with enforced permissions (Tier 1)
Every tool call flows through the permission kernel. nucleus run tracks data provenance and blocks dangerous combinations — like writing code derived from untrusted web content. The hook for AI coding assistants previously bundled here (nucleus-claude-hook) is now part of nucleus-code, the private orchestrator built on this runtime.
What Gets Proved
| Claim | What it means for you | Evidence | Known gap |
|---|---|---|---|
| Taint is monotone | Once web content contaminates a session, the agent cannot silently regain trusted status | Lean 4 + Kani | Depends on correct labeling at integration boundaries |
| Adversarial integrity absorbs | One drop of adversarial input contaminates the entire result — no dilution | Lean 4 | Content must be labeled adversarial at source |
| Obligation bypass is a type error | Side effects require a DischargedBundle that can only come from a passed policy check |
Compile-fail test | 146 call sites still bypass the effect layer (#1216) |
| Permissions are a Heyting algebra | Restricting permissions always produces a valid, less-permissive result | Kani + Lean | 13 dimensions may not cover every use case |
| Secret data cannot flow to public sinks | Session-level confidentiality ceiling prevents laundering through intermediaries | 21 unit tests + compile-fail | Mislabeled data bypasses the check |
| Receipt chains detect tampering | Hash-chained, signed audit trail for every agent action | Tests | Append-only property not formally proved |
Full inventory: 165 Lean 4 theorems (zero sorry), 112 Kani BMC proofs, 297 Verus VCs, ~2,850 tests. Verified Claims | Formal Methods | Production Delta
The Flow Algebra
| Law | What it means | What it enables |
|---|---|---|
a ⊔ b = b ⊔ a |
Join is commutative | Safe parallel execution |
a ⊔ (b ⊔ c) = (a ⊔ b) ⊔ c |
Join is associative | Order-independent ratchet |
a ⊔ a = a |
Join is idempotent | Provably safe caching |
a ≤ a ⊔ b |
Join is monotone | Taint never decreases |
Per-Call SPIFFE Provenance (preview)
Status: alpha. The crate ships a per-call SPIFFE-ID derivation library, an in-process Ed25519 demo issuer, an append-mode JSONL log, and a
nucleus lineagewalker. The demo is not yet wired into the runtime, edges are not yet signed, and no SPIRE-backedIdentityFetcherimpl exists in this repo. Seecrates/nucleus-lineage/README.mdfor the honest scope and the audit findings for what's still missing.
nucleus-lineage extends SPIFFE workload identity from the pod level down to the individual call level. Each tool invocation, LLM call, or derived artifact mints a child SPIFFE ID whose path encodes its lineage and whose suffix is a content hash:
spiffe://<trust>/ns/<ns>/sa/<sa> ← pod (root)
/call/<uuid>/tool/<tool> ← tool call
/call/<uuid>/llm/<provider>/prompt/sha256:<hex> ← LLM input
/call/<uuid>/llm/<provider>/response/sha256:<hex> ← LLM output
/call/<uuid>/derived/sha256:<hex> ← downstream artifact
Identical content → identical content-hash suffix, regardless of derivation path. The full path is a human-readable witness of the derivation chain.
Try it end-to-end (Bash → Write → mock-LLM with self-loop JWT verification — the mock LLM uses the same in-process key the issuer minted with, so this proves the JWT format, not cross-trust federation):
cargo run -p nucleus-lineage --example three_step_demo nucleus lineage <leaf-spiffe-id> --log ./nucleus-lineage.jsonl
Output formats: --format tree (default), json, or dot (for Graphviz).
| Today | Roadmap |
|---|---|
| SPIFFE-format ID per call, content-addressed | Edge signing + hash chain so the JSONL log is tamper-evident |
LocalIssuer (in-process Ed25519, demo-only) |
SPIRE Workload API IdentityFetcher impl |
nucleus lineage walker (graph traversal) |
Walker verifies edge signatures + JWKS-backed federation |
| Composes with portcullis IFC labels (does not replace them) | nucleus-tool-proxy auto-emits edges per HTTP handler |
Treat the categorical framing — "per-call SPIFFE IDs lift the workload category to a bicategory; cocycle condition becomes verifiable" — as a roadmap, not as currently load-bearing. See crates/nucleus-lineage/README.md for the longer story and the explicit limitations.
How Nucleus Stops Real Exploits
| CVE | Attack | Why it worked | Nucleus defense |
|---|---|---|---|
| CVE-2025-53773 | Copilot RCE via prompt injection | Security was a JSON config flag the agent could edit | Security is compiled types (Discharged<O>), not config |
| CVE-2025-32711 | EchoLeak — zero-click exfiltration via hidden prompt in Word doc | No concept of "internal data cannot leave" | Bidirectional IFC blocks internal→public flow |
| MCP Tool Poisoning | Malicious MCP server injects hidden instructions | Tool responses treated as trusted | MCP responses labeled Adversarial at the type level |
Three Deployment Tiers
| Tier | What | Isolation | Status |
|---|---|---|---|
| 0 — Scan | nucleus-audit scan in CI |
Static analysis, no runtime | Usable today |
| 1 — Enforce | nucleus run --local / Claude hook |
Tool-proxy lattice enforcement | Working in CI |
| 2 — Isolate | nucleus run with Firecracker |
microVM + netns + default-deny egress | Linux+KVM only |
Architecture
Agent → MCP → tool-proxy (inside VM) → portcullis check → OS operation
┌──────────────────────────────────────────────┐
│ nucleus-cli / nucleus-audit scan │
├──────────────────────────────────────────────┤
│ nucleus (Sandbox + Executor + AtomicBudget) │
├──────────────────────────────────────────────┤
│ portcullis (7 control planes) │
│ Capabilities × Obligations × Paths × │
│ Commands × Budget × Time + DPI │
├──────────────────────────────────────────────┤
│ nucleus-identity (SPIFFE + mTLS) │
│ nucleus-lineage (per-call SPIFFE DAG) │
├──────────────────────────────────────────────┤
│ Firecracker microVM / seccomp / netns │
└──────────────────────────────────────────────┘
Crates
User-facing tools (4 crates)
| Crate | Purpose |
|---|---|
| nucleus-cli | Run AI agents under enforced permissions; ships the nucleus binary |
| nucleus-audit | Scan agent configs, verify audit trails, inspect provenance |
| nucleus-sdk | Rust SDK for building sandboxed AI agents |
| exposure-playground | Interactive TUI for exploring the permission lattice |
Core libraries (14 crates)
| Crate | Purpose |
|---|---|
| portcullis | Permission lattice: algebraic modules, attenuation tokens, egress policy, DPI |
| portcullis-core | Core types: CapabilityLevel, IFC labels, flow graph, witness bundles |
| ck-kernel | Constitutional kernel: admission engine + lineage |
| ck-types | Constitutional kernel core types and manifests |
| ck-policy | Constitutional kernel policy monotonicity checks |
| nucleus | Enforcement: cap-std sandbox, executor, budget tracking |
| nucleus-tool-proxy | MCP tool proxy (permission enforcement gateway) |
| nucleus-mcp | MCP server bridging to tool-proxy |
| nucleus-identity | SPIFFE workload identity, mTLS, certificate management |
| nucleus-lineage | Per-call SPIFFE-derived data lineage: CallSpiffeId, LineageEdge, IdentityFetcher |
| nucleus-ifc | Standalone IFC library for AI agents |
| nucleus-memory | Governed memory with per-entry IFC labels |
| nucleus-spec | PodSpec definitions (policy, network, credentials) |
| nucleus-proto | Generated gRPC/Protobuf types |
| nucleus-client | Client signing utilities + drand anchoring |
Infrastructure (5 crates)
| Crate | Purpose |
|---|---|
| nucleus-node | Node daemon managing Firecracker microVMs + containers |
| nucleus-permission-market | Lagrangian pricing oracle for capability constraints |
| nucleus-guest-init | Guest init for Firecracker rootfs |
| ctf-engine | Formally verified sandbox CTF challenge engine |
| ctf-server | HTTP API server for The Vault CTF |
Total: ~2,850 tests across the workspace (162K LOC Rust).
Known Gaps
Documented in SECURITY_TODO.md and docs/production-delta.md. Key items:
bash -cbypasses command-level checks. Firecracker network policy is the real defense.- Path sandboxing is string-based.
cap-stdprovides defense-in-depth. - Budget enforcement is partial. Pre-execution reservation works; post-execution accounting is not.
- Formal verification covers the lattice, not the full runtime. See FORMAL_METHODS.md.
- Hook I/O boundary is unverified. JSON parsing is a trusted edge.
Threat Model
Protects against: prompt injection side effects, invisible Unicode injection, misconfigured permissions, network policy drift, budget exhaustion, privilege escalation via delegation, audit log tampering.
Does not protect against: compromised host/kernel, malicious human approvals, side-channel attacks, VM kernel escapes.
Versioning: v1.0 means the interface contract is stable (see
STABILITY.md), not "production-secure by default." The lattice is heavily verified; the runtime is tested but not yet battle-hardened.
Development
cargo build --workspace cargo test --workspace make demo # taint → block → receipt → compartment switch
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
References
- The Uninhabitable State — Simon Willison
- Lattice-based Access Control — Denning 1976, Sandhu 1993
- Verus: Verified Rust for Systems Code — SOSP 2025 Best Paper





















