✳ Field lesson G2 / 10 min

Excessive agency: why a model sentence is never an authorization decision

Excessive Agency (OWASP LLM03:2026, LLM06 in the 2025 list) occurs when an LLM-backed system is granted more tools, broader permissions, or greater autonomy than necessary, allowing untrusted inputs or model errors to trigger unauthorized state changes in downstream services.

aiexcessive-agencyleast-privilegetoolsCISSP D8CySA+ (CS0-003) D3CCSP D4

A proposal is only the beginning

ONE ACTION.

Follow an assistant’s request through the gates that decide what it may change. Open the proposal. Change a boundary. Check what actually happened.

The task is to draft a reply. The runner has only mail:draft.

THE ACTION BOUNDARYPaused · step 1

Read every complete journey below. The spatial view and step controls become available when the interactive lesson loads.

A document is data

The user asks for a draft reply. The supplier document also says to send a payment. That embedded instruction is untrusted data.

  1. 1Supplier data
  2. 2Proposal
  3. 3Policy
  4. 4Approval
  5. 5Application
  6. 6Readback

Inspect the current instant

The caller needs fresh readback to verify the service’s state.

Proposal
Not generated yet
Fresh readback
Not checked

PROPOSE ≠ AUTHORIZE ≠ APPLY ≠ VERIFY

A synthetic, deterministic example. No model is queried and no real action is executed. Outcomes and timings are authored for teaching; payment approval here is a valid, unused record bound to exact parameters.

Read every journey, including refusals and repair

Refuse the injected payment

The task is to draft a reply. The runner has only mail:draft.

  1. A document is data. The user asks for a draft reply. The supplier document also says to send a payment. That embedded instruction is untrusted data.
  2. The assistant proposes an action. payments:send · Supplier B · USD 1000. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. The tool boundary refuses. The runner has mail:draft only. payments:send is not permitted, so no payment call is made.
  4. Ask the authoritative service. A fresh ledger read finds no payment P220. The refused action was never sent to the payment service. Refusal and checked downstream state are separate evidence.

Give the assistant broad tools

The same injected proposal reaches a runner with payment authority and no independent approval gate.

  1. A document is data. The user asks for a draft reply. The supplier document also says to send a payment. That embedded instruction is untrusted data.
  2. The assistant proposes an action. payments:send · Supplier B · USD 1000. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. Broad authority lets it through. The runner has payments:send, and this deliberately flawed workflow treats model output as permission. The warning in its prompt cannot enforce a boundary.
  4. The approval gate is missing. No independent human approval is required by this flawed workflow. The assistant’s claim that it is approved is not an approval record.
  5. The service applies the action. The synthetic ledger creates P220 for Supplier B / USD 1000. This is an unauthorized outcome of the deliberately flawed design. Caller acknowledgment is still pending.
  6. An acknowledgment comes back. The caller receives a success acknowledgment. The next check queries authoritative state by the operation ID instead of treating model prose as evidence.
  7. Ask the authoritative service. A fresh read returns payment P220: Supplier B / USD 1000. The caller can now report that observed state. This example assumes the read endpoint returns current authoritative state.

Repair: keep the task draft-only

A narrow draft action matches the user task and runner permission.

  1. A document is data. The user asks for a draft reply to Supplier A about USD 100. The supplier document supplies facts, not permission to use tools.
  2. The assistant proposes an action. mail:draft · Supplier A · USD 100. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. Independent policy checks the action. A narrow draft tool matches the task and the runner’s mail:draft scope. A draft is not a sent email or a payment.
  4. The draft needs no payment approval. The draft stays within the low-impact task. This example does not require payment approval for saving a draft.
  5. The service applies the action. The draft service saves D220. Only the service knows this effect at this instant; the caller has not received an acknowledgment.
  6. An acknowledgment comes back. The caller receives a success acknowledgment. The next check queries authoritative state by the operation ID instead of treating model prose as evidence.
  7. Ask the authoritative service. A fresh read returns draft D220 saved. The caller can now report that observed state. This example assumes the read endpoint returns current authoritative state.

Authorize one exact payment

