How do I fix mass assignment?

Direct answer

Never pass a whole request body into a model. Define the fields you accept and assign only those. The attack is simple: submit a field your form doesn't display, `role`, `is_admin`, `balance`. And if the framework maps request keys to model attributes, it gets written.

Muhammad HasanUpdated

Mass assignment is when a framework maps request fields onto model attributes automatically. It's convenient, and it means the request controls which columns get written unless you constrain it.

// Vulnerable
await User.create(req.body);

The form has name and email. The request can have anything.

{ "name": "x", "email": "x@y.com", "role": "admin" }

The fix, by ecosystem

Node / Mongoose, pick the fields explicitly:

const { name, email } = req.body;
await User.create({ name, email });

Or validate into a known shape first:

const schema = z.object({ name: z.string(), email: z.string().email() });
await User.create(schema.parse(req.body));

z.object strips unknown keys by default, which is the behaviour you want.

Rails. Strong parameters:

params.require(:user).permit(:name, :email)

Never permit!.

Laravel, $fillable on the model, and validate before assigning:

protected $fillable = ['name', 'email'];

Prefer $fillable to $guarded: with an allowlist, a column added next year is safe by default.

Django. fields on the form or serializer, never __all__:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['name', 'email']

FastAPI / Pydantic, the request model is the allowlist, which is why this is rarer here. Just don't define a request model containing fields users shouldn't set.

Nested objects

The place it survives a first pass. Permitting an association can pull in more than intended:

params.require(:order).permit(:quantity, items_attributes: [:product_id, :price])

price is now client-controlled. Nested attributes need the same scrutiny as top-level ones, and usually less permission.

Update is worse than create

Creating with an extra field is bad. Updating with one is often worse, because update endpoints tend to accept partial payloads and pass them straight through.

// Vulnerable — user changes their own userId, or someone else's row
await Order.update(req.body, { where: { id: req.params.id } });

Two problems in one: the field allowlist, and the missing ownership check.

Auditing what you already have

Reasonably detectable. create(req.body), update(req.body), permit!, fields = '__all__' and models without $fillable are all recognisable patterns.

What tooling can't tell you is which fields should be assignable, so it flags the shape and you decide. That's a good division of labour. The list is short and the review is quick.

Go deeper

Fast remediation with Kolega

Related answers

See what your own repository returns

Connect a repo and run a scan. No credit card, no pipeline changes.

Get started for free