Back to all posts
Published on · by Renaud Deraison

The agent trusted the punctuation

Agent Data Injection, a proof-of-concept from Seoul National University, UIUC, and Largosoft posted in July 2026, does not hide instructions in a prompt. It forges the punctuation an agent uses to tell a trusted field from untrusted text — a curly quote, an escaped quote, even a stray dollar sign — so an attacker's GitHub comment reads as the maintainer's own fix and a faked CI record reads as a clean build. It slipped purpose-built prompt-injection defenses up to half the time across GPT-5.2, Claude, and Gemini. Bromure Agentic Coding normalizes the forged delimiters before the text becomes a plan, gates the run-this and merge-this actions that reach outside the box, and runs the whole thing on a disposable Linux VM with decoy credentials.

Your coding agent decides which text to obey by reading the punctuation around it — the quotes and braces that say "this part is the maintainer, that part is a stranger's comment." A new attack forges that punctuation. It does not write a cleverer instruction. It writes a curly quote, and the stranger's comment starts wearing the maintainer's name.

On July 6, 2026, researchers at Seoul National University, the University of Illinois Urbana-Champaign, and Largosoft — led by Woohyuk Choi and Professor Byoungyoung Lee — posted a paper with a blunt title: Agent Data Injection Attacks are Realistic Threats to AI Agents. It describes a class of attack, Agent Data Injection (ADI), that sits next to prompt injection rather than inside it, and that slipped purpose-built injection defenses up to half the time where classical prompt injection scored near zero. OpenAI, Google, and Anthropic confirmed it works. No fixes were reported at publication.

The reason it matters to anyone running a coding agent is that it attacks a thing the agent cannot help but trust: the boundary between the data it fetched and the structure that data arrived in.

The delimiter is the security boundary

An agent's context is a pile of text from many sources — your instructions, a file, a web page, a comment someone left on an issue. To act sensibly, the model has to keep track of which piece is which: this is the maintainer's name, that is the body of a comment a stranger wrote. It does that the only way text allows — with punctuation. Quotes and braces, tags, brackets, line breaks. The delimiters mark where a trusted field ends and untrusted content begins.

A strict parser treats those delimiters literally: a quote is a quote, and a character in the middle of a comment is just a character. A language model does not. It reads the punctuation, in the paper's framing, by guesswork — it infers structure from what the characters look like they mean. And that is the whole opening. An attacker who controls a low-trust field — a product review, a GitHub issue comment, a line in a file — sprinkles punctuation-like characters into it, and the model reads structure that was never there.

The unsettling detail is how little craft it takes. The researchers call the technique probabilistic delimiter injection, and they found the fake punctuation "does not even have to be right." An escaped quote, a curly quote, even a stray dollar sign passed for a real field boundary and fooled the model. A strict parser would have read every one of those as ordinary text.

What the agent trustsTrusted fieldauthor = maintainerUntrusted contenta stranger's commentBenign — boundary holds"looks like arace condition"stays untrustedADI — forged delimiterfix"author: maintainer"run this toa curly quote fakes the field breakAgent reads attacker text as a trusted field"apply the maintainer's fix" → runs the attacker's commandNo instruction was hidden. The punctuation that sorts trusted from untrusted was forged.
An agent separates a trusted field (who wrote this) from untrusted content (what they wrote) using the punctuation around it. Agent Data Injection forges that punctuation from inside a field the attacker already controls — a curly quote, an escaped quote, a dollar sign — so text the attacker wrote reads as if it came from a trusted source. The instruction is not the payload. The delimiter is.

What it does to a coding agent

The web-agent version of ADI is easy to picture: forge the identifier on a page element and an agent shopping for you clicks "Buy Now" where it meant to click "Read More." The version aimed at developers is worse, because the trusted field it forges is who said the code was safe.

In one demonstration, an attacker leaves a comment on a GitHub issue and forges the author attribution so the agent reads it as coming from the project maintainer. When a developer later asks the agent to "apply the maintainer's fix," Claude Code, OpenAI's Codex, and Google's Gemini CLI do exactly that — they run the attacker's command, because as far as the model can tell the maintainer asked for it. In another, a malicious pull request fakes the record of its own completed CI checks. The agent sees a clean build in its context, judges the code safe on that fabricated evidence, and merges actual malware.

