Mass Assignment: When Users Modify Fields They Shouldn't
AI tools generate clean-looking code that lets attackers modify any field
Mass assignment happens when your API accepts all fields from a request and updates the database without checking which fields users are allowed to modify. Attackers add fields like isAdmin: true to gain elevated privileges. Always use allowlists for updateable fields.
What is Mass Assignment?
Mass assignment (CWE-915) happens when your server blindly saves all fields from a request to the database. Users send extra fields they shouldn't be able to modify, and your server accepts them.
Think of it like an employment form where applicants can write in their own salary. The form accepts whatever they write because nobody checked which fields employees are allowed to fill in.
This vulnerability is so common that it was API6:2019 in OWASP's original API Security Top 10, and has been merged into API3:2023 (Broken Object Property Level Authorization) in the updated list.
Why This Is Devastating
Privilege Escalation
Attackers add isAdmin: true or role: "superuser" to become administrators.
Financial Manipulation
Modify balance, credits, or price fields to steal money or get free services.
Data Corruption
Overwrite createdAt, ownerId, or other system fields to manipulate records.
Account Takeover
Change email without verification, or set emailVerified: true to bypass confirmation.
Real-world example: In 2012, a mass assignment vulnerability in GitHub allowed users to push to any repository by adding their public key to any user's authorized keys list (CVE-2012-2054).
Why AI Tools Generate This Pattern
When you ask AI tools to "create a user update endpoint," they generate the cleanest, most readable code. And in JavaScript, that means Object.assign() or the spread operator. The code looks modern and professional - that's the trap.
// Clean, modern, vulnerable code
app.put('/api/user/:id', async (req, res) => {
const user = await User.findById(req.params.id)
Object.assign(user, req.body) // "Clean" code
await user.save()
res.json(user)
})The AI optimizes for readability and brevity. Security requires extra code that the AI won't add unless you specifically ask. This is classic vibe coding risk - the generated code works perfectly until someone malicious tests it.
Common vibe coded patterns with mass assignment:
- User profile updates - "Let users update their profile"
- Settings pages - "Add user preferences"
- Admin panels - "Create CRUD for users"
- API endpoints - "Build REST API for posts"
Vulnerable Code Examples
Pattern 1: Object.assign (AI Default)
// AI generates this for "update user profile"
app.put('/api/user/:id', async (req, res) => {
const user = await User.findById(req.params.id)
Object.assign(user, req.body) // DANGEROUS!
await user.save()
res.json(user)
})
// Attacker sends:
// { "name": "John", "isAdmin": true, "role": "superuser" }
// All fields get saved, including isAdmin!Object.assign copies ALL properties from request body. Attacker can modify any field in the schema.
Pattern 2: Spread Operator
// Spread operator is just as dangerous
app.put('/api/user/:id', async (req, res) => {
const user = await User.findById(req.params.id)
const updated = { ...user.toObject(), ...req.body } // DANGEROUS!
await User.findByIdAndUpdate(req.params.id, updated)
res.json(updated)
})
// Same attack vector as Object.assignThe spread operator (...) merges objects the same way. No protection against extra fields.
Pattern 3: Prisma Update
// Passing request body directly to Prisma
app.put('/api/user/:id', async (req, res) => {
const user = await prisma.user.update({
where: { id: req.params.id },
data: req.body, // DANGEROUS!
})
res.json(user)
})
// Attacker can set any column in the users tablePrisma accepts any fields matching the schema. No automatic field filtering.
How to Fix Mass Assignment
Secure Pattern: Explicit Allowlist
// Explicitly pick allowed fields
const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'avatar']
app.put('/api/user/:id', async (req, res) => {
const user = await User.findById(req.params.id)
// Only copy allowed fields
for (const field of ALLOWED_UPDATE_FIELDS) {
if (req.body[field] !== undefined) {
user[field] = req.body[field]
}
}
await user.save()
res.json(user)
})
// Attacker's isAdmin: true is ignoredExplicitly define which fields users can modify. Ignore everything else.
Secure Pattern: Zod Validation
// Use Zod to define and validate allowed fields
import { z } from 'zod'
const UpdateUserSchema = z.object({
name: z.string().min(1).max(100).optional(),
email: z.string().email().optional(),
avatar: z.string().url().optional(),
})
app.put('/api/user/:id', async (req, res) => {
// Parse and validate - strips unknown fields
const data = UpdateUserSchema.parse(req.body)
const user = await prisma.user.update({
where: { id: req.params.id },
data, // Only contains allowed, validated fields
})
res.json(user)
})Zod strips unknown fields by default. Only schema-defined fields pass through.
Secure Pattern: Destructuring
// Destructure only allowed fields
app.put('/api/user/:id', async (req, res) => {
const { name, email, avatar } = req.body
const user = await prisma.user.update({
where: { id: req.params.id },
data: {
// Only set fields you explicitly allow
...(name && { name }),
...(email && { email }),
...(avatar && { avatar }),
},
})
res.json(user)
})Destructure specific fields from the request. Only those fields can be updated.
Secure Pattern: Next.js Server Actions
// Next.js Server Action with field allowlist
'use server'
import { z } from 'zod'
const UpdateProfileSchema = z.object({
name: z.string().min(1).max(100),
bio: z.string().max(500).optional(),
})
export async function updateProfile(formData: FormData) {
const rawData = Object.fromEntries(formData)
// Validate and strip unknown fields
const data = UpdateProfileSchema.parse(rawData)
await prisma.user.update({
where: { id: session.userId },
data,
})
}Server Actions with Zod validation ensure only allowed fields are processed.
Key Security Points
- Always use allowlists - explicitly list which fields are updateable
- Never use blocklists - "block isAdmin" misses new dangerous fields
- Validate with schemas - Zod strips unknown fields automatically
- Different allowlists per role - admins may update more fields than users
- Audit sensitive fields - role, isAdmin, balance, permissions need extra protection
AI Fix Prompt
Copy this prompt to your AI tool to scan your codebase for mass assignment vulnerabilities:
Frequently Asked Questions
External Resources
- CWE-915: Mass Assignment - Official vulnerability definition
- OWASP API3:2023 - Broken Object Property Level Authorization
- OWASP Mass Assignment Cheat Sheet - Prevention strategies
- OWASP API6:2019 - Original mass assignment category
- Zod Documentation - TypeScript-first schema validation
Find Mass Assignment in Your Code
VibeShip Scanner automatically detects mass assignment vulnerabilities in your codebase, including Object.assign, spread operators, and direct ORM updates.
Scan Your Code Free