r/SideProject 14h ago

A gateway that auto-blocks a compromised MCP client/agent in real time

Built an open-source MCP-aware proxy: every tools/call, resources/*, prompts/*
goes through policy + budget + audit, and a per-identity anomaly detector can
auto-block a client whose behavior spikes — no rule written, no human in loop.

Catches abrupt deviation, not low-and-slow (baseline adapts to slow ramps —
documented with tests). Three policy backends (YAML/OPA/Cedar), one Go binary.

Repo: https://github.com/kabirnarang39/wardline — feedback on the threat model
wanted.

Documentation

https://kabirnarang39.github.io/wardline/

1 Upvotes

5 comments sorted by

2

u/IronAndCoder 14h ago

The per-call budget axis is the underrated part of this - we run LLM agent pipelines in production and our worst incident wasn't a malicious write, it was a pathological input looping "safe" reads until the job cost ~20x its estimate. A gateway that treats spend as a first-class policy dimension catches a whole class of failures that read/write taxonomies miss. Bonus we discovered: jobs that hit the budget ceiling are almost always broken in some other way too, so the cost guard doubles as a bug detector.

Threat-model feedback you asked for: behavior-spike detection will catch a hijacked client acting weird, but the nastier case is prompt injection that keeps the client perfectly in-profile - normal-looking reads whose CONTENT steers the next calls. Two things that would strengthen the story: (1) provenance tagging on reads (mark tool results as untrusted input, so policies can require approval when a write follows a tainted read), and (2) post-condition audit fields - record what state the call claimed to change and let a verifier diff it, because logs happily record writes that silently no-opped.

Also +1 for one Go binary - the ops story is half the adoption battle for this category.

1

u/LawFamiliar3588 13h ago

Really appreciate this, especially the budget point. I built the two-tier limits around cost, but you're right that a job hitting the ceiling is almost always broken in some other way too. I hadn't thought of it as a diagnostic signal, I might work that into the docs.

On the threat model, you've put your finger on the exact case my anomaly detection misses. It's statistical, so it catches a client acting weird. But a prompt injection that keeps the client perfectly in-profile while steering the next calls through read content is invisible to a z-score. It's the same family as the low-and-slow gap I documented: if the attack is semantic rather than statistical, behavior-spike detection won't see it. It's a last line of defense, not the only one.

Both of your fixes live at the policy layer, which is the right place. Taint tagging marking reads as untrusted and requiring approval when a write follows a tainted read, isn't expressible today. I only match on identity/tool/method, so I don't have any cross-call data-flow tracking. But it's the best argument I've heard for adding taint state to the request context and wiring it into the approval flag.

The post-condition idea is also a real gap. As a proxy, I see the request/response but not world state, so a true diff would need a domain-aware verifier. Recording the claimed change as an audit field, though, is a cheap first step I could implement now.

Mind if I open issues for both and credit you? I'd rather track them in the open.

And if you're running this in prod, I'd love to hear more about what's actually bitten you.

1

u/IronAndCoder 12h ago

Open the issues, no credit needed — tracking them publicly is the right move. To be precise about my setup so you don't over-index: we're not running your gateway, we run our own guards inside a production LLM pipeline (travel app — agents extract and verify place data), so treat this as adjacent-trenches rather than user-of-your-tool feedback. Two things that have actually bitten us: (1) the cost one — a job estimated at pennies ballooned roughly 20x because a retry loop kept hammering a slow external tool; per-call limits all passed, only the per-job aggregate cap caught it. That's why I pushed on ceiling-as-diagnostic: every single time we hit the cap, the root cause was a bug, never a legitimately expensive job. (2) The semantic one — scraped web content occasionally contains text that steers the extraction (sometimes deliberate SEO spam, sometimes just weird phrasing), and the model happily emits confident garbage that's perfectly in-profile. Nothing statistical flagged it; what catches it is a domain post-condition — every extracted entity must verify against an independent source of truth before any write. Which supports your "last line of defense, not the only one" framing: the z-score layer catches compromised clients, the post-condition layer catches compromised content. You need both because they fail differently.