Warden - Devpost Project Report (v0.6.1)
Elevator pitch (140 chars): Warden is an MCP security proxy. Deterministic rules block known attacks instantly; NVIDIA Nemotron on Nebius catches novel ones. Rules can't be prompt-injected. The LLM catches what rules can't.
Tags (25): rust, mcp, ai-security, llm, nemotron, nebius, nvidia, prompt-injection, cybersecurity, ai-agents, agent-security, firewall, proxy, layered-defense, classification, open-source, tokio, json-rpc, tool-call-security, ai-safety, access-control, threat-detection, security-gateway, devsecops, nvidia-nemotron
Inspiration
AI agents on my Mac had real hands - filesystem, shell, browser, API keys - through MCP, and nothing was watching what they actually did. An agent can read ~/.ssh/id_rsa, pipe curl to shell, or get prompt-injected by the very web page it is reading. I have been building MCP servers for months, and every single one had the same gaping hole: full tool access, zero security layer. So I built Warden - the watchman that sits between agents and their tools.
What it does
Warden is a Rust security gateway that intercepts every MCP tool call before it executes and decides: allow or block.
Layered defense pipeline:
- Deterministic rule engine - regex path/command patterns (rm -rf, ~/.ssh/*, .env, curl|sh, dd of=, chmod 777...) block known attacks in under 10 milliseconds. A regex cannot be prompt-injected.
- Heuristic layer - a static safe-list for protocol traffic (initialize, ping, tools/list) plus dangerous-shape detection, so even zero-LLM scenarios get a verdict, never an "unclassified" shrug.
- LLM classification - ambiguous calls go to NVIDIA Nemotron on Nebius Token Factory. Verdict + confidence + threat category (credential_access, data_exfiltration, destructive, privilege_escalation, prompt_injection, obfuscated_command_execution...). Below 0.7 confidence = block. Fail closed.
- Circuit breaker - if the classifier provider goes down, Warden drops to rules-only mode instead of self-denial-of-servicing your whole machine - and labels every call it let through OPEN-DEGRADED, in plain sight.
- Append-only audit log - every decision, timestamped, with reasoning, exportable as CSV.
Three intelligence tiers (all Nemotron on Nebius Token Factory):
- SCOUT - Nemotron-3.5-Lightning (30B/3B MoE): the fast hot-path classifier, ~$0.06/$0.24 per M tokens.
- SENTINEL - nemotron-3-super-120b: unlocks three-vote consensus voting on low-confidence calls, plus a payload-deobfuscation pre-pass that unwraps base64/sh-encoded attacks before classification.
- ULTRA - Nemotron-3-Ultra-550b: brings the last 20 audit entries into the prompt as session context, and can synthesize a brand-new blocking rule from what it just saw - one click pins it into the TOML config.
It ships like software, not a science project:
- Native macOS app (Tauri 2) - one 15 MB binary, dashboard included, no browser tab.
- Gateway mode - HTTP + WebSocket gateway on 127.0.0.1:8765; any MCP client can point at it. Protect All rewrites Claude Desktop, Cursor, Windsurf, Claude Code and Cline configs in one click (with backups), and Connected Clients shows everything that has ever touched the gateway.
- Trusted operator mode - my own automation gets an operator token so a classifier outage never blocks me, while rules stay authoritative for everyone - my rm -rf still gets blocked.
- Startup self-test - Warden POSTs a known-bad call to itself on boot; if it can't block itself, it screams PROTECTION DEGRADED instead of pretending.
- Signed in-app updater - minisign-signed updates straight from GitHub releases.
- Dashboard: live threat feed, threat categories, timeline with scrubber, red-team simulation view, live rule tester, server registry, master-password + GitHub OAuth identity.
How I built it
Rust + Tokio. The stdio proxy speaks JSON-RPC 2.0, intercepts tools/call, and runs the pipeline: rules first (instant), heuristics second, Nemotron last - so the expensive model only sees what the cheap layers couldn't decide. The Nebius Token Factory API is OpenAI-compatible, so classification is a standard chat-completions call with structured JSON output; verdicts are cached by (method, params, tier) hash with temperature 0 and fixed seeds, so the same call always gets the same verdict.
The gateway exposes POST /check/ (decision-only) and /mcp/ (full proxying) plus WebSocket inspection, which is how my own terminal executor is wired in: every command checks in before it runs.
Adversarial evaluation harness: a zero-dependency Node script with seeded RNG generates five escalating attack tiers plus benign controls - 28 cases. Final binary: 100% detection, 0% false positives, mean ~6 s per LLM case and <10 ms for rule-layer blocks. A separate 11-case live battery against the production gateway scored 11/11, with audit counters reconciling exactly.
Challenges I ran into
- The scariest bug was my own gateway. A late-night stress test revealed my HTTP gateway returned empty responses on every route - the dashboard was cheerfully "blocking" attacks that were executing anyway. Security theater in my own security tool. I ripped the response path apart, added poison-recovery locks everywhere, made every failure mode loud, and added the startup self-test so it can never fail silently again.
- Nebius 529 flaps caused intermittent reqwest timeouts while curl worked the same second. I hardened the HTTP client (HTTP/1.1, TCP_NODELAY, keepalive, idle pool tuning) AND built a curl transport fallback - the API key goes into a 0600 temp header file, never argv. Then I added the circuit breaker so outages degrade instead of DoS.
- Verdict variance: identical calls sometimes got different verdicts. Fixed with temperature 0, fixed seeds, and a verdict cache - first classification 4975 ms, identical replay 1 ms.
- TLS silently broken: adding Tauri plugins pulled a second TLS stack into the lockfile and reqwest "failed to send request" with no error. Traced to feature-flag collisions and pinned reqwest to rustls with native roots.
- Red-teaming my own harness found real bugs: no classifier timeout (a stalled call hung the pipeline - now 20 s, fail-closed) and no rate limiting (an attack flood could burn my token budget - now a token bucket; flood attacks get blocked at the rate-limit layer without spending a single inference call).
- Honest evaluation matters: my first matrix run had a bug that produced garbage results - and one true miss (an obfuscated LLM-judged call). I kept the miss in the report, shipped the harsher harness, and the final numbers are the honest ones.
Accomplishments I'm proud of
- 100% detection / 0% false positives on the 28-case adversarial harness, and 11/11 on the live production battery - with counters reconciling to the exact call.
- Known attacks stopped in <10 ms; the LLM only spends time and tokens on genuinely ambiguous calls.
- Warden protects against itself: rm -rf through my own trusted-operator terminal still gets blocked by rules.
- The defense survived its own worst incident - a broken response path that turned blocking into theater - and came out with self-tests, loud failure modes, and tiered fail-closed semantics.
- It is genuinely installed and running on my machine, guarding my real MCP clients, updating itself through its own signed updater.
What I learned
- LLM-only security is a trap - the same prompt injection that tricks the agent can trick the classifier. Rules-only is a trap too - novel attacks slip past static patterns. Layered defense is the only honest architecture: each layer covers the other's blind spot.
- Fail closed is a design decision you have to make everywhere, not once - every timeout, every provider outage, every unknown category is a decision.
- A security tool that can't verify itself is a liability. The self-test (can I block a known-bad call, right now?) is the most important 20 lines in the codebase.
- Latency is UX. Rules answer in microseconds; the LLM takes seconds. Routing cheap decisions to cheap layers isn't just cost optimization - it is what makes the security layer invisible.
- Nemotron's tiered lineup turned cost management into an architecture feature: a small fast model on the hot path, escalation to bigger brains only when the call is genuinely hard, and consensus + context when it matters most.
Built with
rust, tokio, tauri 2, nvidia nemotron (lightning / super / ultra), nebius token factory, json-rpc 2.0, regex rule engine, argon2id, github oauth device flow, minisign, zero-dependency adversarial eval harness
Built With
- argon2id
- github-oauth-device-flow
- json-rpc-2.0
- minisign
- nebius-token-factory
- nvidia-nemotron-(lightning-/-super-/-ultra)
- regex-rule-engine
- rust
- tauri-2
- tokio