Mass Assignment
The framework binds every field in the request body to a model attribute. Send role=admin in the JSON payload and the model writes it — no explicit code required.
The framework binds every field in the request body to a model attribute. Send role=admin in the JSON payload and the model writes it — no explicit code required.
Mass assignment (also known as autobinding) is a vulnerability where an attacker adds unexpected fields to a request payload, and the server blindly binds those fields to a database model or internal object. Frameworks that automatically map request body properties to model attributes make this attack trivial.
role=admin or is_admin=true to a request body modifies protected fields.Consider a user profile editor that expects name, email, and bio fields. A typical implementation using an ORM might look like User.update(req.body). If the database model also has a role or is_admin column, an attacker can add "role": "admin" to the JSON payload and gain elevated privileges.
Mass assignment is especially dangerous because the attack surface is invisible — the developer only lists the intended fields in their code or UI, but the server accepts everything the client sends. Any column in the database that the developer forgot to protect becomes a potential escalation vector.
The sandbox below simulates an admin system's profile editor. You can edit name, email, and bio fields. Try adding role=admin or is_admin=true to the JSON body. Toggle safe mode to see how an allowlist approach silently ignores unexpected fields.
In 2012, a security researcher discovered that GitHub's public key upload endpoint was vulnerable to mass assignment. The endpoint allowed users to add SSH keys to their account. By adding extra fields to the JSON payload, an attacker could associate a public key with anyGitHub user account, effectively gaining push access to that user's repositories.
The vulnerability existed because the Rails model used attributes=params[:key] without an allowlist. An extra user_idfield in the request body would bind to any account. GitHub patched it by switching to strong parameters — an allowlist of permitted fields. The incident led to widespread adoption of Rails' strong_parameters pattern across the Ruby on Rails community.
The defence is an explicit allowlist of fields that the endpoint will accept. Any field not in the list must be silently ignored or rejected. Most modern frameworks provide built-in mechanisms for this — Rails strong parameters, Express express-validator schemas, or dedicated Data Transfer Object (DTO) classes.
// VULNERABLE - accepts every field in the body
app.patch('/api/profile', async (req, res) => {
// req.body might include role, is_admin, user_id...
const user = await db.query(
'UPDATE users SET name = $1, email = $2, bio = $3, role = $4 WHERE id = $5',
[req.body.name, req.body.email, req.body.bio, req.body.role ?? 'user', req.session.userId],
);
res.json({ ok: true });
});
// SAFE - allowlist approach
const ALLOWED_FIELDS = ['name', 'email', 'bio'] as const;
app.patch('/api/profile', async (req, res) => {
const updates: Record<string, unknown> = {};
for (const field of ALLOWED_FIELDS) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
// role and is_admin never reach the database
await db.query(
'UPDATE users SET name = $1, email = $2, bio = $3 WHERE id = $4',
[updates.name ?? '', updates.email ?? '', updates.bio ?? '', req.session.userId],
);
res.json({ ok: true });
});
// SAFER - DTO pattern
class UpdateProfileDto {
readonly name!: string;
readonly email!: string;
readonly bio?: string;
}
app.patch('/api/profile', async (req, res) => {
const dto = validateDto(UpdateProfileDto, req.body);
// dto only contains the declared fields
await userService.updateProfile(req.session.userId, dto);
res.json({ ok: true });
});Never use generic update methods like Model.update(req.body) or Object.assign patterns without filtering. Audit your API for endpoints that accept object spreads (...) or pass the entire body to an ORM method. Each such endpoint is a potential mass assignment vector.
Model.update(req.body) or object spreads for database writes.1.A Node.js endpoint does: const user = await User.findByIdAndUpdate(req.params.id, req.body). Why is this dangerous?
2.What is the recommended defence against mass assignment?
3.Why is mass assignment hard to detect during development?