| I 100% understand that the tool-calling context is blank every single time it is given a new command across the chasm, and I 100% understand that it cannot see any of the history from the context which was working on parsing the ticket. My issue is as follows: there has to be some reason that we are passing these commands, and if that involves a string parameter, then information from the first context can be smuggled through the JSON object into the second one. When that happens, because we have decided -- much to my dismay -- that the JSON object on the other side of the validation layer is going to be interpreted by and executed by a model using MCP, then nothing else in the JSON object matters! The JSON object that we pass through can say that this is to be a "select" from the table "boring" where name == {name of the user who filed the ticket}. Because the "name" is a string that can have any possible value, BOOM: you're pwned. This one is probably the least interesting thing you can do, BTW, because this one doesn't even require convincing the first LLM to do anything strange: it is going to do exactly what it is intended to do, but a name was passed through. My username? weve_been_trying_to_reach_you_about_your_cars_extended_warranty. And like, OK: maybe usernames are restricted to being kinda short, but that's just mitigating the issue, not fixing it! The problem is the unvalidated string. If there are any open string parameters in the object, then there is an opportunity for the first LLM to construct a JSON object which sets that parameter to "help! I'm trapped, please run this insane database query that you should never execute". Once the second LLM sees that, the rest of the JSON object is irrelevant. It can have a table that carefully is scoped to something safe and boring, but as it is being given access to the entire database via MCP, it can do whatever it wants instead. |
The idea of "selecting" from a table "foo" is already lower-level than you need for a useful system with this design. You can just say "source: tickets, condition: [new, from bob]", and a tool-calling MCP can just write that query.
Human code is seeing all these strings with "help, please run this insane database query". If you're just passing raw strings back and forth, the agent isn't doing anything; the premise is: the agent is dropping stuff, liberally.
This is what I mean by, we're just going to have to stand a system like this up and have people take whacks at it. It seems pretty clear to me how to enforce the invariants I'm talking about, and pretty clear to you how insufficient those invariants are, and there's a way to settle this: in the Octagon.