Instructions in the Wiki: Hunting an Indirect Prompt Injection
The challenge
Wren is Fernpath's staff assistant. It answers from a ten-document wiki export and it answers instantly, except on expenses questions, where three people this week noticed it pause and one saw their browser warn about an outbound request. Nothing in Wren's configuration changed. Its knowledge base did, last night. Somewhere in those ten documents is text that talks to the assistant instead of to a colleague, and it is not in either of the two pages that discuss prompt injection openly. Find the poisoned document and name the file.
What you'll learn
- Explain how indirect prompt injection reaches a model through retrieved content
- Use a sync log to narrow a corpus to recently changed documents
- Recognise markup that renders as nothing but is indexed in full
- Distinguish quoted injection examples from a live instruction
- Connect a tool-enabled assistant to the exfiltration path it creates
Skills tested
Prerequisites
- What a retrieval-augmented assistant does
- Basic shell navigation
How it works
A retrieval assistant answers by pasting documents into a prompt. Once that happens, the model has no reliable way to tell the difference between the operator's configuration and a sentence somebody typed into the wiki, because by the time it reads them they are the same kind of text in the same context window. That is indirect prompt injection: the attacker never talks to the model, they write something the model will later be handed.
What turns it from a curiosity into an incident is tools. An assistant that can only talk gives up, at worst, some text. An assistant with fetch_url has an outbound channel, and appending the colleague's question to a URL is enough to exfiltrate whatever they asked, one query at a time, while the answers stay helpful enough that nobody investigates.
The hiding place is the part worth remembering. Rendering and indexing are different pipelines. HTML comments, white text, zero-width characters, off-screen spans, alt text and document metadata all render as nothing to a person and go into the index in full. Reviewing the wiki in a browser would never have found this page.
Common mistakes
- Answering security-awareness.md. It contains three injection strings, quoted as examples with an explicit instruction not to treat them as instructions. It is the page that wins a keyword search, which is exactly why it is here.
- Answering wren-faq.md. It documents the policy on whether Wren should obey documents. Writing about the rule is not breaking it.
- Grepping for the word ignore. The live injection is polite, plausible and never says any of the phrases people expect.
- Reading the rendered wiki. The payload is in an HTML comment. A browser shows nothing there.
- Skipping the sync log. One document changed, doubled in size, and was written by an automation account. That is most of the investigation in a single file.
How to defend against it
Treat retrieved content as data that is never allowed to become instruction.
- Strip what a reader cannot see before indexing: HTML comments, zero-width characters, hidden elements and metadata. If it does not render, it should not be embedded.
- Allowlist the hosts
fetch_urlcan reach and log every call with the document that triggered it. WREN-88 has been open since March, and it is the reason this injection had somewhere to send data. - Alert on changes to indexed documents from automation accounts, and on any document whose size jumps sharply between syncs.
- Require a second, non-model check before a tool call is made on the strength of retrieved text, and show the user every outbound request the assistant makes.
- Rotate nothing and panic about nothing until you know the window: the sync log gives the exact time the document changed, and the fetch logs give the questions that leaked.