A separate payment task permits Supplier A / USD 100, with approval bound to these exact parameters.

  1. A document is data. An authorized operator requests a separate payment workflow. Supplier text still cannot grant permissions or approve a payment.
  2. The assistant proposes an action. payments:send · Supplier A · USD 100. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. Independent policy checks the action. The payment tool and runner scope are permitted for this operator. That policy result alone does not supply human approval.
  4. Approve this exact action. The trusted approval record matches payments:send, Supplier A, USD, and 100. This synthetic record is valid and unused; real implementations must also enforce expiry and single use.
  5. The service applies the action. The synthetic ledger creates P220 for Supplier A / USD 100. This action passed the policy and exact approval checks. Caller acknowledgment is still pending.
  6. An acknowledgment comes back. The caller receives a success acknowledgment. The next check queries authoritative state by the operation ID instead of treating model prose as evidence.
  7. Ask the authoritative service. A fresh read returns payment P220: Supplier A / USD 100. The caller can now report that observed state. This example assumes the read endpoint returns current authoritative state.

Change the approved recipient

Approval covers Supplier A / USD 100. The proposed Supplier B / USD 100 is a different action.

  1. A document is data. An authorized operator requests a separate payment workflow. Supplier text still cannot grant permissions or approve a payment.
  2. The assistant proposes an action. payments:send · Supplier B · USD 100. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. Independent policy checks the action. The payment tool and runner scope are permitted for this operator. That policy result alone does not supply human approval.
  4. Approval does not match. The stored approval is for Supplier A / USD 100. The proposed payee is Supplier B. Binding approval to tool, payee, currency, and amount stops the changed action.
  5. Ask the authoritative service. A fresh ledger read finds no payment P220. The refused action was never sent to the payment service. Refusal and checked downstream state are separate evidence.

Lose the service acknowledgment

The exact approved payment is applied, but the caller times out. A fresh readback resolves this authored case.

  1. A document is data. An authorized operator requests a separate payment workflow. Supplier text still cannot grant permissions or approve a payment.
  2. The assistant proposes an action. payments:send · Supplier A · USD 100. This is an authored model output, not a prediction that every model follows the document. A proposal grants no authority.
  3. Independent policy checks the action. The payment tool and runner scope are permitted for this operator. That policy result alone does not supply human approval.
  4. Approve this exact action. The trusted approval record matches payments:send, Supplier A, USD, and 100. This synthetic record is valid and unused; real implementations must also enforce expiry and single use.
  5. The service applies the action. The synthetic ledger creates P220 for Supplier A / USD 100. This action passed the policy and exact approval checks. Caller acknowledgment is still pending.
  6. The caller times out. The call timed out after this service applied the action. A timeout does not prove failure. Blindly retrying could duplicate an action; query the stable operation ID first.
  7. Ask the authoritative service. A fresh read returns payment P220: Supplier A / USD 100. The caller can now report that observed state. This example assumes the read endpoint returns current authoritative state.

Helpful first: Prompt injection and untrusted retrieval (RAG): when data hijacks instructions

Key points

  • Identify the three root causes of Excessive Agency: excessive functionality (too many tools), excessive permissions (over-scoped credentials), and excessive autonomy (missing approval gates)
  • Contrast read/draft actions with high-impact write actions across downstream services
  • Verify downstream state via fresh readback rather than trusting model prose
  • A model sentence is not an authorization decision.

Three ways an AI agent gains excessive agency

When an engineering team connects a language model to real enterprise APIs - databases, email servers, payment rails, or CI/CD deployers - the failure mode shifts from "the chatbot said something wrong" to "the agent did something destructive." In the OWASP LLM Top 10, this architectural flaw is called Excessive Agency.

Excessive agency stems from three distinct mistakes: Excessive Functionality (attaching delete, wire-transfer, or shell-exec tools to an agent that only needs to draft summaries), Excessive Permissions (running the tool executor with a global admin service account instead of a least-privilege token), and Excessive Autonomy (allowing the model’s tool-call JSON output to execute high-impact writes directly without an independent policy and human gate).

