The Authorization Bug No Scanner Understands

Authorization is a fact about who should access what — intent a pattern scanner can't see. That's why broken access control is OWASP's #1 web risk, and why an AI-written authz check that looks plausible is the dangerous case. BrassCoders draws the line.

Copper Sun Brass Team · · 4 min read
securityai-code-review

Authorization is a fact about who should access what — intent, not syntax. A pattern scanner sees the code path taken; it never sees the intent the path was supposed to enforce, which is why broken access control is OWASP’s #1 web risk and why no scanner reliably catches it. BrassCoders flags structural patterns; verifying that an access check is correct for your data model is a job that requires knowing what the code was meant to do.

The dangerous version of this bug in AI-generated code isn’t the missing check. It’s the check that’s present, looks right, and enforces the wrong thing.

Why Authorization Is a Semantic Property

BrassCoders reads structure, and an authorization rule is meaning. The line if user.id == resource.owner_id: is syntactically clean and might be exactly right — or exactly wrong, if your model allows team members to access a teammate’s resource, or if the resource has no single owner. Whether the check is correct depends on your business rules, and those rules aren’t in the code for a scanner to read.

An IDOR is this gap made concrete: an endpoint that returns object 41 when the user asks for it, without confirming the user is allowed object 41. The code that fetches the object and the code that fetches it safely look almost identical; the difference is a check that reasons about permission, and its absence leaves no structural hole a rule can match.

Why AI-Generated Code Makes This the Sharp Case

BrassCoders sees this failure mode intensify with AI-generated code, because an assistant writes authorization checks that look plausible. Asked to build an endpoint returning a user’s record, the model frequently adds a check — it knows checks belong there — but whether that check enforces your ownership model is something the model has no way to know, and it will produce a confident, well-formed version that’s subtly wrong.

A present-but-wrong check is more dangerous than a missing one. A missing check is at least a conspicuous absence a careful reviewer might notice; a check that reads if user.is_authenticated: where it needed if user.id == record.owner_id: looks like diligence. The plausible-but-incorrect authorization check is precisely what AI generation is good at producing, and it clears review because it appears to do the right thing.

Why a Pattern Scanner Can’t Verify It

BrassCoders can’t flag the wrong check for the same reason it can’t flag a logic bug: correct and incorrect authorization are structurally identical, so there’s no bad shape to match. A rule that flagged every ownership comparison would flag the right ones and the wrong ones equally, which is noise; a rule that flagged none is a scanner reporting the truth about what a pattern can see.

OWASP is explicit that this is why broken access control sits at the top of its list — it’s the class where automated detection struggles most, because the vulnerability is defined by application-specific intent rather than a recognizable pattern. No amount of rule-writing crosses that line, because the missing knowledge is your authorization model, which lives outside the code.

Where the Verification Happens

The controls that actually catch broken access control reason about intent. Threat modeling enumerates, before the code exists, who should reach each resource and under what condition. Authorization tests encode that model as assertions — the wrong user requests the object and must receive a 403. DAST and manual testing issue the unauthorized request against the running app and check whether it’s refused.

Each of those carries the one thing a scanner lacks: a statement of who’s supposed to have access. OWASP’s A01 guidance and its testing resources are the authority on how to build that verification. A scanner that claimed to confirm your authorization was correct would be asserting knowledge of an ownership model it was never given.

What BrassCoders Covers

BrassCoders catches the structural findings around access control — hardcoded credentials, insecure session handling, missing-auth patterns where they’re detectable — deterministically on every scan. Those have shapes, and AI assistants ship them at measured rates. The semantic correctness of an authorization check is the layer above, and BrassCoders names it as such rather than faking coverage of it.

The two layers are complementary. The scanner clears the structural security patterns so review isn’t buried in them; threat modeling and authorization tests verify that the checks enforce your actual model. Run the scan for the patterns, and test the authorization for the intent no pattern can encode.

pip install brasscoders
brasscoders --offline scan /path/to/your/project

The scan catches the credential and the session bug. The authorization check that’s present and wrong is the testing layer’s job, and the first step is knowing a scanner was never going to see it.

Frequently Asked Questions

Can static analysis find broken access control or IDOR?

Not the ones that matter. A scanner sees the code path; it can't see whether the authorization check is the correct one for your data model, because that's a fact about intent — who should be allowed to access what. An IDOR is a check that should exist and doesn't, or exists and is wrong. Neither has a structural marker that distinguishes it from correct code, which is why OWASP ranks broken access control as the #1 web risk.

Why does AI-generated code make authorization worse?

Because an AI assistant writes a check that looks plausible. Asked for an endpoint that returns a user's record, it often adds an authorization check — but whether that check enforces your actual ownership model, the model has no way to know. A check that's present and wrong passes review more easily than one that's missing, and it's exactly what AI generation tends to produce.

What actually catches broken access control?

Threat modeling that enumerates who should access each resource, authorization tests that assert the wrong user gets denied, and DAST or manual testing that actually issues the unauthorized request and checks the response. Each of those reasons about intent, which a pattern scanner structurally cannot.

What does BrassCoders cover around authorization?

The structural findings — hardcoded credentials, missing-auth patterns where they're detectable, insecure session handling. It does not verify that an authorization check is semantically correct for your data model, because correctness there is a question of intent. That verification is threat modeling and authorization testing, the layer above the scan.