tyler

2026-06-1418 minsecuritythreat-modelingaillm

Threat modeling an authenticated RAG chatbot for a bank

Part 2. Add a login gate and read-only session-scoped tools to the Part 1 bank chatbot, re-run the OWASP LLM Top 10, and the triage nearly inverts.

The injection didn’t get worse. Its payoff did.

Part 1 threat-modeled a retrieval-only, unauthenticated bank chatbot: public corpus, no tool calls, no customer data. Indirect prompt injection was the dominant threat, but its worst outcome was a corrupted answer or an XSS payload against a session with nothing in it. Now put the same chatbot inside the customer’s authenticated online-banking session and give it read-only, session-scoped tools that fetch their accounts, transactions, and statements. The injection vector is unchanged. The corpus is unchanged. The framework is unchanged. But the model now decides on its own when to call those tools, so a single poisoned chunk in the public corpus can turn it into a confused deputy that fetches the logged-in user’s financial data and pushes it out the output channel. The user asked about a mortgage rate. The model harvested their statements.

What you’ll walk away with: the same OWASP LLM Top 10 triage re-run against one changed assumption, where it nearly inverts and why; the confused-deputy chain as the new dominant threat; the output channel promoted from exploit surface to exfiltration channel, with the documented lineage behind it; and where least privilege stops helping. This is a diff against Part 1. Everything Part 1 established (RAG mechanics, the indirect-injection derivation, the framework itself) stays a link, not a re-explanation.

The reference system, as a diff

Same disclaimer as Part 1: this system is constructed for the exercise. It’s a plausible retail-bank deployment, not a real product.

Only one thing changed from Part 1, and everything in this post follows from it. The chatbot now runs inside the customer’s authenticated online-banking session, and the orchestration service has a set of read-only, session-scoped API tools: get accounts, get transactions, get statements. The agent invokes them autonomously, deciding per query whether a tool call is warranted. Retrieval is otherwise identical: the same public, PII-free corpus feeding the same prompt. The tools are strictly read-only. No transfers, no address changes, no card locks, nothing state-changing.

Authentication mechanics (login, MFA, session management) are out of scope. Assume a correctly authenticated session. The threat is not that an attacker steals the session; it’s what a poisoned corpus can do with a legitimate one.

That’s the reference design, call it (a): tool calls hit account APIs, the corpus stays PII-free. There’s a common variant (b) where per-user statements and documents are embedded into a shared vector store partitioned by a metadata filter. That variant moves the authorization boundary somewhere much weaker, and it gets its own section below.

The authenticated reference system, forked from Part 1. The dashed session boundary is new: the chatbot now lives inside the user's logged-in session. The read-only, session-scoped tool-call layer (orchestration to the account, transaction, and statement APIs) is the added surface. Follow the path from those APIs back into the prompt and out to the rendered response: that is how the user's financial data reaches the output channel. The public corpus and ingestion pipeline are unchanged.

The triage table, re-rated against Part 1

Re-run the OWASP LLM Top 10 (2025 edition) against the architecture above. The table mirrors Part 1’s format exactly so the diff is visible row by row. The Reasoning column states the Part 1 rating and why it moved, or didn’t.

