Access Control Vulnerabilities
Broken authorization at the application layer — IDOR, object- and function-level access control, path traversal, open redirect, business-logic and TOCTOU flaws, multi-tenant isolation.
13 questions
JuniorTheoryVery commonWhat is an insecure direct object reference (IDOR), and why are sequential ids a tell?
What is an insecure direct object reference (IDOR), and why are sequential ids a tell?
An IDOR is a request naming an object that the server returns without checking the caller may see it. Sequential ids are a tell — the next value is guessable. The defect is the missing ownership check.
Common mistakes
- ✗Believing random or opaque ids are themselves an access-control mechanism
- ✗Treating authentication as sufficient and skipping the per-object check
- ✗Assuming the flaw is only real when identifiers are sequential
Follow-up questions
- →Which HTTP status should a denied object fetch return, and why?
- →How would you write an automated test proving the ownership check runs?
JuniorTheoryCommonWhere should an authorization check live — gateway, service or object — and what is deny-by-default?
Where should an authorization check live — gateway, service or object — and what is deny-by-default?
A gateway enforces coarse rules, but only the service that loads the object knows who owns it, so the per-object decision belongs there. Deny-by-default means access is refused unless a rule grants it.
Common mistakes
- ✗Assuming a gateway rule can express per-object ownership
- ✗Scattering ad-hoc checks in handlers with no default posture
- ✗Reading deny-by-default as merely a logging or auditing setting
Follow-up questions
- →What happens when a new endpoint ships without an authorization annotation?
- →How do you test that the default posture is actually deny?
JuniorTheoryCommonWhat is broken function-level authorization, and how does forced browsing expose admin routes?
What is broken function-level authorization, and how does forced browsing expose admin routes?
It is a route performing a privileged action, checking only that the caller is logged in, not their role. Forced browsing requests it directly, past the interface. Hiding a menu item is not a control.
Common mistakes
- ✗Confusing authentication with authorization on privileged routes
- ✗Treating an unlinked or obscure path as an access control
- ✗Checking the role in the interface layer rather than in the handler
Follow-up questions
- →How would you inventory every route in a codebase during a security review?
- →Why do older API versions often keep unprotected admin handlers alive?
JuniorTheoryCommonWhat is an open redirect, and why is it dangerous for phishing and for authorization framework OAuth2?
What is an open redirect, and why is it dangerous for phishing and for authorization framework OAuth2?
An open redirect sends the browser to a target from the request, unvalidated. It lends your domain to a phishing link and in OAuth2 can leak an authorization code. Validate targets against an allowlist.
Common mistakes
- ✗Validating the target with a substring or prefix match instead of an exact allowlist
- ✗Treating an open redirect as cosmetic because the user can read the final URL
- ✗Forgetting that
redirect_urihandling in OAuth2 is the same failure
Follow-up questions
- →Why is a prefix match on the host name a weak allowlist?
- →How does mapping a short key to a URL server-side remove the flaw?
MiddleTheoryCommonWhat is broken object-level authorization (BOLA), and why must you test every HTTP verb?
What is broken object-level authorization (BOLA), and why must you test every HTTP verb?
BOLA is the API form of IDOR — the handler acts on an object id without confirming the caller owns it. Teams guard GET but forget PUT and DELETE, so a test replays it on every verb from a second account.
Common mistakes
- ✗Testing only the read path and assuming writes inherit the same policy
- ✗Trusting an owner id embedded in the URL as proof of ownership
- ✗Confusing rate limiting with an authorization control
Follow-up questions
- →Why is 404 sometimes preferred over 403 on a denied object?
- →How do you keep such a test suite honest as new endpoints are added?
MiddleTheoryCommonWhy is a UUID not authorization, and what does a per-request ownership check look like?
Why is a UUID not authorization, and what does a per-request ownership check look like?
A UUID only makes an id hard to guess; anyone who saw one — in a log, a referrer or a link — can still replay it. The control is a per-request check comparing the object owner with the authenticated subject.
Common mistakes
- ✗Treating unguessable identifiers as an access-control mechanism
- ✗Trusting an owner id taken from the request instead of the session
- ✗Checking ownership once at login rather than on every request
Follow-up questions
- →Where do object identifiers commonly leak outside the application?
- →How does scoping the query by owner in SQL shrink the surface?
JuniorTheoryOccasionalWhat is path traversal, and how do canonicalization and a base-directory check stop it?
What is path traversal, and how do canonicalization and a base-directory check stop it?
Path traversal is user input reaching a file path, so .. segments escape the intended folder. Resolve the path to canonical absolute form first, then verify it still sits inside the allowed base directory.
Common mistakes
- ✗Blacklisting
..by string match instead of resolving the canonical path - ✗Checking the path before resolution, so encoded traversal slips through
- ✗Relying on process permissions instead of an explicit base-directory check
Follow-up questions
- →Why is an allowlist of file names stronger than sanitising the input?
- →How does serving files by opaque id remove the traversal surface entirely?
MiddleTheoryOccasionalWhy do negative amounts, coupon reuse and quantity underflow pass validation, and what stops them?
Why do negative amounts, coupon reuse and quantity underflow pass validation, and what stops them?
Each request is well-formed, so schema validation passes; the rule broken is a business invariant, not a format. Enforce it server-side where state changes — bounds on amount and quantity, atomic redemption.
Common mistakes
- ✗Assuming schema validation also covers business invariants
- ✗Checking the invariant on read but not where state changes
- ✗Expecting a firewall to infer application-specific rules
Follow-up questions
- →Why must a single-use redemption be recorded atomically?
- →How do you express these invariants as tests rather than review comments?
MiddleDebuggingOccasionalAn access log shows one session reading three invoice ids in a row — find and fix the handler defect
An access log shows one session reading three invoice ids in a row — find and fix the handler defect
The handler authenticates but never authorizes — it loads the invoice by id, so any logged-in caller reads another account's record. Scope the lookup to the authenticated owner and answer 404 on a mismatch.
Open full question →Common mistakes
- ✗Reading
requireLoginas an authorization check rather than authentication - ✗Fixing the identifier format instead of adding the ownership constraint
- ✗Answering 403 on a denied object, confirming that it exists
Follow-up questions
- →Why is scoping the query safer than loading first and comparing afterwards?
- →What test would prove this route is closed for a second account?
SeniorTheoryOccasionalHow does mass assignment escalate a role, and why is allowlist binding the fix?
How does mass assignment escalate a role, and why is allowlist binding the fix?
A handler binds the whole request body onto a domain object, so an extra field the form never showed — role, isAdmin — is written straight to storage. Bind an explicit allowlist of editable fields instead.
Common mistakes
- ✗Using a denylist that silently misses newly added fields
- ✗Assuming a field the interface never renders cannot be submitted
- ✗Binding request bodies directly onto persistence entities
Follow-up questions
- →Why does a separate input type beat annotating the persistence entity?
- →How would a test catch a newly added privileged column?
SeniorTheoryOccasionalHow does a time-of-check to time-of-use (TOCTOU) race drain a balance, and what closes it?
How does a time-of-check to time-of-use (TOCTOU) race drain a balance, and what closes it?
The balance is read, judged sufficient, then debited as a separate step; concurrent requests all pass the check against one stale value. Make check and debit one atomic operation — a conditional update or row lock.
Common mistakes
- ✗Treating the window as a latency problem rather than an atomicity one
- ✗Believing an idempotency key alone prevents distinct concurrent debits
- ✗Adding a client-side guard that a direct request simply ignores
Follow-up questions
- →How does a conditional update differ from re-reading inside a transaction?
- →What would you log to detect this race after the fact?
SeniorDesignRareYou inherit a platform of 40 services where every handler makes its own authorization decision in inline code. Two incidents this quarter came from a new endpoint shipping with no check at all, and audit cannot answer who may read a given record. Design a centralized authorization scheme — a policy decision point the services consult, with rules expressed as data instead of scattered conditionals. Constraints — no request may gain more than 10 ms; the platform must keep serving if the policy service is briefly unreachable; per-object ownership still depends on data only the owning service holds; roughly 300 endpoints must migrate without a release freeze; auditors need a durable record of every decision. Describe where the decision point sits, how policies are expressed and distributed, how deny-by-default is guaranteed for new endpoints, how object-level data reaches the decision, and how you migrate incrementally and prove coverage.
You inherit a platform of 40 services where every handler makes its own authorization decision in inline code. Two incidents this quarter came from a new endpoint shipping with no check at all, and audit cannot answer who may read a given record. Design a centralized authorization scheme — a policy decision point the services consult, with rules expressed as data instead of scattered conditionals. Constraints — no request may gain more than 10 ms; the platform must keep serving if the policy service is briefly unreachable; per-object ownership still depends on data only the owning service holds; roughly 300 endpoints must migrate without a release freeze; auditors need a durable record of every decision. Describe where the decision point sits, how policies are expressed and distributed, how deny-by-default is guaranteed for new endpoints, how object-level data reaches the decision, and how you migrate incrementally and prove coverage.
Separate decision from enforcement — services embed an enforcement point that asks a decision point about subject, action and resource. Policies ship as versioned, locally cached data; unannotated endpoints are denied.
Common mistakes
- ✗Assuming a gateway can resolve per-object ownership it cannot see
- ✗Centralizing policy without a default-deny posture for new endpoints
- ✗Making the decision point a hard runtime dependency with no local cache
Follow-up questions
- →How do you prove that all 300 endpoints are actually covered?
- →When should the platform fail open rather than fail closed?
SeniorTheoryRareIn a multi-tenant system, why must the tenant id come from the token rather than the request?
In a multi-tenant system, why must the tenant id come from the token rather than the request?
Anything the caller sends can be altered, so a tenant id in a path, body or header is attacker-chosen. Derive it from the verified token and bind it at the persistence layer so every query is scoped automatically.
Common mistakes
- ✗Trusting a tenant id from the path, body or Host header
- ✗Scoping queries per call site instead of centrally
- ✗Silently correcting a mismatch instead of denying the request
Follow-up questions
- →How does row-level security in the database complement application scoping?
- →What test proves a cross-tenant read is impossible?