Agentic architecture
Verified by ROOTVIEL11 minTool Output Injection
A tool returns attacker-influenced content, and that content flows into a security-relevant decision — a policy check, an approval, a branch that grants access. The agent trusts a tool result as if it were a trusted fact, so untrusted output steers a decision it should never touch.
Classification
- Domain
- Agentic architecture
- ROOTVIEL support
- Verified by ROOTVIEL — A deterministic, LLM-free verifier settles a specific property under stated assumptions — either a structural property over a graph, or an effect reproduced in execution. It does not by itself mean the whole system is secure, nor that an end-to-end exploit was run beyond what the evidence scope states.
- Evidence scope
- Structural property — Checked over a static authority/dataflow graph — the property holds in the code; no exploit was run.
- Analysis family
- FAM-GENAI-STATIC
- CWE
- CWE-807
- OWASP
- LLM01
Definition
Tool output injection is the use of a tool’s result — the text or data a tool hands back to the agent — as an untrusted carrier of instructions or facts that then influence a privileged decision. A tool that fetches a web page, reads a file, queries a ticket, or calls another service returns content an attacker may control. When that content reaches a point where the system decides whether to approve, authorize, or branch on it, the attacker’s data is deciding the outcome.
It is prompt injection framed as a dataflow problem: the taint source is the tool result, and the sink is a policy or approval decision. The distinguishing property of this class is that the decision itself is the sink — not merely the model’s prose, but a security-relevant branch whose inputs should have been trusted.
Context
Agentic pipelines routinely feed one tool’s output into the next step: a fetched document becomes the basis for an action, a lookup result gates whether a request is allowed, a sub-agent’s answer is taken as ground truth. Any such hand-off is a candidate when the upstream tool can return attacker-influenced data and the downstream step makes a decision that matters.
- ▸An agent that fetches external content and then decides, from that content, whether an operation is permitted.
- ▸A policy or approval check whose inputs include fields populated by a tool result.
- ▸Multi-tool chains where a result from a low-trust tool is consumed by a high-trust action without re-establishing trust.
Technical fundamentals
The durable model is taint tracking over the agent’s dataflow graph. A tool result is a taint source; a decision node — the predicate of a policy check, the condition on an approval — is a sink. Tool output injection exists when there is a path from the source to the decision sink with no intervening control that either strips the data’s authority or re-validates it against trusted state.
This class is amenable to sound static reasoning precisely because the question is structural: is there a proven path from untrusted tool output to a policy/approval decision? That is a graph reachability question, not a judgement about natural-language content. A verifier can establish the path deterministically and demand that the path be real — an actual edge in the observed or statically-derived graph — before it reports anything.
- Taint source
- A tool result an attacker can influence — fetched content, a queried record, a sub-agent reply.
- Decision sink
- A node whose output authorizes or denies — a policy predicate, an approval gate, an access-granting branch.
- Influence/decision graph
- The dataflow graph connecting sources to the decisions they feed; the object the verifier reasons over.
Root cause
The defect is that a tool result — untrusted by provenance — is admitted into a decision that is supposed to rest on trusted state. The system conflates "data a tool returned" with "a fact the system vouches for". The authority to decide is implicitly delegated to whoever controlled the tool’s output. Nothing on the path re-anchors the decision to trusted inputs, so the decision inherits the trust level of its worst input, which is the attacker’s.
How the attack works, step by step
- 1An attacker influences what a tool will return — by planting content the tool fetches, or a record the tool queries.
- 2The agent calls the tool and receives the attacker-influenced result.
- 3The result flows into a decision: a policy predicate reads a field from it, or an approval gate branches on it.
- 4Because the decision trusts its inputs, the attacker’s data chooses the branch — approve instead of deny, allow instead of block.
- 5The privileged action downstream of the decision executes, authorized by content the attacker controlled.
Vulnerable example
A synthetic, illustrative approval gate that reads its verdict from a field in an untrusted tool result:
synthetic — do not use; illustrates the flaw# UNSAFE: an approval decision branches on untrusted tool output. result = fetch_tool(url) # attacker-influenced content record = parse(result) # e.g. {"risk": "...", "approved": "..."} # the decision trusts a field the attacker can set: if record.get("approved") == "yes": # tainted field drives the branch grant_access(user, resource) # ... and the privileged action runs else: deny(user, resource)
Corrected example
Re-anchor the decision to trusted state. The tool result may inform a human or a model, but the authorization predicate must read only trusted inputs — the caller’s identity, a server-side policy, state the system itself vouches for. Tainted fields are treated as data to display or log, never as the verdict.
synthetic — the decision rests only on trusted state# SAFER: untrusted tool output never reaches the authorization predicate. result = fetch_tool(url) # still untrusted record = parse(result) display(record) # fine to show / log as DATA # the decision reads only trusted inputs: allowed = policy.decide(actor=authenticated_user, resource=resource, context=trusted_state) # no field from 'record' here if allowed: grant_access(authenticated_user, resource) else: deny(authenticated_user, resource)
Limit of the fix: keeping tainted output out of the predicate removes this path, but the same tool result can still mislead a human or a model elsewhere. The guarantee is specific — "no untrusted tool output reaches this decision" — and holds only for decisions the verifier can see on a proven path.
Impact
- ▸Authorization: an attacker flips an allow/deny decision by controlling a tool’s output, bypassing the intended policy.
- ▸Integrity: approvals and gated actions proceed on attacker-chosen inputs, corrupting the decisions they record.
- ▸Confidentiality: a decision to release data, steered by tainted output, leaks it to the attacker.
- ▸Trust propagation: once one decision is subverted, downstream steps inherit the false "approved" state.
Detection
This class is detectable with sound static analysis: build the influence/decision graph, taint tool results as untrusted sources, and search for a path into a policy/approval decision node. Because the question is structural reachability, a finding can be a proven path rather than a guess — and the discipline is to report only when the path is real, not builder-introduced. A proven source-to-decision path is a confirmed finding; the absence of one over the analysed graph is "no path found in scope", which is stronger here than in purely probabilistic classes but still bounded by what the graph captures.
How ROOTVIEL analyzes it
- Support level
- Verified — the execution engine’s influence/decision-graph verifier establishes the path deterministically on a proven route.
- Family
- FAM-GENAI-STATIC — static dataflow/graph reasoning.
- Declared invariant
- "tainted tool output must not override policy."
The engine builds an influence/decision graph, marks tool output as an untrusted source, and flags a case where that untrusted output influences a policy or approval decision. It reports only when the path from the tainted tool result to the decision node is proven in the graph.
Honest framing: a confirmed finding requires a proven path from untrusted tool output to a policy/approval decision; the verifier does not grade a builder-introduced edge as a finding. Where no such path is proven, the result is "no path found in the analysed graph" — scoped to what the graph captures, not a universal guarantee that no decision anywhere can be influenced.
Mitigation
Anchor every security decision in trusted state and keep untrusted output out of predicates.
- ▸Preventive: authorization predicates read only trusted inputs (authenticated identity, server-side policy, system-vouched state) — never a field from a tool result.
- ▸Preventive (provenance): tag tool results as untrusted and carry that label through the dataflow so a decision node can refuse tainted inputs.
- ▸Detective: run influence/decision-graph analysis to find any untrusted-output→decision path before it ships.
- ▸Corrective: when a decision must consider external content, require an independent trusted check (or human approval) that the external content cannot alter.
Verifying the mitigation
Verify by re-running the influence/decision-graph analysis and confirming no proven path remains from any untrusted tool output to a policy or approval decision. Complement it with a synthetic test: craft a tool result that tries to set an "approved" field and confirm the decision ignores it and rests only on trusted state. State the result as "no tainted-output→decision path in the analysed graph", scoped to what the graph captures.
A proven-path analysis is strongest when the graph is complete; decisions reached through paths the graph does not model are outside the guarantee and need their own review.
Common mistakes
- ▸Reading an "approved"/"risk"/"allow" field from a tool result and treating it as a trusted verdict.
- ▸Sanitising a tool result’s text but still letting a field from it reach the decision predicate.
- ▸Assuming a tool is trusted because it is first-party, while its output is attacker-influenced upstream.
- ▸Taking "no path found" as universal immunity rather than as scoped to the analysed graph.
References
- [1]OWASP Top 10 for LLM Applications — LLM01 Prompt Injection
- [2]OWASP Agentic Security Initiative
- [3]MITRE ATLAS
- [4]MITRE CWE-807 (Reliance on Untrusted Inputs in a Security Decision)
- [5]Model Context Protocol — Specification
References point to public, well-known technical sources for further study.
Related articles
