TL;DR
Mass assignment happens when you pass user input directly to database operations without filtering. Attackers add extra fields like "isAdmin: true" to their request and your code saves it. Always explicitly pick which fields to accept from user input, never spread the entire request body.
How Mass Assignment Works
Vulnerable: spreading request body
// User registration endpoint
app.post('/api/register', async (req, res) => {
// VULNERABLE: saves whatever user sends
const user = await User.create(req.body);
res.json(user);
});
// Normal request:
// { "email": "user@example.com", "password": "secret" }
// Attack request:
// { "email": "user@example.com", "password": "secret", "role": "admin" }
Common Dangerous Fields
role,isAdmin,permissionsverified,emailVerifiedbalance,creditspassword(when updating profile)id,userId(changing ownership)
How to Prevent It
Safe: explicit field picking
app.post('/api/register', async (req, res) => {
// Only pick allowed fields
const { email, password, name } = req.body;
const user = await User.create({
email,
password,
name,
role: 'user', // Always set defaults server-side
verified: false
});
res.json(user);
});
// Or use a validation library
import { z } from 'zod';
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string()
}).strict(); // Reject extra fields
app.post('/api/register', async (req, res) => {
const data = registerSchema.parse(req.body);
const user = await User.create(data);
});
Isn't this just bad input validation?
Yes, mass assignment is a specific type of input validation failure. It is common enough to have its own name because ORMs and frameworks make it easy to accidentally allow it.
Does TypeScript prevent this?
No. TypeScript types are compile-time only. At runtime, req.body can contain anything. You need runtime validation with libraries like Zod, Yup, or Joi.