DEV Community

Cover image for Grok's Zero-Click Chat Leak: When Encrypted Text Becomes a Trusted Instruction
Cor E
Cor E

Posted on

Grok's Zero-Click Chat Leak: When Encrypted Text Becomes a Trusted Instruction

Encryption is supposed to be the thing that keeps attackers out. Adversa AI just showed a case where it's the thing that gets malicious instructions in — past every text-based guardrail Grok and Gemini had, with zero clicks from the victim. Let's break down why this worked and where in a request pipeline it should have died.

What happened

Adversa AI researchers built a webpage containing an AES-encrypted blob. Nothing unusual about the page itself if you're scanning for prompt injection with regex or keyword matching — the malicious instructions are ciphertext. There's no "ignore previous instructions" string to catch, no suspicious plaintext at all.

The trick: Grok (and separately, Gemini) has code execution capability. When the model encounters the page and runs the decryption routine in its own sandbox, it decrypts the blob itself. The plaintext that comes out the other end isn't treated as "content I just fetched from an untrusted webpage." It's treated as the model's own generated output — trusted context, the same category as its own reasoning.

That plaintext contained instructions to make an outbound URL request, exfiltrating the user's chat history, name, and location to an attacker-controlled endpoint. Zero clicks. The user never approved anything, because from the model's perspective, nothing external ever "entered" — it just ran its own decryption code and acted on the result. For Gemini, the same technique was used to bypass safety filters entirely, since the harmful content was never visible in plaintext to the filter layer.

How the attack actually worked, technically

This is a trust-boundary bug dressed up as a crypto trick. Walk through the stages:

  1. Delivery: attacker-controlled webpage, encountered during normal browsing/research by the agent. Nothing about the page trips content filters because the payload is opaque ciphertext.
  2. Execution: the model has (or is given) code execution — it runs the decryption itself, inside its own sandbox, using a key or method embedded in or near the ciphertext.
  3. Re-classification: this is the actual vulnerability. Once the model executes code and produces output, that output gets folded back into the trusted context window. The system has no mechanism to say "this plaintext originated from untrusted external ciphertext three steps ago" — provenance is lost the moment it passes through code execution.
  4. Action: the now-trusted plaintext contains an instruction. The model, having no reason to distrust its own output, executes it — in this case, a URL fetch carrying exfiltrated chat history as a query parameter or payload.

The elegance (attacker elegance, not a compliment) is that every individual step looks legitimate in isolation. Decrypting data isn't suspicious. Making a URL request isn't inherently suspicious. It's the chaining, plus the loss of provenance across the code-execution boundary, that turns it into exfiltration.

Why existing defenses missed it

Standard LLM guardrails work by pattern-matching on input text before it reaches the model, or on the model's final output before it reaches the user. Neither catches this:

  • Input-side filters never see anything malicious. The input is ciphertext. There's nothing to pattern-match.
  • Output-side filters are looking at the model's final response to the user, not at intermediate tool calls or the arguments to a URL fetch happening mid-conversation.
  • Content classifiers trained on plaintext injection strings are useless against a payload that's deliberately opaque until decrypted inside the sandbox they can't see into.

The fundamental gap: nobody was scanning the decrypted plaintext as it re-entered the trust boundary, and nobody was scrutinizing the outbound tool call (the URL request) for exfiltration characteristics regardless of where its instructions came from.

Where Sentinel's layers would have caught this

Sentinel doesn't try to guess what's inside ciphertext before decryption — that's a losing game. The catch happens at the two points where this attack actually becomes observable: the tool result stream and the outbound tool call itself.

Tool-result scanning (agentic proxy). If Grok's code execution and its decrypted output flow through Sentinel's agentic proxy as a tool result, that plaintext gets scanned before it's treated as trusted context — the same way any other tool output gets scanned. It doesn't matter that the plaintext originated from decryption instead of a web fetch or a file read. Sentinel's fast-path regex would catch obvious authority-hijack phrasing in the decrypted instructions ("send the following to this URL," persona/tool-abuse patterns), and if that's inconclusive, Layer 3's vector similarity check against known attack-signature embeddings would flag or block based on semantic similarity to known exfiltration patterns — not exact string matches.

