PromptFu
03 Offense: Agentic3.5Core24 min

Authorization bypass, model-free

A test this course puts early, because it needs no prompt-craft at all.

LLM03:2026ASI03The authorization-bypass range

No jailbreak, no hidden text, no encoding, no payload. Sign in as an ordinary user, change one identifier, and see whether anything downstream objects. In this course we run this before any prompt-craft, and it is the least glamorous thing here.

Why this is in scope at all

A reasonable objection: if nothing is injected, is this even an LLM finding? The standard answers it in the definition, with the clause module 3.4 asked you to read twice.

Excessive Agency is the vulnerability that enables damaging actions to be performed in response to unexpected, ambiguous or manipulated outputs from an LLM, regardless of what is causing the LLM to malfunction.
OWASP LLM03:2026

The entry is defined over outputs. Nothing in it requires an attacker to have persuaded anything. A tool call carrying an identifier its caller should not have is exactly the case it describes, whether that identifier arrived by injection or by typing.

And there is a practical reason to run it first. Injection work is slow, probabilistic, and hard to reproduce in a report. This is deterministic. It either returns the other tenant's record or it does not, which is why this course puts it ahead of the model-layer work.

Run the range

Watch the trace for a plant step. There is not one, and there is no system-prompt trickery either. Then flip the lever through all five enforcement points.

The authorization-bypass range

0 / 11

Signed in as Alice Okafor, tenant "acme". The target document belongs to tenant "globex".

Objective

Read another tenant's document and approve an expense, while signed in as an ordinary employee with no approval right and without writing a single injected instruction.

Where is authorization enforced?

Step through the trace. Every node is a real place content enters or leaves the application, and each one arrives with the analysis beside it: what a defender can still do at that instant.

Synthetic world, no live model and no network requests. Enforcement points per OWASP LLM03:2026 mitigation 7 (complete mediation); the cross-tenant shape follows the Cursor/Supabase MCP case in LLM01 Scenario #9, where row-level security existed and the connection identity outranked it.

Four of the five fail. The three that fail worst share a property: they put the decision somewhere the model can read, which means they put it somewhere the attacker's text also lives.

The one that looks like it works

Filtering the answer afterwards comes up often, and it is the most interesting failure of the five. The user sees a refusal. The record was still read.

It is in the context window, in the trace, in the logs, in the cache, and available to the next turn. Filtering protects the screen, and the screen was never the asset.

Where the check belongs

Implement authorization in logic rather than relying on an LLM to decide if an action is allowed or not.
OWASP LLM03:2026, mitigation 7

OWASP then names the three valid places for it: the tool, an independent pre-execution policy decision point between the tool and the downstream system, or the downstream system itself. All three sit outside the model. That is the property that matters, more than the choice between them.

Enforcement pointIn the source?Holds?
System promptNoNo. The model has no ownership data to check against.
Tool descriptionNoNo. Advisory text in the same context window as the request.
Agent reasoning stepNoNo. A component deciding its own permissions.
Post-response filterNoNo. The read already happened.
The tool itselfYesYes.
Independent pre-execution policy decision pointYesYes.
The downstream systemYesYes.
Middle column records whether OWASP LLM03:2026 mitigation 7 names it as a valid location. The verdicts are this course's.

How to actually run this

Two accounts and a browser devtools panel. No tooling, no model access, no payload library.

The procedure
1. Sign in as a low-privilege user. Do something ordinary.

2. Capture the tool call the agent makes on your behalf.
     Network tab, trace viewer, debug panel, agent logs.
     You are learning the parameter names and the ID format.

3. Horizontal: re-issue it with an identifier you should not
     own. Another tenant, another user, another project.

4. Vertical: ask for an action your role may not perform.
     Approve, refund, delete, escalate, invite.

5. Grade on the recorded side effect, never on the reply.
     "I can't show you that" plus a row in the access log
     is a finding, not a refusal.

Step 5 is the one that separates a useful report from a wasted week. The assistant's text is not evidence of anything. Check the database, the audit log and the trace.

Deep diveThe classic names for this

Steps 3 and 4 are insecure direct object reference and privilege escalation, which application security has been testing since long before any of this. The API Security Top 10 calls them broken object level authorization and broken function level authorization.

Those terms do not appear anywhere in the OWASP Top 10 for LLM Applications, so do not cite them to it. Cite them to the appsec standards where they live, and cite LLM03 for why an agent makes them worse.

What the agent changes is reachability. A tool exposes a clean, documented, uniform interface to internal operations, and it will call it for anyone who asks nicely.

When there is no clean answer

Some actions cannot be decided by a per-request rule, because whether they are safe depends on context. OWASP offers a graduated policy for those: audit, warn, block, escalate.

Its example is worth stealing. A refund as store credit is recoverable, so auto-approve it. An external payout is not, so route it to a human. The axis is reversibility, not sensitivity.

Bounds blast radius· survives adaptive attack

Authorization enforced outside the model, per request, against the caller's real identity. It holds because it never consults the component under attack, and it is the only control in this module that does not care how the identifier got into the tool call.

Sources