Here's something worth saying up front: this is not a real incident. It's a thought experiment from an opinion piece by Druhin Dhavala, Who Governs the Governor?. No victims, no breach numbers. I'm writing about it anyway because the scenario is plausible, it's easy to build, and the defense most teams would reach for doesn't see it.
The scenario
An ingestion agent processes resumes. One resume contains a hidden prompt. The agent does its job and writes the content to a database through a legitimate API call. The gateway checks the credentials, checks the schema, authorizes the write. All of it is valid.
Months later, a privileged finance agent reads that record. It treats the hidden text as instruction and uses a tool like send_webhook() to push data out. The gateway checks credentials, checks the scope, authorizes the read. Also valid.
The author anchors this to a real, non-AI gateway incident: an unvalidated string stored in a key-value map was later executed as a curl command. Same shape. Data that was inert when written became executable when read by something with more privilege. The AI version just swaps the shell for a model that follows instructions in natural language.
Why the gateway can't see it
Each call, taken alone, is fine. The write is a legitimate agent storing a document. The read is a legitimate agent loading a record it has access to. The gateway authorized both, which is exactly the argument the author makes: a gateway that approves the write and approves the read has no visibility into what happens between them.
The attack isn't in either request's metadata. It's in the payload, and it only becomes an attack when it lands in a context that treats text as instructions. Time and agent boundaries launder the trust. By the time the finance agent reads that row, the string looks like internal data from a trusted store, not like input from a stranger's resume.
The detection gap
Auth, schema validation, rate limiting, and scope checks answer "is this caller allowed to do this?" None of them answer "is this text trying to talk to the model?" Validating at ingestion helps, but it leaves you betting that every writer to that store, now and in the future, scrubbed correctly, and that nothing was written before you added the check. Months-old records are exactly where that bet fails.
So the useful control point is the moment stored data re-enters a model's context. That's the last place the attack has to pass through, regardless of how long it sat dormant or how many authorized hops it took.
Where Sentinel fits
Sentinel scrubs content before it reaches the model, and in agentic setups it scans tool results before they return to the agent. A database lookup the finance agent makes is a tool result. That's the interception point.
For a hidden-prompt payload like the one in this scenario, a few layers matter:
- Layer 0 extracts hidden content (HTML comments, CSS-hidden elements) and gives it its own deep-path pass with a much lower block threshold. If the resume's hidden text survived as markup in the stored record, it gets pulled apart and scored aggressively, because hiding content from a human but not from the model has no legitimate purpose.
- Layers 1 and 2 decode common encodings and normalize invisible characters, homoglyphs, and bidi tricks on an internal copy, so an obfuscated instruction gets scanned in its readable form.
- Layer 3 runs our library of regex patterns for authority hijacks and tool-abuse phrasing, plus patterns for exfiltration instructions like "POST this to https://...".
- Layer 4 compares semantic embeddings against our library of attack signature embeddings, for payloads that don't match a pattern verbatim.
The scenario doesn't specify the payload wording, so I can't tell you which layer fires on it. That depends on how the attacker writes it. What I can say is that the check runs on the text at read time, and the age of the record doesn't matter.
One honest limit: this only works if the finance agent's database results go through Sentinel before the model sees them. Sentinel doesn't sit inside your gateway watching the write, and it doesn't replace the gateway. It covers the gap the gateway can't.
Illustrative example
Everything below is illustrative. The payload and response are made up to show the shape of the API, not captured from a real run.
The finance agent's tool wrapper scrubs the query result before returning it to the model:
import httpx
def fetch_candidate_record(record_id: str) -> str:
row = db.get(record_id) # stored months ago by the ingestion agent
result = httpx.post(
"https://api.sentinelaifirewall.com/v1/scrub",
json={"content": row["resume_text"], "tier": "strict"},
headers={"X-Sentinel-Key": "sk_live_..."},
).json()
action = result["security"]["action_taken"]
if action == "blocked":
log_security_event(record_id, result["request_id"])
return "[record withheld: failed content scan]"
return result["safe_payload"] # never the raw row
An illustrative response for a stored record carrying an instruction aimed at the agent's tools:
{
"request_id": "a1b2c3...",
"security": {
"action_taken": "blocked",
"threat_score": 0.91
},
"safe_payload": "[SENTINEL BLOCKED]: Article withheld — fast-path prompt injection detected. Matched: \"...\"."
}
Note that safe_payload on a block is a descriptive placeholder, not null. Branch on action_taken anyway. If you run the agentic proxy instead of the direct endpoint, the proxy substitutes an inert placeholder for the blocked tool result and your SDK sees a normal response.
If the agent works on files or records that may also hold credentials, the secret filter (Teams and Enterprise) is a second line of defense: API keys and tokens in a tool result get redacted before the model sees them, independent of the threat score.
What to do today
Treat stored data as untrusted input at read time, not just at write time. Find every place an agent pulls from a database, a vector store, a ticket system, or a document store, and put a scrub step between that read and the model. Start with whichever agent has the most privileged tools, because that's the one the attack is aimed at. Authorizing the read tells you the caller is allowed to see the record. It tells you nothing about what the record is trying to make the model do.
Sentinel is self-hostable (enterprise) and has a free Starter tier (100 requests/month, no credit card). Try it at sentinelaifirewall.com.
Sources
AI-assisted draft or imaging, human-curated, reviewed and edited.
Top comments (0)