Skip to main content
interlace
Plugin: drizzle-securityRules

no-mass-assignment

Disallow writing an inbound request object straight to the database through Drizzle, which lets the caller set every column the model exposes.

CWE: CWE-915 OWASP: A04:2021 – Insecure Design

Detects an inbound request object — or a spread of one — reaching a Drizzle write. This rule is part of eslint-plugin-drizzle-security.

💼 This rule is set to error in the strict config.

Quick Summary

AspectDetails
CWE ReferenceCWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes)
SeverityHigh (CVSS 8.1)
Auto-Fix❌ No auto-fix available
CategorySecurity

Why this matters

await db.insert(users).values(req.body);

That line updates the fields the endpoint is about. It also updates every other column on the model: role, isAdmin, ownerId, emailVerified, credits, stripeCustomerId. None of them appear in the diff, which is why this passes review — the vulnerability is in what the code does not say.

It is also one of the few defects that gets worse without anyone touching it. Add a role column to the model six months from now and every existing mass-assignment site silently starts accepting it. No line changes; the exposure is new. That is what makes this worth a lint rule rather than a code review habit.

Drizzle takes the row directly on .values() and .set(), so the argument itself is the payload.

❌ Incorrect

// ❌ the whole request object
await db.insert(users).values(req.body);

// ❌ spreading it is the same thing
await db.update(users).set({ ...req.body }).where(eq(users.id, id));

✅ Correct

// ✅ name the columns this endpoint owns
await db.insert(users).values({ name: req.body.name, email: req.body.email });

// ✅ or validate into a typed object first
const input = insertUserSchema.parse(req.body);
await db.insert(users).values(input);

What this rule deliberately does not report

  • A payload that names its fields. { name: req.body.name } reads one value out of the request; it is the fix, and it is silent. Note that a named field beside a spread does not help — { ...req.body, updatedAt } still carries everything the spread brought.
  • An object that merely has a body or query key. form.body and config.query are ordinary application objects. The chain has to bottom out in a request-shaped identifier (req, request, ctx, context, event).
  • ctx.data / context.data. data is ordinary application state in several frameworks, so it is not treated as a request surface — a deliberate false negative in exchange for not reporting code with no request in it.
  • A value it cannot see through. repo.create(validated) or repo.create(buildInput(req)) may still be unsafe, but the rule cannot prove it and will not guess. Guessing is how a security rule earns a false-positive reputation.
  • A file that never imports drizzle-orm. The driver import is the gate that keeps this rule inside its own plugin.

When Not To Use It

There is no configuration in which handing the raw request to a write is correct, so this rule has no options — and deliberately so. An allowlist option would let a project re-approve the dangerous shape wholesale, one config file further from the call site, which is the same mistake with more steps.

If a specific call is genuinely safe — an internal job with a payload you construct yourself — disable it there with a reason:

// eslint-disable-next-line drizzle-security/no-mass-assignment -- payload is built in-process, not from a request
await db.insert(users).values(req.body);

Further Reading

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.