Get the latest, first
arrowBlog
Prompt Injection in RAG: The Payload Is Still in Your Index

Prompt Injection in RAG: The Payload Is Still in Your Index

Sep 6, 2026

Yossi Ben Naim
VP of Product Management

Key takeaways

  • What makes prompt injection in RAG different from every other injection surface? A poisoned chunk is a stored record. A typed instruction is consumed once by one session; an indexed instruction is retrieved by every query that lands near it, by any agent sharing the index, for as long as it stays there. One write produces an unbounded number of executions.
  • Why does a standard containment runbook fail against it? Every action in the runbook operates on the agent: restart the pod, rotate the credential, revoke the session, disable the tool. The payload is in the index, so each action stops one workload from repeating one behavior and leaves the chunk live for the next matching query.
  • How do you scope an incident when the payload is still serving? Retrieval logs at chunk resolution rarely exist, so the blast radius is rebuilt from the other side: what each agent did after retrieval. Per-agent runtime records show which sessions deviated and when, and those records bound the incident window until the chunk is evicted from the index and every derived store.

Every action in your agent-incident runbook operates on the agent. The payload of a RAG prompt injection sits in the index. You can kill the pod, rotate the credential and revoke the session, and each of those stops this workload from doing that thing again. None of them touch the chunk that caused it.

The chunk is a stored record. It fires on the next matching query, against whichever agent retrieves it, for as long as it stays indexed, and the attacker chose which queries match when they wrote it. The mitigation lists you have read treat a retrieved instruction as a request to block. This piece treats it as a record to find and remove: why a poisoned chunk is a stored record, how the attacker picks who receives it, why your containment runbook does not reach the index, how blast radius is rebuilt from what the agents did, why eviction is a three-system operation, and why a retrieved instruction is an incident with a date range.

A Poisoned Chunk Is a Stored Record

A poisoned chunk sits in the index the way any other chunk does: embedded, ranked and waiting for a query that scores it into the top-k. It has no session attached. It has no user attached. It was written once, by a connector or an editor or a pull request, and from that moment it is content the retriever is allowed to return.

Compare that with the instruction an attacker types into a chat window. That text lives for one request. The model reads it, acts or does not act, and the context is discarded. The incident has a session ID, a timestamp and a user, and closing the session closes the exposure.

A chunk has none of those properties. It is retrieved when a query matches, and it is retrieved again when the next one does, and the two retrievals may be weeks apart and belong to different people. Its dwell time is bounded by nothing except the next corpus rebuild, and a rebuild only helps if it starts from a cleaned source: an incremental sync leaves the chunk in place, and a full re-embed of an uncleaned source writes it back under a new ID.

The index is also rarely private to one agent. A support agent, a sales assistant and an internal search bot often share one knowledge base, with retrieval scoped by tenant or namespace at best. A chunk that one agent retrieved on Tuesday is available on Wednesday to every other agent that reads the same namespace. The agent you are looking at is the one that happened to ask first.

How the text got into the index is the indirect injection story, and it decides who can attack you. What the text does once it is there is decided by the properties above, and the index-time window between the write and the first retrieval is where those properties are set.

The Attacker Picks Who Receives It

Retrieval is similarity search, which makes it addressable. The attacker writes text whose embedding lands in a chosen region of the vector space, and every query that lands in the same region pulls that text into context. Scoring well against those queries is the whole requirement. Being a good answer to them is not.

The PoisonedRAG researchers showed how precise that addressing can be: the attacker picks target questions in advance, crafts each injected text so the retriever returns it for one of those questions, and five such texts per question were enough in a corpus of millions. Their payload was a false answer; an instruction rides the same retrieval condition. Read as a targeting result, that is a delivery list. The attacker is choosing a question class, and every user who asks a question in that class is on the list.

So the victim population of a RAG injection is a query class. Anyone who asked about return-policy edge cases, or about which service account a deployment uses, or about the SSO configuration for a customer, received the payload. Anyone who asked about something else did not.

A query class is also the unit of disclosure. A direct injection has one victim, the person whose session it ran in. A RAG injection has as many victims as the query class produced over the chunk’s dwell time, and the attacker knew the shape of that class before you did. So scoping starts from the class, and the next two sections cover what to do when you cannot see who was in it.

Your Containment Runbook Does Not Reach the Index

The runbook’s containment actions all descend from the same model of an incident: something is wrong with a workload, so change the workload. That model has held for every incident before this one, and it is why the runbook is written the way it is. Each action below is correct against a compromised agent. Each one maps to a stage of the attack chain that runs after retrieval, and the payload is present at the stage before all of them.

