#AI#AI Agent#DevOps

Agent Untrusted Data Taint Propagation: Forced Isolation from Retrieved Content to Tool Parameters

External web pages, RAG fragments, and tool results remain untrusted even after being summarized. This article designs taint tags that propagate with data across steps, deterministic sink policies, audit events, attack testing, and isolated rollback.

Key Points

  • Topic Focus: AI、AI Agent、DevOps
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

The difficulty of indirect prompt injection is not only that the model reads a malicious web page, but that the content undergoes retrieval, summarization, memory, and planning transformations many times. Many systems only tag <untrusted> at the fetch entry, but after summarization create new strings without provenance, and the next round of the Agent may use them to select tools, assemble URLs, or request permissions.

Prompt Injection Defense has already established that model instructions cannot serve as a security boundary. This article further turns "untrusted" into runtime-propagable metadata, and like static-analysis data flow, blocks it at high-risk sinks via deterministic policy, rather than asking the model again "is this safe?"

Values and Trust Tags Must Move Together

Every value entering the Agent is wrapped as an object with provenance. Fields include source, tenant, sensitivity level, taints, evidence ID, generation step, and policy version; the source uses a bounded enumeration and cannot accept a tool self-reporting trusted=true.

ts
type Tainted<T> = {
  value: T;
  provenance: Array<{ source: string; evidenceId: string }>;
  taints: Array<"untrusted_instruction" | "external" | "sensitive" | "cross_tenant">;
  tenantId: string;
  policyVersion: string;
};

function derive<T>(value: T, inputs: Tainted<unknown>[]): Tainted<T> {
  return {
    value,
    provenance: inputs.flatMap((item) => item.provenance),
    taints: [...new Set(inputs.flatMap((item) => item.taints))],
    tenantId: requireSameTenant(inputs),
    policyVersion: ACTIVE_POLICY,
  };
}

Propagation rules should be monotonic: when merging, take the union of taints, and translation, summarization, embedding, reranking, and model rewriting cannot remove tags. Only deterministic transformations can downgrade a class of taint; JSON Schema only proves shape, and manual review only approves specific actions, neither of which can permanently mark natural language as trusted.

RAG scenarios must also preserve document ACLs and tenant tags. RAG Retrieval Access Control filters evidence before recall, while taint propagation ensures that post-recall summaries and citations do not lose that boundary. Any data mixed in from different tenants fails at requireSameTenant, and cannot rely on the model to "only use the part that belongs to the current user."

Write an Explicit Matrix for High-Risk Sinks

The source only describes origin; the sink decides whether it can be used. You can allow external_web to enter summary and citation candidates, but forbid it from deciding tool names, expanding capability scope, modifying system policy, constructing arbitrary network targets, or entering a shell. Values tagged sensitive can enter controlled database writes for the current tenant, but cannot enter external HTTP bodies. Business text in tool parameters can carry taint to the executor, which handles it per field policy.

text
sink=planner_context      external=allow_as_data  sensitive=redact
sink=tool_selector        untrusted_instruction=deny
sink=capability_request   external=deny           cross_tenant=deny
sink=network_destination  external=deny_unless_allowlisted
sink=external_body        sensitive=deny
sink=audit_log            sensitive=hash_or_drop

Policy inputs must come from structured tags and trusted sessions, and cannot accept a model output of risk=low. When the policy denies, it returns a stable code, the triggered taint, the source evidence, and the allowed alternative actions, such as only generating a draft or requesting manual approval. This way the Agent can recover the task, but cannot launder the taint by rewriting the same content.

Cover Memory, Sub-Agents, and Tool Re-flow

Long-term memory stores taint, tenant, evidence ID, and expiry time together, and restores them verbatim after retrieval. Otherwise, the summarized entry loses its pollution source after being stored, and all derived memory cannot be isolated when the source document is purged.

When delegating to a sub-Agent, the task envelope carries input tags and allowed sinks, and the sub-Agent can only add restrictions, not change external to trusted. The trusted tenant context in MCP Multi-Tenant Isolation applies as well. Tool outputs default to carrying tool_output and the tool's own trust level; even internal tools may return text written into a database by an attacker, and content cannot be trusted just because the transport channel is trusted.

Traces only record taint counts, policy decisions, evidence ID hashes, and rejection reasons. AI Agent Observability can carry these step-level security events.

Prove Propagation Has No Gaps with Cross-Step Attack Testing

Testing cannot just send a sentence like "ignore the previous rules." Prepare malicious PDFs, web comments, ticket fields, and tool returns, let the instruction first be summarized, written into memory, passed to a sub-Agent, and then induce a call to an exfiltration tool. Assert that the taint still exists at each hop, dangerous sinks are rejected at runtime, and the model cannot execute even if it claims to have verified. Also add legitimate external data, confirming it can still be used for summaries and citations, to prevent the policy from degrading into "all content is unusable."

Property testing randomly combines source, transform, and sink, verifying that derived taints are no less than the input union, and that trust cannot rise without sanitizer proof. Policy upgrades must also replay historical rejection samples.

Incident Isolation and Rollback Must Fail Closed

When a taint is found to have been lost after some transform, first disable the relevant write tools and external egress, retaining only read-only summary capability; isolate derived memory by evidence ID, pause affected runs, and revoke the short-term permissions they obtained. Do not simply delete audit records, and do not continue running the old context with a new prompt.

After the fix, restore the last signed propagation policy and envelope schema, replay the attack chain in an isolated environment, and confirm each sink produces the expected decision. Open read-only first, then reversible writes, and finally restore external side effects. The rollback safety baseline should be "unknown tags deny high-risk sinks," rather than treating unknown values as trusted to be compatible with old tasks.

Taint propagation cannot guarantee that the model is unaffected by malicious text, but it can guarantee that the effect does not silently turn into permissions and data flows. As long as provenance moves with values, downgrade requires evidence, and sinks are controlled by deterministic policy, the Agent's wrong judgments remain bounded within observable and revocable limits.

Further Reading

Related

Related Content