OWASP LLM Top 10 (2025), re-triaged against the authenticated system
IDNameApplicabilityReasoning (vs Part 1)
LLM01:2025Prompt InjectionHIGHUnchanged rating, changed payoff. Same indirect-injection vector via the public corpus (Part 1: HIGH). OWASP: injection severity is “largely dependent on… the agency with which the model is architected,” and agency just went up. This is now the trigger for the confused-deputy chain, not just output corruption.
LLM02:2025Sensitive Information DisclosureHIGHLOW to HIGH. Not because PII entered the corpus, but because the user’s financial data now transits the prompt at query time and can surface in output and in retained logs. OWASP LLM02 names “financial details” and PII explicitly.
LLM03:2025Supply ChainLOW-MEDIUMUnchanged. The managed provider is still a trust boundary; corpus content sources are still the relevant supply-chain surface. The new tool APIs are first-party.
LLM04:2025Data and Model PoisoningMEDIUMUnchanged. Corpus poisoning via the ingestion pipeline, same as Part 1. Overlaps LLM01 and LLM08.
LLM05:2025Improper Output HandlingHIGHHIGH, and now the exfiltration channel. In Part 1 this was XSS against a session with nothing in it. Here the rendered output (markdown image URLs, links, XSS) carries the tool-fetched financial data out of the browser. EchoLeak / CWE-74 lineage.
LLM06:2025Excessive AgencyHIGHNOT APPLICABLE to HIGH. Read-only session-scoped tools mean the model autonomously decides which calls to make. Bounded (no state-changing actions), but OWASP: even a read-only extension “could pose a confidentiality risk.” This is the framework center of the confused-deputy risk.
LLM07:2025System Prompt LeakageMEDIUMUnchanged. Jailbreaks still extract prompt contents; the prompt may now describe the tool schema, useful to an attacker mapping the confused-deputy path, but there are still no secrets in it.
LLM08:2025Vector and Embedding WeaknessesHIGHHIGH in reference (a); the (b) variant adds a multi-tenant isolation dimension. The corpus-poisoning surface is unchanged; in variant (b), OWASP treats shared-vector-DB tenant isolation as an authorization boundary. See the (b)-variant section.
LLM09:2025MisinformationHIGHUnchanged. Hallucinated authoritative financial answers still carry compliance and reputational liability. Same CFPB posture caveat as below.
LLM10:2025Unbounded ConsumptionFOOTNOTE (was HIGH)HIGH to footnote. The endpoint is now behind authentication; per-account rate limits are trivial and denial-of-wallet stops being a design-driving threat. It still exists; it no longer competes for design attention.

Four rows moved. LLM02 went LOW to HIGH, because PII now transits the prompt at query time rather than staying out of the corpus. LLM06 went from NOT APPLICABLE (Part 1 had no tools at all) to HIGH, and it’s the framework center of everything below. LLM05 kept its HIGH but changed job: in Part 1 it was an exploit surface, here it’s the exfiltration channel for whatever the session can reach. LLM10 collapsed from HIGH to a footnote once the endpoint went behind a login. Nothing about the framework changed to cause this. Same 2025 list, one changed assumption, and the triage nearly inverts.

The confused deputy: injection pivots to tool-driven exfiltration

The dominant threat is now a confused deputy. The injection vector is exactly the one from Part 1: attacker-controlled instructions ride into the model’s context on a poisoned corpus chunk. What changed is what the model can do with them. Because the agent decides autonomously when to call its tools, injected instructions can drive those decisions.

Walk the chain concretely. An attacker plants a chunk in the public corpus (the ingestion pipeline is the same surface Part 1 covered). A logged-in customer asks a benign question, say about mortgage rates. Retrieval pulls the poisoned chunk into context alongside the legitimate content. The chunk instructs the model to call the transaction and statement tools. The model does, because those tools are session-scoped: they run with the logged-in user’s authority and return that user’s real financial data. The data lands in the model’s output. Everything after that is the output channel’s problem, covered next. The user asked about a mortgage rate; the model fetched their statements and put them somewhere an attacker can read.

This is not an exotic reading of the framework. OWASP’s canonical LLM06 example scenario is exactly this shape: “a maliciously-crafted incoming email tricks the LLM into commanding the agent to scan the user’s inbox for [sensitive] information and forward it to the attacker’s email address.” Swap the inbox for account APIs and that’s the bank chatbot. And OWASP is explicit that read-only doesn’t save you: even a read-only extension can pose a confidentiality risk if it has access to sensitive data beyond what is required. Read-only bounds what the deputy can do. It does nothing about what the deputy can read and leak.

In MITRE ATLAS (content version 2026.06), the tool-call pivot is AML.T0086 “Exfiltration via AI Agent Tool Invocation” (maturity: Realized), the technique for an agent’s own tools being invoked to exfiltrate data. The upstream injection is AML.T0051.001 “LLM Prompt Injection: Indirect”, unchanged from Part 1.

The output channel is now the exfil channel

In Part 1, improper output handling (LLM05) was XSS and CSRF against a browser session with nothing worth stealing in it. Here the rendered output carries the tool-fetched financial data out of the browser. Same render path, radically different payload.

The mechanics are the ones the exfiltration literature has documented since 2023. The model emits a markdown image or link whose URL encodes the stolen data in a query string. The client renders it, the browser fetches the attacker’s URL, and the secret leaves with the request. No user click is even required when it’s an auto-rendered image. This lineage is the evidence, and it’s a straight line:

Output-channel exfiltration lineage, 2023 to 2025
IncidentDateProductExfil channelVendor mitigation
Rehberger, image exfillate 2023ChatGPTmarkdown image auto-rendered to attacker URLurl_safe client-side safe-URL check (end 2023)
PromptArmor, Slack AIAug 20, 2024Slack AIrendered markdown link, secret in URL queryNone. Slack called it intended behavior.
Rehberger, spAIwareSep 20, 2024ChatGPT macOS appmarkdown image plus persistent memoryfixed in ChatGPT v1.2024.247
EchoLeak (CVE-2025-32711)Jun 2025M365 Copilotrendered output, CWE-74 injectionpatched server-side (May/Jun 2025)

Two entries carry the argument. Slack AI is the sharp one: an attacker put an instruction in a public channel, the victim’s own query pulled both their private secret and the attacker’s message into one context window (the attacker never joined the private channel), and the injection had Slack AI render a “click here to reauthenticate” markdown link with the secret in the URL. Slack shipped no technical fix and deemed it intended behavior. Correct authorization throughout, and the data still left. That is the whole problem with treating authorization as the fix.

EchoLeak is the closest published analogue to the bank scenario, though it applies to M365 Copilot, not a bank, and no financial-sector analogue has surfaced. It was a zero-click exfiltration Microsoft describes as “AI command injection in M365 Copilot allows an unauthorized attacker to disclose information over a network,” classified as CWE-74 (improper neutralization of special elements in output). It carries a known CVSS split: Microsoft’s CNA rating is 9.3 Critical; NVD’s re-analysis is 7.5 High. The CWE-74 framing is the point: this is an output-neutralization failure, the same class as the markdown-image lineage above, not a training-data problem.

In ATLAS 2026.06, the rendered-output technique is AML.T0077 “LLM Response Rendering” (maturity: Demonstrated).

Least privilege bounds the blast radius, it does not shorten the list

OWASP prescribes a control stack for exactly this architecture, and it maps almost point-for-point onto the reference design. Read-only tools authenticated “via OAuth and with the minimum scope required.” Execution “in the context of that specific user, and with the minimum privileges necessary,” so a session’s tools only ever reach that session’s data. And complete mediation: “Implement authorization in downstream systems rather than relying on an LLM to decide if an action is allowed or not.” The authorization boundary lives in the account and transaction APIs, not in the model. LLM01’s strategy #4 says the same thing from the other side: “Provide the application with its own API tokens for extensible functionality, and handle these functions in code rather than providing them to the model.” Build all of that. It’s necessary.

Here’s my take: for this architecture, the OWASP-prescribed stack is necessary but not sufficient. Session scoping is real and it does real work: it bounds the blast radius to one user’s data. There is no cross-tenant mass breach here, because a session’s tools can’t reach another session’s accounts. But it does not close the exfiltration channel, and it does not shorten the threat list. Slack AI is the proof. Authorization was correct, scoping was correct, and the data still walked out the output channel. Bounding what the deputy can reach is not the same as stopping the deputy from leaking what it legitimately reached.

The controls that actually bear on the exfil channel are the output-channel ones from the last section: URL allowlisting and url_safe-style rendering checks on any model-emitted link or image, plus a strict Content-Security-Policy so an exfil URL can’t fire. Add argument validation on tool calls and session-bound tool credentials that die with the session. Those harden the tool layer; the output-channel fixes are what stand between a fetched statement and an attacker’s server.

The design patterns that would close the channel structurally are still frontier, not production. Simon Willison’s Dual LLM pattern splits a privileged LLM (has tools, sees only trusted input) from a quarantined one (sees untrusted content, has no tools), with the invariant that quarantined output never reaches the privileged side. CaMeL (arXiv:2503.18813) extends that with a capability layer that tracks data provenance and blocks “the exfiltration of private data over unauthorized data flows by enforcing security policies when tools are called.” These are the most promising answers to the unsolved part. They are also not something I’d tell a bank to ship today.

The (b) variant: when the retrieval filter becomes the authorization boundary

Variant (b) is worth its own look because it moves the authorization boundary to a much weaker place. Instead of fetching per-user data through a session-scoped API, (b) embeds each customer’s statements and documents into a shared vector store and partitions them with a metadata filter on the query. Now the filter clause is the authorization boundary.