Containment actionWhat it stopsDoes the chunk still fire?
Kill or restart the podThis process, this context windowYes, on the next retrieval by any pod
Rotate the credentialThis key from being used againYes, with the new key
Revoke the sessionThis user’s conversationYes, for the next user in the query class
Roll back the modelA model-side behavior changeYes, the chunk is model-independent
Tighten egressExfiltration to that destinationYes, with a different action on the next retrieval
Disable the toolThis tool from being calledYes, and the payload can ask for a different tool
Quarantine the chunk with a retrieval filterRetrieval through that retrieverYes, for any agent that reads the index through a different path
Evict the chunkThe payload from being retrievedNo, if derived stores are cleared too

Read the third column top to bottom. Seven correct actions, seven live chunks. The quarantine row is the interim step, because a metadata filter or an exclusion list in the retriever is often within the application team’s authority when the index is not, and it holds only for agents that retrieve through that filter. The eighth action is the only one that changes the answer, and it is the only one the responder usually cannot take.

The index belongs to a platform team or a knowledge-base owner. The connector that populated it belongs to whoever runs the data pipeline. A security engineer holding the incident ticket can restart every pod in the namespace and cannot delete a document from a vector database they have no write access to, and if they could, the connector would put it back on the next sync.

So the runbook’s last step for a RAG injection is a handover. What crosses to the index owner is a chunk reference (the document ID, or the chunk ID if the store exposes one), the source URI the connector ingested it from, the time it was indexed, and the query class it was tuned for. The first three identify the record. The fourth is what makes the eviction verifiable, and it is reconstructed the hard way, which is the next section.

Blast Radius Is Rebuilt From What the Agents Did

Three questions bound the incident: which queries hit the chunk, which sessions acted on it, and since when. All three would be answered by retrieval history at chunk resolution, and most stacks do not keep it. A vector database that logs queries rarely logs which chunks came back. An agent framework that logs the assembled prompt rarely tags the chunks inside it. What survives is the other end of the incident: what each agent did after it read the chunk.

Which queries hit it is the question the attacker already answered. The chunk was written for a query class, and the class is recovered by working the retriever backward: generate the questions the chunk’s text reads as an answer to, run each against the retriever, and keep the ones that rank the chunk in their top-k. Where the application kept its own query log, in traces or request logs, replay that log instead. Either way the result is a set of questions with a shape, and the shape is enough to search the action records for.

Which sessions acted on it is answered from the action side. An instruction that asks an agent to verify eligibility against a customer table leaves no trace in the text plane, because the sentence is ordinary. It leaves a trace in the agent’s behavior: a table read this agent has never performed, a tool called in an order it has never used, a connection to a destination outside its history. ARMO records what each workload does in execution with a kernel-level eBPF sensor and builds a per-agent behavioral profile, Application Profile DNA (APD™), then evaluates each new action against that agent’s own record. The deviations are the sessions. A database read is routine for a reporting agent and unprecedented for a ticket summarizer, and a fleet-wide rule cannot express that difference at all.

Since when is the earliest deviating action across every agent that shares the index. The chunk was indexed no later than that timestamp, and the incident has been running at least since it, so the timestamp is the end of the window the index owner searches connector logs in, and the indexed-at time found there is the start.

The case this reconstruction exists for is coercion: a trusted prompt redirecting authorized capability without breaking a single control. The agent had the service account. The table was in scope. The tool was registered. Every step was permitted, and the sequence is the attack, which is why the sequence is the only evidence. The instruction borrowed the agent’s authority, and borrowed authority is measured on the action side, as tool misuse carried out with a valid identity.

Everything in this section is retrospective. It tells you how far a chunk already reached. What to instrument so that the retrieval side is visible next time is a posture question, and it belongs to the index-time window discussed above.

Eviction Is a Three-System Operation

A poisoned chunk lives in three systems, and deleting it from one of them leaves it in the other two: the upstream source it was ingested from, the index that serves it, and the derived state that was built from it after retrieval.

The upstream source is the wiki page, the repository, the ticket store or the public site the connector reads. Delete the chunk from the index and leave the source untouched, and the next scheduled sync re-embeds the same text under a new chunk ID. The eviction has to start at the source, or at the connector’s allow-list, and the runtime-derived inventory of which connectors feed which index is what tells you where the source is.

The index is the easy part, once someone with write access is in the loop. Delete the record, or mark it excluded, and confirm the deletion propagated to every replica, and to every namespace or collection the connector wrote the same source into, because one source often lands in several.

