Back to Cheat Sheets

🤖 Excessive AgencyLLM06

OWASP LLM Top 10 2025 · LLM06

HIGH RISK

📋 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!

❌ 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.