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.