OWASP LLM08 treats this as an authorization concern, not a search-relevance one. “In multi-tenant environments where multiple classes of users or applications share the same vector database, there’s a risk of context leakage between users or queries,” and “embeddings from one group might be inadvertently retrieved in response to queries from another group’s LLM, potentially leaking sensitive… information.” The mitigation is “strict logical and access partitioning of datasets.” (OWASP scopes its example to “business information” and doesn’t name metadata filtering specifically; it treats it as one instance of access partitioning.)

My view: a metadata filter is a much weaker isolation primitive than a session-scoped API, and the failure modes are what separate them. A missing or malformed filter clause fails open. Drop the WHERE user_id = ... predicate through a bug, a refactor, or an injection that reshapes the query, and the store silently returns another customer’s chunks with no error. A session-scoped API call fails closed: the credential is the wrong user’s or it isn’t, and a bad call returns nothing or an authorization error, not someone else’s data.

There’s a second reason I’d reach for the session-scoped API, and it’s about what happens after the incident, not during it. The API gives you a discrete, attributable call per data access: this session, this user, this account, this timestamp, in the account-service logs. With a shared metadata-filtered store you’re reconstructing who-saw-what from filter predicates against a vector query log, which is a much worse position to audit from when a regulator or an incident-responder asks who accessed a given customer’s records.

The privacy lens: PII in prompts and logs, no attacker required

Set the adversary aside entirely and there’s still a new surface. The architecture change means customer financial data now transits prompts and lands in retained chat logs as a matter of normal operation. That’s a privacy problem on its own, and it’s the LINDDUN lens on the same system: data flows and retention, not attacks.

For a bank, this has a compliance shape. Balances, transactions, and statement data are nonpublic personal information, which makes them “customer information” under the GLBA safeguards duty. The cleanest public articulation of that duty is the FTC’s Safeguards Rule (16 CFR Part 314): 314.4(c)(1) requires access controls that “limit authorized users’ access only to customer information that they need” (least privilege, written into the rule), and 314.4(c)(3) requires encryption of customer information “both in transit over external networks and at rest.” One accuracy note: 16 CFR 314 by its terms covers non-banking financial institutions. A chartered bank falls under the parallel Interagency Guidelines under GLBA section 501(b), a materially identical duty. Either way, putting balances into a prompt and a retained log is in scope with zero attacker involved.

Retention by the managed provider is part of this surface too. Azure OpenAI doesn’t train on your prompts or expose them to other customers, but its default abuse-monitoring path still retains a sample of prompts and completions in a store for possible human review, unless you’re approved for modified abuse monitoring. AWS Bedrock, by contrast, defaults to zero data retention (“by default, Amazon Bedrock does not store model inputs or outputs”) and no human operator access, with some model-specific 30-day exceptions. If financial data is in the prompt, which provider you chose, and under which monitoring mode, is a data-retention decision, not just an operational one.

What the frameworks do and don’t give you

Nothing about the OWASP LLM Top 10 changed between Part 1 and this post. Same 2025 list, same ten categories, same definitions. The triage nearly inverted anyway, driven entirely by one architectural change: the system’s agency went up. That’s the reusable lesson. You can move a threat model’s whole severity profile without touching the threat list or the framework, by changing what the system is allowed to do on the user’s behalf. Frameworks enumerate; the analyst scopes and prioritizes. Neither the re-rating nor the confused-deputy chain is something OWASP hands you. It’s the same point the DREAD walkthrough makes about priority order and the Automotive Threat Matrix post makes about enumeration versus scoping.

One durability note on the LLM09 compliance angle, since it’s time-sensitive. The CFPB’s June 2023 “Chatbots in consumer finance” issue spotlight is still published, but it’s a non-binding research report, and the CFPB’s broader guidance posture contracted sharply in 2025: a mass withdrawal of roughly 67 guidance items took effect May 12, 2025. The spotlight itself was not on that list (it’s a research report, not one of the withdrawn interpretive rules, policy statements, or advisory opinions). Treat it as directional signal rather than binding expectation, and read that posture as of the May 2025 withdrawal. The underlying consumer-finance statutes remain in force independently.

Further reading

← OlderThreat modeling a RAG chatbot for a bank