Mass Assignment (Autobinding)
Also known as: Mass assignment, Autobinding, Over-posting, Object injection via binding
A flaw in which an endpoint binds the whole request body onto an internal object, so a client can set fields it should never control, such as a role, a verified flag, a balance or an owner id.
How it works
Mass assignment happens when a framework or helper copies every property of an incoming request onto an internal object in one step. It is convenient, because a form with ten fields maps to a model with ten columns without ten lines of code. The danger is that the request, not the developer, decides which properties are set.
Models usually hold more than the user interface exposes: a role, an administrator flag, a verification status, an account balance, the id of the owning user. If the handler binds the whole body, a client that adds one of those names to the JSON can change it, even though no screen ever offered the control. The flaw is an authorization and design error, not an input-sanitization one, because every value may be perfectly well formed.
The fix is to make the server, not the client, decide what can be written. Bind only an explicit allowlist of fields through a data transfer object or a pick helper, set privileged fields in server code from trusted context such as the session, and use a separate input schema for each action so that a profile update and an admin edit cannot share one shape.
Blocklists that remove a few known dangerous names are fragile: a new sensitive column is added later and nobody updates the list. Allowlists fail safe. Pair them with validation of types and ranges, and audit changes to privileged fields so that an unexpected write is noticed.
Walk through it
- 1Read the update handler
- 2Name the dangerous field
- 3Choose the fix
- Set privileged fields on the server
- Verify the fix and add monitoring
A pre-release review flags the profile settings endpoint. The form lets a member change a display name and a phone number. Read the handler as a reviewer and note what decides which fields get written.
1// PATCH /profile (member edits their own profile)2router.patch("/profile", requireSession, async (req, res) => {3 // vulnerable: the whole body is bound onto the model4 const user = await User.update(req.session.user.id, req.body);5 res.json(user);6});7 8// models/user.ts9// fields: id, displayName, phone, email, role, isVerified, pointsBalanceSpot it
- Requests to a self-service endpoint carrying properties that no client form sends, such as role, isAdmin, isVerified, balance or ownerId.
- Changes to privileged fields made through an endpoint that is not an administrative one.
- A user whose role or balance changed with no matching action in an approval or admin workflow.
- Code review hits: a request body passed whole to an update, create or merge call on a model.
- Responses that echo back privileged fields the caller never legitimately supplied.
Application audit log
ts=2026-10-11T10:02:11Z event=user.update actor=u_2207 route=PATCH:/profile changed=displayName,role old_role=member new_role=admin
ts=2026-10-11T10:05:40Z event=user.update actor=u_2207 route=PATCH:/profile changed=phoneAPI gateway
ts=2026-10-11T10:02:11Z route=PATCH:/profile status=200 body_keys=displayName,phone,role unexpected_keys=rolesplSplunk: privileged fields changed through a self-service route
index=app sourcetype=audit event=user.update route="PATCH:/profile"
| where match(changed, "(role|isAdmin|isVerified|balance|ownerId)")
| stats count by actor, changedExpect legitimate changes from the admin console; scope the alert to routes meant for members.
grepgrep: unexpected keys reported by the gateway
grep 'route=PATCH:/profile' gateway.log | grep -E 'unexpected_keys=(role|isAdmin|isVerified|balance|ownerId)'Stop it
Bind only an explicit allowlist of fields
Define, per action, the exact properties a client may set and copy only those from the request, using a data transfer object, a schema or a pick helper. Anything not named is ignored or rejected.
Never take authorization or ownership fields from input
Roles, administrator and verification flags, balances and owner ids are set by server code from trusted context such as the session or an approval step, never from the request body.
Use separate input schemas per action and validate
A profile update, a registration and an admin edit each get their own schema, so one shape cannot be reused to write another action's fields. Validate types and ranges, and reject unknown properties where the framework allows.
Bind an allowlist, not the whole body
Vulnerable
router.patch("/profile", requireSession, async (req, res) => {
const user = await User.update(req.session.user.id, req.body);
res.json(user);
});Hardened
const ProfileInput = z.object({
displayName: z.string().min(1).max(60),
phone: z.string().max(20),
}).strict();
router.patch("/profile", requireSession, async (req, res) => {
const input = ProfileInput.parse(req.body); // unknown keys such as role are rejected
const user = await User.update(req.session.user.id, input);
res.json(toPublicProfile(user));
});Use your framework's own DTO or strong-parameters feature where one exists; the sketch shows the shape.
- Prefer allowlists over blocklists for every create and update path.
- Reject, rather than silently drop, unknown properties on sensitive routes so attempts are visible.
- Keep authorization fields out of the public input types and out of serialized responses.
- Write privileged fields only from server-side code paths that perform their own authorization check.
- Audit every change to role, verification, balance and ownership fields.
- Add review and test checklist items: no whole-body binding on a model that has sensitive columns.
If it already happened
Ship the allowlist fix or disable the affected route, and freeze privileged changes made through it while you investigate.
Use the audit log to find accounts whose role, balance, verification or ownership changed through a self-service route, and restore them to their correct values.
Revoke sessions and tokens of any account that gained elevated access, confirm the fix with a test that posts an extra privileged property, and monitor for rejected unknown keys.
Make allowlist binding the framework default, add a code-review rule against whole-body binding, and keep the audit alerting on privileged fields.
Check yourself
1. What makes a mass assignment flaw possible?
2. Which remediation is the strongest default for mass assignment?
3. Which OWASP and CWE identifiers are most closely tied to this weakness?