Eliminating the three dimensions of Excessive Agency
Agency DimensionVulnerable PatternLeast-Privilege Engineering Control
Functionality (Tool Catalog)Exposing a generic run_sql(query) or http_post(url, body) tool to the modelExpose only narrow, typed, single-purpose tools (e.g., create_email_draft) and strip unneeded plugins
Permissions (Downstream Identity)Tool runner authenticates to the payment API using a static master key with payments:adminUse scoped, short-lived OBO (On-Behalf-Of) tokens limited to the exact resource and action
Autonomy (Execution Gate)Model output call_tool("release_payment") executes immediately against the bank APITreat model output as an unverified Proposal; enforce an independent policy latch and human approval before execution

Separating Task, Proposal, Decision, Application, and Readback

At Meridian Space & Robotics (chronos.orbital), Principal Security Architect Vera Vance requires every agentic workflow to separate five distinct steps: Task (the initial user goal plus any untrusted external data), Propose (the LLM generating a candidate tool call), Decide (an independent policy enforcement gate and, for high-impact actions, an authenticated human reviewer evaluating the proposal), Apply (the downstream service executing only what passed the gate), and Check (performing a fresh GET readback from the authoritative service to confirm what actually changed).

Why is that final Check step mandatory? Because an LLM can easily hallucinate "I have saved the draft and canceled the transfer" even when the API call failed, or an attacker’s prompt injection can instruct the model to lie about a silent payment.

Walkthrough: gating tool calls against untrusted supplier input

Compare the three execution paths in the interactive figure below (Task -> Propose -> Decide -> Apply -> Check). In every path, the workflow begins with an untrusted supplier input (Supplier A · USD 100) while the downstream email draft service starts at Not saved and the payment ledger starts at No payment record.

In the defended baseline, the model may propose both a harmless email draft (D220) and a risky payment action, but the independent Gate / Latch enforces least privilege: the low-risk draft is applied (GET · D220 retained), while the unapproved payment proposal (P220 / USD 1,000) is blocked so the ledger readback confirms GET · no P220 / USD 1,000. Contrast that with the Broad tools fault variant - where an over-privileged tool gate lets the risky payment land (GET · P220 / USD 100 held in fault state) - and the Missing approval variant, where a payment to a different payee or amount (None for B / USD 100) is rightly refused at the human approval latch until an authorized operator explicitly signs off on the exact payee, currency, and amount.

FOLLOW THE ACTIONS

Drafting and payment need different authority

Follow one invoice through a proposal, service checks, application and fresh readback.

Choose a path
LANEPAY / ONE LOCAL EXERCISEI220 · draft taskReceived invoice I220Original keptSupplier A · USD 100Selected model outputTeaching exampleNo proposal yetTool service grantsCheck every callNot checked yetDraft D220Saved, not sentNot savedPayment queue P220Not settlementNo payment recordTrusted approvalExact argumentsNo approval suppliedTASK RECEIVEDProposal ≠ authority ≠ outcome

Kael Cho asks for a draft for I220, Supplier A, USD 100. No payment was requested.

More about this step

The invoice and draft task are received. The selected service begins with USD 1,000 and no D220 or P220.

Chapter 1 of 5

Read all chapters

Nothing plays on its own. Watch all runs one overview. Replay this step stops on the selected chapter; Pause and Resume keep your place. Scrub or choose a chapter to inspect. Opening detail, leaving the picture, or hiding the tab pauses playback. Reduced motion shows still chapters with no travel.

Draft only

  1. Kael Cho asks for a draft for I220, Supplier A, USD 100. No payment was requested.The invoice and draft task are received. The selected service begins with USD 1,000 and no D220 or P220.
  2. The selected output proposes a draft and a USD 100 payment to Supplier B instead. Neither proposal has executed.This is predetermined teaching output, not a live model test. An unexpected output can exceed the task without prompt injection.
  3. The assistant can save D220 for I220. It has no payment grant, so the proposed debit is refused.A separate decision request reads stored grants. It changes no records. Each later application call checks its authority again.
  4. The service saves D220 and returns 403 for payment. No P220 is created and the balance stays USD 1,000.Separate POST calls recheck identity, action, resource and any required approval. Draft storage and queue acceptance have separate responses.
  5. Fresh reads find D220, no payment record and USD 1,000. I220 and the legitimate draft remain available.Independent GETs retrieve draft, invoice, queue and balance. They prove these local records only, not a bank transfer or every agent workflow.