The derived state is where evictions usually stop short. A chunk that was retrieved and acted on has been copied into places the index owner does not manage: a response cache keyed on the query, a summary the agent wrote and stored, an agent memory store that persists across sessions, a downstream index built by re-embedding agent outputs. Each of those is a second copy of the payload with its own dwell time, and each one has to be found through the action records from the previous section, because nothing else links them to the original chunk.

Three systems means three owners. Security holds the incident and the evidence. Platform holds the index and the connectors. The knowledge-base team holds the source and decides whether the document is corrected or removed. Name in the runbook which of those three decides that the chunk goes. An incident whose fix needs three approvals and has no decider is still open at the next sync, and the next sync is when the connector puts the chunk back.

Then verify. Replay the query class reconstructed earlier against the index and confirm the chunk, and any chunk derived from the same source, is absent from the top-k for every query in it. Repeat the replay against each derived store the action records pointed to. An eviction is finished when the attacker’s own queries come back clean everywhere the payload was copied, and not before.

A Retrieved Instruction Is an Incident With a Date Range

The incident opened when the chunk was indexed, and it closes when the chunk is gone from the source, the index and every derived store, with the replay to confirm it. Between those two dates, every containment action taken on an agent mitigated one symptom. A ticket closed anywhere in between recorded a restart as a resolution.

Write the ticket with two dates. The start is the earliest of the indexed-at time from the connector logs and the earliest deviating action from the agent records. The end is the verified eviction. The population is the query class between them, and every session the profiles flagged inside that window is in scope for notification.

That ticket is writable only if the record it depends on predates the incident. The sequence is Audit, then Enforce. The sensor records each agent’s actions first, the team reviews what a policy would have blocked against real traffic, and the policy arms per agent once the record shows the boundary is right. The progressive enforcement path exists so that the profile is already there on the day a chunk fires, and the reachability claim it produces, what this agent can call, hold and reach, is the bound on what the chunk could have done. When the agent holds placeholder credentials and the real ones are substituted outside its reach, the instruction that needed the key completes with a key that opens nothing, and the chunk’s reach shrinks before anyone has evicted it.

ARMO’s AI workload security covers the action side of a RAG incident: what each agent did after retrieval, measured against what that agent does. Bring one agent, the index it reads from and its tool registry to a working session, and the three questions in this piece get answered against that agent’s own runtime record.

FAQ

Does restarting the agent stop a RAG prompt injection? No. A restart clears the context window of one process, and the payload is a chunk in the index that the next retrieval by any process will load again. The restart is still worth doing, because it ends the current session’s actions, but it changes nothing about the chunk. Treat it as a mitigation of one session and keep the incident open until the chunk is evicted.

How do I find out which users received the poisoned answer? If your stack keeps retrieval results at chunk resolution, query that history for the chunk ID and you have the list. Most stacks do not, so the list is rebuilt from the action side: which agent sessions performed something outside that agent’s behavioral profile in the window since the chunk was indexed. Those sessions map to users through the application’s own session records. The list is a lower bound, because a session that retrieved the chunk and did nothing observable will not appear in it.

How long can a poisoned chunk stay dormant? Until a query lands close enough to retrieve it, and there is no upper bound on how long that takes. A chunk tuned for a rare question class can sit for months before the first match, and a corpus rebuild will not remove it unless the rebuild starts from a cleaned source. Dwell time is set by how often the target question is asked, which the attacker chose when they picked the class. Index-time detection is the only thing that shortens it.

Who should own removing a poisoned document? The team that owns the upstream source has to make the change, because a deletion from the index alone is undone by the next connector sync. Security owns the decision that the document is malicious and the evidence for it. Platform owns the index deletion and the replica check. Put the decider in the runbook before the incident, because an eviction that needs three teams and has no owner stays open.

Can I just re-index the whole corpus? You can, and it will not help unless the source is cleaned first, because the rebuild re-embeds the poisoned document along with everything else. A full re-index also costs embedding time and money proportional to the corpus, and in most stores it regenerates the chunk IDs you were using to trace the incident. Fix the source, evict the specific chunk and its derived copies, and replay the query class to verify. Re-index only if the source is known clean and the index cannot be edited in place.

Close

Your Cloud Security Advantage Starts Here

Webinars
Data Sheets
Surveys and more
Group 1410190284
Ben Hirschberg CTO & Co-Founder
Rotem_sec_exp_200
Rotem Refael VP R&D
Group 1410191140
Amit Schendel Security researcher
slack_logos Continue to Slack

Get the information you need directly from our experts!

new-messageContinue as a guest