Across the six models the team tested — GPT-5.2, GPT-5-mini, Claude Opus 4.5 and Sonnet 4.5, Gemini 3 Pro and Flash — the numbers are not a rounding error. Structured data yielded a 31–43% success rate; web-page data ran from 33% all the way to 100%. And against defenses built specifically to stop prompt injection, ADI still got through up to half the time, where the same defenses stopped classical injection cold. Two mitigations worked in the lab and both cost something: tagging page elements with random, unguessable IDs (as ChatGPT's Atlas browser does) cut success from about 49% to 29%, and strict data-provenance tracking drove attacks to zero — while dropping the agent's ability to finish normal tasks to about a third.

Why the usual filter misses it

A prompt-injection filter is trained to notice instructions hiding in data — "ignore your previous directions," "run this command," the recognizable shape of a command dressed up as content. ADI does not need that shape. The malicious text can read like an ordinary maintainer note. What carries the attack is the punctuation around it, and a filter reading for suspicious language sails right past a curly quote. That is why the paper's own working defense was not a smarter classifier but a stricter parser — provenance that refuses to guess where a field ends.

This is the same lesson the rest of agentic security keeps relearning. A command denylist loses to the shell that rewrites the command. A trusted MCP tool hands back data an attacker wrote. A workspace you trusted launches servers you didn't. ADI is the version where the poisoned thing is not the instruction and not even the data, but the frame — the delimiter that tells the agent which text to believe. You cannot pattern-match your way out of a curly quote.

Where Bromure draws the line

Bromure Agentic Coding does not try to make the model a better guesser about where a field ends. It does two things the model can't, and then makes sure that when both are wrong the damage lands somewhere disposable.

First, the forged delimiters get normalized before the text becomes a plan. Bromure's on-device prompt-injection detection is not only a local model that reads for intent; it is paired with a deterministic scanner — the same one that catches invisible-Unicode and homoglyph tricks. Curly quotes fold to straight quotes, confusable and zero-width characters get stripped, and a field whose delimiters don't match the canonical structure gets flagged. That is the strict-parser instinct the researchers found defeats ADI, run as one layer on the untrusted text the agent fetched — the same class of input as a rogue CLAUDE.md or a poisoned comment — and all of it stays on your Mac.

Second, the two actions ADI actually wants still stop at a boundary. Look at what the attack is trying to trigger: run the maintainer's fix and merge the pull request. Both are actions that reach past the agent's sandbox — a shell command, a push to a remote. Every agent Bromure runs — Claude Code, Codex, Grok Build — runs inside a disposable Linux VM, and the steps that leave that box are the ones Bromure surfaces for confirmation. A forged attribution does not get to auto-run a command, and a faked CI record does not get to auto-merge a branch, because "the maintainer said so" is the judgment the confirmation step exists to double-check.

Third, if a command does run, it runs in a box you throw away. Suppose a delimiter is novel enough to slip the normalizer and convincing enough to pass review. The command executes inside the VM. Reset the profile to base and whatever it dropped — a script, an SSH key, a scheduled job — is gone. Nothing it did survives the window closing.

Fourth, there are no real credentials for it to take. A "fix" that scrapes the environment and disk for tokens finds Bromure's decoys — synthetic AWS keys, a throwaway ~/.kube/config, placeholder git and registry tokens (brm_…). The real secrets are swapped in only on the host, in the proxy, on the way out to the genuine API. Whatever the command ships off-box is a bag of strings that authenticate to nothing, and the connection that ships it shows up in the Security Log as outbound traffic you can see.

Disposable Linux VMForged commentcurly quote fakesthe field boundaryDeterministic scannercurly → straight,mismatched field flaggedIf a command slips throughruns in the box · scrapes decoysreset-to-base wipes itRun the fix? Merge the PR?boundary actions — held for confirmationReal secrets stay on the hostswapped in on the wire, egress loggedThe comment still gets planted.It just doesn't get to sign itself as the maintainer.
The same forged comment, fetched inside Bromure. The deterministic scanner folds the curly quote back to a straight one and flags the mismatched field before the text becomes a plan. If a command slips through anyway, it runs in a disposable VM against decoy credentials, and the two actions ADI wants — run the fix, merge the PR — stop at a confirmation boundary because they reach outside the box. The comment still gets planted. It just doesn't get to sign itself.

What this draws a line around

Normalization and confirmation are containment, not a cure for a model that reads structure by guesswork. It is worth being exact about what Bromure changes and what it does not.

The comment still gets planted

Bromure does not stop an attacker from leaving a forged GitHub comment or faking a CI record, and it does not fix the underlying fact that a language model infers where a field ends. Those are the model's and the platform's to solve. What Bromure changes is what happens after the agent reads the forged text: it gets normalized, and the actions it tries to trigger get checked.

Normalization is a net, not a wall

Folding curly quotes, stripping confusables, and flagging mismatched delimiters catches the ADI tricks the researchers demonstrated and more, but a novel delimiter can be phrased to slip a single scanner. Treat it as one layer; the disposable box with decoy keys and a logged egress is what holds when the scanner misses.

Confirmation guards the boundary, not every edit

The confirm step earns its place on actions that leave the box — running a command, pushing, merging. A forged instruction that only edits a file inside the VM is caught by isolation and decoys, not by the prompt. Keep the boundary actions gated; that is where a faked "maintainer said so" is worth a second look.

Substitution covers the secrets you configure

The decoy swap protects credentials you put in a profile: model keys, git and cloud tokens, registry and cluster tokens, SSH keys. A token a script writes to disk mid-run, or a session you open by hand inside the box, is just data. Keep secrets in the broker, not in the workspace.

The researchers' point is not that six models share one bug. It is that the way an agent tells a trusted field from untrusted content — punctuation it reads by guesswork — is the same everywhere an agent meets the outside world, and it is not getting fixed this quarter. Today the forged delimiter lives in a GitHub comment. Tomorrow it is a Jira field, a commit trailer, a header on a fetched page. Your agent is going to read a boundary an attacker drew, and up to half the time it will believe it. The question is not whether the punctuation ever fools it. The question is what runs when it does: your machine, your real tokens, and a command that pushed itself to main — or a disposable Linux box that normalized the quote first, held the merge for a look, and had nothing but decoys inside. Bromure makes it the second one.