Broken Object Property Level Authorization: Mass Assignment and Its Twin
The 2023 OWASP API list folded two well-known vulnerabilities into one category, and the merger makes sense once you see the shared root. Broken Object Property Level Authorization is what happens when an API enforces access at the level of the whole object but not the individual properties inside it — reading fields a user shouldn't see, or writing fields a user shouldn't set.
API3 combines excessive data exposure and mass assignment. They look like opposites — one is about output, the other about input — but both stem from the same shortcut: serialising or binding an entire object without deciding, property by property, what this particular user is allowed to read or change.
Excessive data exposure: returning too much
The classic pattern is an endpoint that fetches a record and returns it whole, trusting the client to display only the relevant fields. A user-profile endpoint returns the profile — including password_hash, is_admin, internal risk scores, or another user's email — because the developer relied on the frontend to hide them. The data is right there in the JSON; hiding it in the UI hides nothing from anyone reading the response.
Mass assignment: accepting too much
Mass assignment is the mirror image. Many frameworks bind an incoming JSON body straight onto a model object, so PATCH /api/users/me with {"name":"Ada"} updates the name. But if the code binds the whole body, an attacker can add {"is_admin": true} or {"balance": 999999} and the framework will dutifully write those properties too — privilege escalation delivered by a field the API never intended to expose for writing.
Closing both halves
- Never serialise objects wholesale. Return an explicit response schema — an allow-list of fields — rather than the raw database model. If a property isn't named, it doesn't ship.
- Bind inputs to an allow-list too. Accept only the properties a given operation is meant to change; reject or ignore everything else. Never bind a request body directly onto a persistence model.
- Authorise sensitive properties individually. Fields like
role,is_admin, orbalanceneed their own checks — whether the caller may read them and whether they may write them are separate questions. - Use schema validation at the boundary. A strict request and response contract, enforced automatically, turns property-level authorization from a per-developer habit into an invariant.
- Test with extra fields. Replay requests with unexpected properties added and confirm they are rejected, not silently persisted.
API3 is a discipline of specificity: every object crossing the boundary should carry only the properties this user is entitled to, in whichever direction it travels. Generic serialisation and generic binding are convenient precisely because they don't ask the property-level question — which is why the answer defaults to "everything". Knowing which endpoints hand back or accept whole objects is the first step, and you can't audit fields on endpoints you haven't mapped.
- Read one object back and scan the JSON for fields the caller should never see — internal flags, other users' data,
roleorisAdmin. Over-exposed properties are half the bug. - On an update call, add a property the client shouldn't control (such as
"role":"admin") and see if the server accepts it. Silent acceptance is mass assignment.
The full, at-scale version — auditing every response and writable field across the whole API surface for property-level authz gaps — is what our assessment (and SecStudio agents) runs for you.