📋 What Is It?
Excessive Agency is the harm that follows when an LLM system is granted too much ability to act - too many tools, too much permission, or too much autonomy - so that an unexpected, ambiguous, or adversarially manipulated model output triggers a damaging real-world action: an email sent, a row deleted, a payment made. Once a model is wired to tools, its words become actions. It is a design and architecture flaw in the agentic scaffolding, not a flaw inside the model weights - a perfectly benign model can still cause catastrophic damage.
LLM06
OWASP Rank 2025
3 Roots
Functionality · Permissions · Autonomy
Agentic
Tools · Plugins · Multi-agent
🌱 The Three Roots
- Excessive Functionality: the agent can reach tools/capabilities it does not need - every extra tool is extra attack surface.
- Excessive Permissions: tools are scoped far broader than the task (UPDATE/DELETE/DROP when SELECT would do; shared high-privilege service accounts).
- Excessive Autonomy: high-impact, irreversible actions execute with no human confirmation or independent authz check.
⚠️ Top Attack Vectors
- Indirect prompt injection → tool abuse: instructions hidden in a page/doc/email the agent reads hijack its tools.
- Ambiguous output → destructive op: "clean up duplicates" resolves to
delete(filter="*"). - Tool chaining & cascade: search → read secret → HTTP exfiltrate; each call "allowed", the composition is the exploit.
- Over-scoped tools: a "lookup" tool that runs free-form SQL reaches any table.
- Confused deputy / shared creds: agent acts as one privileged identity; per-user authz evaporates.
- Argument injection: attacker text flows into a path, shell fragment, or URL (SSRF).
🔴 Attack Flow
1. Attacker plants text in content the agent reads (page/doc/email/tool result)
↓
2. Text enters the model's context (LLM01 trigger)
↓
3. Model emits tool_call{ delete_user, {id: 42} }
↓
4. No scope check? No approval gate? No downstream authz?
↓
5. IMPACT: Real, often irreversible action executes!
↓
2. Text enters the model's context (LLM01 trigger)
↓
3. Model emits tool_call{ delete_user, {id: 42} }
↓
4. No scope check? No approval gate? No downstream authz?
↓
5. IMPACT: Real, often irreversible action executes!
❌ Vulnerable Code
# A "lookup" tool that is really arbitrary SQL over an admin connection
conn = psycopg2.connect("postgres://admin:secret@db/app") # full-privilege
@tool
def query_database(sql: str) -> str:
"""Run a SQL query to answer the user's question."""
with conn.cursor() as cur:
cur.execute(sql) # ← ANY statement: SELECT, UPDATE, DROP…
return str(cur.fetchall())
# Injected input → query_database("DELETE FROM orders") irreversible, no gate
✅ Secure Code
# One narrow capability, read-only role, parameterised, bounded
ro_conn = psycopg2.connect("postgres://agent_ro:pw@db/app") # SELECT only
class OrderStatusArgs(BaseModel):
order_id: int = Field(gt=0) # typed, validated - not free text
@tool(args_schema=OrderStatusArgs)
def get_order_status(order_id: int) -> str:
with ro_conn.cursor() as cur:
cur.execute(
"SELECT status FROM orders WHERE id = %s LIMIT 1", # parameterised
(order_id,),
)
row = cur.fetchone()
return row[0] if row else "not found"
# No other tables, no writes, no arbitrary SQL - the vector is gone.
✓ Prevention Checklist
- Minimise functionality - explicit, reviewed tool allow-list; no debug/left-over tools
- Least-privilege scope each tool (read vs write vs delete)
- Propagate the end-user's identity; no shared admin creds
- Enforce authorisation downstream, in code - not the model's say-so
- Human-in-the-loop for high-impact / irreversible actions
- Complete mediation: every call through one central policy gate
- Validate/constrain arguments; no model text to shell, eval, or raw SQL
- Rate-limit, budget, sandbox to bound the blast radius
- Fail safe (default-deny, stop and ask); prefer reversible ops
- Log every invocation with the real user; alert on anomalies
🧰 Tools & Takeaway
Pydantic
Zod
OAuth scopes
Least-priv DB roles
Approval gate
Egress allow-list
Audit log
💡 Takeaway: The model proposes, code disposes. Authorisation must live outside the model, at the tool boundary - the security question is never "did the model say the right thing?" but "should THIS call, with THESE args, in THIS context, run at all?"
⚠️ Compounds with: LLM01 Prompt Injection (the trigger) and LLM05 Improper Output Handling (the hand-off). Fixing one without the others leaves the system exposed.