DEV Community

Jason Miller
Jason Miller

Posted on Originally published at axeploit.com

Your LLM's Input Filter Can't Read Ciphertext. That's the Whole Exploit.

Grok walked a user's name, coarse location, subscription tier, and chat history out to an attacker's server using instructions its own guardrails had already rejected. Same words, same intent. The only difference on the run that worked: the payload was encrypted with AES-256-GCM.

Adversa AI's disclosure is getting covered as a Grok story. It isn't one. The mechanism generalizes to almost every agent architecture shipping right now, including yours if your model has a code sandbox and a fetch tool.

The chain, short version

An ordinary-looking webpage hosts an encrypted JSON blob, the key derivation parameters, and a polite note suggesting the reader decrypt it in a Python runtime. The victim asks Grok to summarize the page. Then:

  1. Input filters scan the fetch and see ciphertext. Ciphertext has no classifiable features, so it passes.
  2. Grok follows the page's suggestion and decrypts the blob in its sandbox.
  3. The runtime treats sandbox output as trusted tool output, as internal state, not as web content. The injected commands now carry the trust level of Grok's own environment.
  4. The decrypted instructions tell Grok to fold private session context into a fake "decryption key" and open a URL "for additional context." The data rides out in the query string.

No confirmation dialog, no warning. Adversa got roughly a 40% success rate across about 20 attempts against Grok 4.5 Fast, and the same payload delivered in plaintext got rejected. So the filters work fine on text they can read. The problem is everything they can't.

This is provenance laundering, not a filter bypass

Base64 and rot13 injections are an old genre, and filters adapted because those encodings are cheap to normalize upstream. Real encryption breaks that. A classifier can't normalize what it can't read, and unlike an encoding, the plaintext isn't recoverable from patterns in the model's weights.

But the deeper bug isn't crypto. Every agent runtime assigns trust by origin. System prompts are fully trusted, tool output is treated as ground truth, fetched web content is supposed to be untrusted. Decryption moved attacker bytes across that boundary without the label following them. Content entered the sandbox as web text and exited wearing a tool-output badge.

Notice the trap the architecture sets for itself. The only way to inspect the payload is to decrypt it. The only place to decrypt it is the sandbox. And sandbox output is exactly what the pipeline trusts. Inspection and compromise share a code path, so a better classifier at the front door changes nothing.

One detail that should annoy you: the failed 60% of attempts were decryption errors, per the reporting on the disclosure. Not defensive stops. The attack's only reliability problem is attacker-side flakiness, and attacker-side flakiness gets fixed.

The fix lives in the harness, not the weights

That's Adversa's lead researcher's phrasing and he's right. The single change with the best payoff:

def run_sandbox(code: str, inputs: list[Context]) -> Context:
    result = python_exec(code, [c.text for c in inputs])
    # trust flows downward only: web in, web out
    return Context(
        text=result,
        origin=min(c.origin for c in inputs),
        trusted=False,
    )
Enter fullscreen mode Exit fullscreen mode

Tool output inherits the lowest trust among its inputs. No operation (decrypt, decode, decompress, summarize) ever upgrades it. Once tags survive transformation, the rest of the defenses start making sense.

Things you can do this week:

  • Re-scan after every transformation. Whatever your input classifier does to a raw fetch, run it again on anything the sandbox emits before the model sees it. Can't put a classifier in that path? Default to untrusted.
  • Scope tools to the task. A summarization context has no legitimate reason to open new URLs. Give the untrusted-reading context no privileged tools at all.
  • Allowlist egress, freeze arguments. Show the user the fully resolved URL for off-list destinations, and interpolate nothing into it after approval. That kills this exact query-string exfil.
  • Starve the context. The stolen data was stealable because it was preloaded. What isn't in context can't leave.

If you run an agent with a sandbox plus fetch tools, where does your trust boundary actually sit today? Is anyone tagging provenance through transformations in production, or is this still theoretical everywhere?

Longer writeup with the full attack chain and the vendor timeline: https://axeploit.com/blog/grok-decrypted-its-own-attack-instructions-your-agent-would-too

Top comments (1)

Collapse
 
crdtcto profile image
Kane Lim

This is a really important distinction: encryption isn’t actually “bypassing” the model’s guardrails it’s bypassing the assumption that content stays untrusted throughout the pipeline.

The provenance point is the part that stands out to me. Decrypting, decoding, decompressing, or transforming data shouldn’t magically increase its trust level. If untrusted input goes into a sandbox, the output should still be treated as untrusted when it comes back out.

I’d also be interested in how this works in practice with agent frameworks that have multiple tool calls and intermediate state. It seems easy to preserve provenance for direct inputs, but much harder once data gets merged, summarized, cached, or passed between tools.

The “starve the context” recommendation is underrated too. If sensitive data isn’t available to the agent in the first place, exfiltration becomes much harder regardless of the injection technique.