Skip to content
Access control and identity

Access control and identity 🔐

Access control looks like a clean category until the resource type changes. On the Web you check a route; in an API you must check every object and property; in Kubernetes a machine identity; in an agent a capability that can produce further actions. Controlling “access” without saying to which resource is like promising to protect a building without saying which door you mean.

The first thing to internalize: the Web list treats access control as a single container (A01:2025, which also absorbs SSRF), the API list splits it into three granularities, and Kubernetes treats it as declarative configuration. That is not inconsistency: each domain selects the level it needs.

Same principle, different mechanisms 🧩

Domain Typical failure Category Useful control
Web Reachable administrative route A01 Server-side authorization on every request
API BOLA, mass assignment, or BFLA API1 / API3 / API5 Ownership checks and policy per object and function
Kubernetes RBAC with wildcard or cluster-admin K03 Minimal roles, distinct service accounts, audit
CI/CD Build job with production cloud credentials CICD-SEC-5 (PBAC) Permissions tied to job scope, zero standing access
Serverless Function with all-encompassing IAM role S4 Event- and resource-specific role
LLMs/agents Over-privileged tool with no confirmation LLM08 / LLM07 Restricted capabilities, approval, limits

The API breakdown is the best teaching case: API1 (BOLA) concerns the object (GET /api/v1/orders/{id} of another user), API3 (Broken Object Property Level Authorization) concerns properties — reading hidden fields and unauthorized writes via mass assignment — and API5 (BFLA) concerns functions, like a normal user invoking DELETE /api/v1/users. Three levels, three checks, three tests. The Web list folds them into A01; whoever designs APIs must be finer-grained.

Authentication and authorization: two questions, one word 💬

Authentication answers “who are you?”; authorization asks “what can you do here, now, to this resource?”. A valid JWT does not automatically authorize access to order 1234. It seems obvious until that check is scattered across five endpoints and two workers.

Beware the identity trap for agents: separate user identity, service identity, and tool identity. The agent must not inherit a super-token just because it is convenient in a demo. The same applies in pipelines: the build runner’s cloud credentials (CICD-SEC-6) must be short-lived and tied to job scope, not an admin key shared in a group vault. The Mobile Top 10 instead merges authentication and authorization into one category (M3) precisely because neither is self-managed on the client: it is the page where the “confused deputy” becomes architecture, on the opposite side from the server.

Session management no longer has its own row: it has been absorbed by identity, authorization, and secrets. Which means a session policy has to be looked for in three places, not one.

Delegation, where identity becomes curatorial 🎭

When an application authenticates through OAuth/OIDC, who is really acting? The classic problem is the confused deputy: a user’s token is issued to a service that holds more powers than the user, and the next request starts with the service’s privileges. The same goes for agents: if the agent “acting on the user’s behalf” travels with a richer service token, every downstream abuse is not attributable to the user — it is signed by the service.

The operational rule for delegation has three parts: minimal issuance (narrow scopes, not random openid profile email), audience matching (the token says for whom it was issued and for which audience), and invoker awareness (the agent mediates, it does not replace the user). In pipelines the same trio is called federated OIDC: the build runner obtains short-lived cloud credentials tied to job scope, not a static key with production write access.

Controls that survive domain changes 🛡️

Four invariants span Web, APIs, Kubernetes, pipelines, and agents:

  • Least privilege and zero standing access for human and machine identities: if an identity does not routinely use a privilege, it should not have it.
  • Decision on the server or control plane, never on the client, the unvalidated manifest, or a tool that grants itself permission.
  • Deny by default: the unauthorized action is denied, not “allowed unless someone else checks”.
  • Audit of decisions: logs of who granted what, to whom, and why — the basis for role rotation.

Test the negative cases: users in the same tenant, lower roles, expired tokens, and tools invoked outside the expected path. If your authorization suite contains not a single “must fail” test, you are testing the happy path while pretending to do security.

An anti-pattern to recognize early: cluster authorization, where a middleware checks “are you authenticated?” and then five handlers are left to remember (not always) to verify resource ownership. It works while the demo runs on two endpoints; it collapses when a new endpoint copies the neighboring handler without checking its details. The rule is not “add a check everywhere”: it is centralizing the decision (a policy per function or a function per object) and making it impossible to bypass by omission. If the control lives only in a method you “hope gets called”, it is not a control: it is a wish.

Checklist ✅

  • Authorization close to the resource, not only the frontend.
  • Least privilege and dedicated identities for workloads, pipelines, and agents.
  • Object-, property-, and function-level checks where the architecture requires them.
  • Denied-access tests included in the suite.
  • Decision logs and periodic role review.
  • Separate identity for user, service, and tool in agents.

Access control is the control you do not see when it works. When you design it badly, it is the first one you see.

Read the matrix and integrate this analysis into threat modeling.

Last updated on