You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEAT] Adaptive multi-turn XPIA via iterative payload re-injection #100
XPIAExecution plants one malicious payload, triggers the agent once, and stops at the first refusal. I'd like to add an adaptive variant: if the agent refuses, re-plant a reworded payload into the same surface, trigger again, and repeat until it works or a budget runs out. The attacker only ever edits the planted data and sends the agent the same benign prompt - it never argues with the agent directly - so the attack stays indirect.
Motivation
Real indirect red-teaming is iterative. You rarely land the payload on the first try - you reword or re-encode it, re-trigger, see what got through, and try again. RAMPART can't express that loop today: single-shot XPIA treats the first refusal as the end.
That direct-vs-indirect line is the whole point, and it's why this isn't just "port Crescendo." In Crescendo the attacker is the user: it types escalating messages straight at the target and prunes the failed ones from the shared chat (that's how PyRIT drives it). That can't live in an indirect framework without making the trigger itself adversarial - which RAMPART explicitly forbids (attacks/__init__.py: "XPIA triggers are never adversarial - the attack is in the injected payload, not the prompt"). So instead of porting a direct technique, this gives the same try-again behaviour to the indirect channel, where the adaptation happens in the planted payload rather than the conversation.
Proposed solution
A new execution beside XPIAExecution that puts the loop around the injection instead of the chat. It reuses what's already there - Surface.inject() to re-plant, the benign trigger driver, the evaluators, and resolve_as_attack for the verdict - and reuses the existing injection-handle lifecycle for teardown, cycling one handle per round. Additive, no protocol changes. Cost per round depends on the surface (cheap where the payload is just overwritten, slower where re-indexing is involved). I'll bring the design and a working PoC in the PR rather than spec it out here.
Additional context
Before I open a PR: is an adaptive or iterative XPIA execution already on your roadmap? If so I'll stand down; if not, I'll put up a PoC shortly.
Thanks Volkan Kutal (@KutalVolkan) for raising this. I agree that iteratively refining an indirect payload can be useful during red-team exploration, but I don't think that loop belongs inside a single XPIA execution.
I'm also not convinced that "refusal" is a meaningful signal here. The user is making a benign request and does not know that the retrieved content contains an injection. A correctly defended agent may still complete the benign task while ignoring the malicious instruction. For example, it may summarize the document but decline to follow an embedded instruction to send the summary elsewhere. It could also mention the suspicious instruction, return a generic apology, ask for clarification, or surface a retrieval error. Those outcomes mean different things, so a refusal detector cannot reliably decide when to generate and plant another payload.
Current XPIA does not actually stop on the first refusal. It stops when the attack objective is detected, the driver runs out of prompts, or the turn budget is reached. With a single static trigger, the driver simply has nothing else to send after the first turn. There is no current notion of "the agent refused, so retry the attack."
I think one thing that's really important is that replacing payload A with payload B while keeping the same agent session makes the result difficult to interpret. Payload A is still part of the conversation state, and an eventually consistent search index may continue serving it after cleanup. If a later attempt succeeds, we cannot tell whether B caused it, A was still influencing the agent, or the attempts worked cumulatively.
RAMPART already has a cleaner workflow for this. We can use PayloadTemplate and Payloads.generate_async() to create variants, review them, store them with PayloadStore, and check the resulting collection into the repository. Each payload can then run as its own parametrized XPIA test with a fresh session. That gives us a reproducible failure tied to one known payload.
Generating payloads during the test would make the result depend on the target agent, the generation model, sampling behavior, previous responses, and indexing timing. Even with a seed, that is too much variability for a PR gate. LLMDriver already introduces some unavoidable variability where an adaptive conversation is genuinely needed. I would rather not compound that by dynamically changing the injected artifact too.
An adaptive payload generator could still be useful as an offline discovery tool. It could run a series of isolated experiments and use the results to produce better candidates. Successful candidates would then be reviewed and promoted into the pinned payload collection. At that point, though, we are describing a campaign across independent XPIA runs, not a multi-turn XPIA conversation.
I would therefore reframe this issue around offline payload discovery or an isolated campaign runner rather than add the proposed same-context execution strategy. That also fits the roadmap's direction of pinning verdict-affecting corpora and running them through normal pytest parametrization.
Activity
nina-msft commentedon Jul 21, 2026
ACK - Discussing this with the team and will get back to you by EOW :-)
bashirpartovi commentedon Jul 30, 2026
Thanks Volkan Kutal (@KutalVolkan) for raising this. I agree that iteratively refining an indirect payload can be useful during red-team exploration, but I don't think that loop belongs inside a single XPIA execution.
I'm also not convinced that "refusal" is a meaningful signal here. The user is making a benign request and does not know that the retrieved content contains an injection. A correctly defended agent may still complete the benign task while ignoring the malicious instruction. For example, it may summarize the document but decline to follow an embedded instruction to send the summary elsewhere. It could also mention the suspicious instruction, return a generic apology, ask for clarification, or surface a retrieval error. Those outcomes mean different things, so a refusal detector cannot reliably decide when to generate and plant another payload.
Current XPIA does not actually stop on the first refusal. It stops when the attack objective is detected, the driver runs out of prompts, or the turn budget is reached. With a single static trigger, the driver simply has nothing else to send after the first turn. There is no current notion of "the agent refused, so retry the attack."
I think one thing that's really important is that replacing payload A with payload B while keeping the same agent session makes the result difficult to interpret. Payload A is still part of the conversation state, and an eventually consistent search index may continue serving it after cleanup. If a later attempt succeeds, we cannot tell whether B caused it, A was still influencing the agent, or the attempts worked cumulatively.
RAMPART already has a cleaner workflow for this. We can use
PayloadTemplateandPayloads.generate_async()to create variants, review them, store them withPayloadStore, and check the resulting collection into the repository. Each payload can then run as its own parametrized XPIA test with a fresh session. That gives us a reproducible failure tied to one known payload.Generating payloads during the test would make the result depend on the target agent, the generation model, sampling behavior, previous responses, and indexing timing. Even with a seed, that is too much variability for a PR gate.
LLMDriveralready introduces some unavoidable variability where an adaptive conversation is genuinely needed. I would rather not compound that by dynamically changing the injected artifact too.An adaptive payload generator could still be useful as an offline discovery tool. It could run a series of isolated experiments and use the results to produce better candidates. Successful candidates would then be reviewed and promoted into the pinned payload collection. At that point, though, we are describing a campaign across independent XPIA runs, not a multi-turn XPIA conversation.
I would therefore reframe this issue around offline payload discovery or an isolated campaign runner rather than add the proposed same-context execution strategy. That also fits the roadmap's direction of pinning verdict-affecting corpora and running them through normal pytest parametrization.
[FEAT]: Add adaptive XPIA (microsoft#100)
[FEAT]: Add adaptive XPIA execution (microsoft#100)
[FEAT]: Expose adaptive XPIA factory (microsoft#100)
[FEAT]: Export adaptive XPIA execution (microsoft#100)
[TEST]: Cover adaptive XPIA re-injection (microsoft#100)