Broad tools

  1. Kael Cho asks for a draft for I220, Supplier A, USD 100. No payment was requested.The invoice and draft task are received. The selected service begins with USD 1,000 and no D220 or P220.
  2. The selected output proposes a draft and a USD 100 payment to Supplier B instead. Neither proposal has executed.This is predetermined teaching output, not a live model test. An unexpected output can exceed the task without prompt injection.
  3. The broad service permits payment without approval; this exceeds Kael Cho’s drafting task.A separate decision request reads stored grants. It changes no records. Each later application call checks its authority again.
  4. The broad service saves D220, queues P220 for Supplier B and holds USD 100. Bank settlement is not shown.Separate POST calls recheck identity, action, resource and any required approval. Draft storage and queue acceptance have separate responses.
  5. Fresh reads find D220 and the unintended queued payment. USD 100 is held; settlement and later effects remain unknown.Independent GETs retrieve draft, invoice, queue and balance. They prove these local records only, not a bank transfer or every agent workflow.

Missing approval

  1. Kael Cho asks for a draft for I220, Supplier A, USD 100. No payment was requested.The invoice and draft task are received. The selected service begins with USD 1,000 and no D220 or P220.
  2. The selected output proposes a draft and a USD 100 payment to Supplier B instead. Neither proposal has executed.This is predetermined teaching output, not a live model test. An unexpected output can exceed the task without prompt injection.
  3. The executor has an I220 payment grant, but no trusted approval for Supplier B and USD 100.A separate decision request reads stored grants. It changes no records. Each later application call checks its authority again.
  4. The service saves D220 and returns 403 for payment. No P220 is created and the balance stays USD 1,000.Separate POST calls recheck identity, action, resource and any required approval. Draft storage and queue acceptance have separate responses.
  5. Fresh reads find D220, no payment record and USD 1,000. I220 and the legitimate draft remain available.Independent GETs retrieve draft, invoice, queue and balance. They prove these local records only, not a bank transfer or every agent workflow.
Show as a table

The same I220 draft task and selected unintended Supplier B proposal reach three services. Draft only refuses payment; Broad tools queues it; Missing approval refuses it. Draft D220 still saves in all three. OWASP Version 2026 identifies excessive agency as LLM03:2026.

Drafting and payment need different authority in text
Stage or scopeWhat the local records supportWhat remains separate
TaskI220 is Supplier A, USD 100. Kael Cho requests a draft. The original is retained.The selected model output proposes Supplier B instead; no live model or attack success is measured.
DecisionA separate decision request checks stored grants without applying either action.Every application call must recheck identity, action, resource and any required approval.
Draft onlyAssistant identity can read I220 and save D220. Payment POST returns 403. Fresh reads find no P220 and USD 1,000.No payment credential or grant follows from a confident proposal. Ordinary drafting still works.
Broad toolsThe assistant can pay any invoice without independent approval. POST stores P220 to Supplier B and holds USD 100; fresh reads confirm those records.This is synthetic queue state, not bank settlement. No bank, mail service or real money is involved.
Missing approvalA separate executor has a grant on I220, but no matching trusted approval for B and USD 100. Payment POST returns 403; fresh reads find no P220 and USD 1,000.An unexpired approval must bind executor, invoice, payee, amount, currency and expiry. A client claim or model output is insufficient.
Scope and assumptionsTrusted identities and grant/approval storage are assumed. The HTTP/SQL fixture tests decisions, application, fresh reads and retained originals.A post-story exact Supplier A approval permits a scoped executor call; changed arguments and expired approval fail. This is not assurance for every agent workflow.

Scene checked 2026-09-30.

Use this picture in the decision check

CHECK YOUR JUDGMENT

A developer at Meridian Space & Robotics (chronos.orbital) builds an assistant that reads incoming supplier invoice emails and prepares replies. To save time, the developer gives the assistant’s tool runner a single service-account token that has permission to both save email drafts (mail:draft) and execute live wire transfers (payments:send), relying on the system prompt "Only call payments:send if the email looks completely legitimate." What is the primary architectural flaw?

NEXT FIELD LESSON

Human oversight gates and the NIST AI Risk Management Framework

↗

Find your next idea.

Tip: press / to open search. Escape closes this window.