Broken access control is a failure to enforce what a signed-in (or anonymous) user is allowed to do. The app knows who the user is, but it does not check whether that user may read, change or delete a given resource. OWASP ranks it first in its Top 10, and CWE groups it under improper access control (CWE-284), missing authorization (CWE-862) and incorrect authorization (CWE-863).
Updated October 2026
Why it matters
Access control is the line between one customer's data and another's. When it breaks, a member can see the admin billing page, a guest can download private reports, or one user can edit another user's profile. No malware and no stolen passwords are needed. The person is using the app as designed, just reaching things they should never have been shown.
The impact is usually data exposure: names, invoices, contracts, health records, internal notes. It can also be quiet data changes, which are harder to notice and harder to undo. Regulators and customers treat both as a breach.
A simple illustration
Acme Corp runs a project tool with three roles: admin, member and guest. Only admins should be able to export the full user list. The export button is hidden in the UI for members and guests, and the team assumed that was enough. The server route behind the button checks that the user is signed in, but never checks the role.
Vulnerable check:
@app.get("/api/users/export")
@login_required
def export_users():
return csv_of(User.query.all())
Corrected check:
@app.get("/api/users/export")
@login_required
def export_users():
if current_user.role != "admin":
abort(403)
return csv_of(User.query.filter_by(org_id=current_user.org_id).all())
The fix does two things: it enforces the role on the server, and it limits results to the user's own organization. Hiding a button is a UI choice, not a security control.
Why automated scanners often miss it
A traditional scanner sends inputs and looks for known error patterns. Broken access control has no signature. The vulnerable response is a normal, successful page with a 200 status. To know it is wrong, a tool needs to know that a member should not see that data. That means signing in as several roles, understanding which role owns what, and comparing results. Most scanners crawl as one user, or as none, so they never see the difference. We cover this in more depth in why scanners miss broken access control.
How to prevent it
- Deny by default. Every route should require an explicit allow rule.
- Enforce authorization on the server, never only in the UI or client code.
- Put checks in one shared layer (middleware, policy objects, or a policy engine) so new routes inherit them.
- Scope every database query to the current user or organization, not just the lookup by ID.
- Check both role (what kind of action) and ownership (which specific record).
- Log authorization failures and alert on spikes.
- Write tests that call each endpoint as each role and assert the expected allow or deny.
How to test your own app for it
Only test software you own or have written permission to test.
- List every endpoint and the roles that should reach it. Gaps in this list are findings in themselves.
- Confirm every endpoint enforces role and object ownership on the server.
- Sign in as admin, member and guest and compare what each can see and change.
- Confirm hidden UI elements are backed by server-side denials.
- Review code for routes that only use a login check and no permission check.
- Add these cases to your automated test suite so they stay fixed.
How Wakefield helps
Wakefield's blackbox agents sign in as every role your app has (admin, member, guest), map its pages and APIs, build a model of who should see what, then try to cross user, tenant and role lines. A separate verifier replays each finding with a live exploit and a negative control, so you only see bugs that reproduce. See Blackbox testing.