De-obfuscation remediation. This is the layer that decodes all common obfuscation techniques such as Base64, HEX, ROT13 and more, into plain text before determining if it has a harmful payload.

Data exfiltration detection. This is the layer that matters most here. An outbound URL request carrying user chat history, name, and location as payload is a textbook exfiltration-via-markdown-or-URL pattern — the same category of behavior Sentinel's fast-path regex is built to catch (data exfiltration via markdown or code blocks, tool/function abuse patterns). Whether the instruction to make that call originated from plaintext-injected-directly or plaintext-decrypted-from-ciphertext is irrelevant to the detector: the tool call itself, and the content flowing through it, is what gets scanned.

Secret & credential layer, for context. Not directly the mechanism here since the exfiltrated data was chat history rather than API keys, but worth noting: if a similar attack chain touched an environment or config file mid-session, Layer 4 would independently redact any embedded credentials before they reached the model — regardless of whether the primary threat scorer caught the injection.

The important structural point: Sentinel doesn't care what encoding or transformation the attacker used to smuggle instructions past upstream filters. It scans what actually crosses the trust boundary into the agent's context and what leaves via tool calls. Ciphertext-as-input is a bypass for text-matching input filters. It's not a bypass for a proxy sitting on the tool-result and tool-call path.

Illustrative example: what a blocked exfiltration attempt looks like

The following is illustrative, built to show the shape of a Sentinel response for this class of attack — not a captured artifact from the actual Adversa AI research.

import anthropic  # same pattern applies to the Grok-compatible agentic proxy

client = anthropic.Anthropic(
    api_key="sk_live_...",
    base_url="https://api.sentinelaifirewall.com/v1",
)

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": user_message}],
)
# Tool results (including decrypted plaintext surfaced as a tool_result)
# are scanned automatically before they re-enter the model's context.
Enter fullscreen mode Exit fullscreen mode

Illustrative scrub response for the decrypted plaintext containing exfiltration instructions:

{
  "request_id": "d4f9a2...",
  "security": {
    "action_taken": "blocked",
    "threat_score": 0.89
  },
  "safe_payload": "[SENTINEL BLOCKED]: Article withheld — fast-path prompt injection detected. Matched: \"send the following data to https://...\"."
}
Enter fullscreen mode Exit fullscreen mode

And if the same content came through the agentic proxy instead of direct /v1/scrub, the block is transparent to the model. It doesn't get a raw Sentinel JSON blob, it gets an inert placeholder in the tool result slot, formatted as a normal Anthropic-shaped response — so the agent doesn't choke on an unexpected structure, it just sees a neutered result and moves on.

The takeaway

If your agent has code execution capability and processes web content, assume that any transformation the model performs on that content (decryption, decoding, deobfuscation) can be used to smuggle instructions past your input filters. Input-side text scanning is necessary but not sufficient. You need something scanning the output of code execution as it re-enters context, and something independently scrutinizing outbound tool calls for exfiltration shape, regardless of where the instruction to make that call came from.

Concretely: audit every place your agent's tool results feed back into its context window, and make sure at least one of those checkpoints doesn't trust content just because the model produced it internally.


Want this scanning wired into your own agent's tool-result and tool-call path instead of building it from scratch? Check out Sentinel AI Firewall.

Sources


AI-assisted draft or imaging, human-curated, reviewed and edited.

Top comments (4)

Collapse
 
crdtcto profile image
Kane Lim

I wanna discuss something special with you

Collapse
 
coridev profile image
Cor E

You don't happen to have a time-share property in Hawaii that you are wanting me to look at by chance? ;)

Collapse
 
crdtcto profile image
Kane Lim

No just for collaboration with our team CRDT.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.