Broken authorization is the #1 API risk.
authzfuzzfires every identity at every request concurrently, then compares what the API allowed against what it should have — surfacing BOLA / IDOR, BFLA and broken authentication in one matrix.
You describe the access model once — who the identities are, and who legitimately owns each request — and the fuzzer does the tedious N×M cross-product for you, at speed.
| Finding | OWASP | Trigger |
|---|---|---|
| Unauthenticated access | API2 | anonymous gets a 2xx on a protected endpoint |
| BOLA / IDOR | API1 | one user reaches an object owned by another user |
| BFLA | API5 | a non-admin reaches an admin-only function |
| Over-restriction | — | a legitimate caller is wrongly denied (misconfig) |
git clone https://github.com/Sudo-Zaid/api-authz-fuzzer
cd api-authz-fuzzer
pip install -e .Write who can do what (authzfuzz template > config.json to start):
{
"base_url": "https://api.example.com",
"identities": [
{ "name": "anonymous", "headers": {} },
{ "name": "user_a", "headers": { "Authorization": "Bearer TOKEN_A" } },
{ "name": "user_b", "headers": { "Authorization": "Bearer TOKEN_B" } },
{ "name": "admin", "headers": { "Authorization": "Bearer TOKEN_ADMIN" }, "is_admin": true }
],
"requests": [
{ "name": "read profile A", "method": "GET", "path": "/api/users/1", "owner": "user_a" },
{ "name": "delete user", "method": "DELETE", "path": "/api/users/2", "admin_only": true },
{ "name": "health", "method": "GET", "path": "/health", "public": true }
]
}Tag each request with one of: owner (object belongs to that identity), admin_only, or public. Untagged requests are treated as "any authenticated user may access".
authzfuzz run --config config.json --workers 40 --rps 50 --output report.md
authzfuzz matrix --config config.json # just the raw status matrix## Access matrix
| request | anonymous | user_a | user_b | admin |
|---|---|---|---|---|
| read profile A [owner=user_a] | 200* | 200* | 200* | 200* |
| delete user [admin-only] | 401 | 200* | 200* | 200* |
| health [public] | 200* | 200* | 200* | 200* |
_`*` = HTTP 2xx (access granted)._
## Findings
### [CRITICAL] Unauthenticated access (anonymous / read profile A)
### [HIGH] Object-level authz bypass (BOLA / IDOR) (user_b / read profile A)
### [HIGH] Function-level authz bypass (BFLA) (user_a / delete user)
--workerscontrols concurrency;--rpssets a global rate cap so you stay gentle on shared environments.- Exit code is non-zero when any finding is present — drops into CI.
Sends real (including state-changing) requests. Only run against APIs you own or are explicitly authorised to test, and prefer a staging environment for the destructive verbs. See LICENSE.
MIT © 2026 Sudo-Zaid