no-unscoped-mutation
Require a chained `.where()` on Knex delete and update builders, so a bulk mutation cannot rewrite or delete every row in the table.
CWE: CWE-284 OWASP: A01:2021 – Broken Access Control
Detects Knex bulk mutations that reach every row in the table. This rule is part of eslint-plugin-knex-security.
💼 This rule is set to error in the strict config.
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-284 (Improper Access Control) |
| Severity | High (CVSS 7.5) |
| Auto-Fix | ❌ No auto-fix available |
| Category | Security |
Why this matters
A bulk mutation without a filter is one forgotten clause away from rewriting or
deleting the entire table. It type-checks, it passes review, and it usually only
shows up once it has run against production data. Knex's update() argument is the values object rather than a filter, and del() takes none — the scope always arrives as a chained .where*() clause. Every spelling of the where family counts.
❌ Incorrect
// Empties the table
await knex('users').del();
// The values object is not a filter
await knex('users').update({ role: 'admin' });✅ Correct
await knex('users').where({ id }).del();
await knex('users').whereIn('id', ids).del();
await knex('users').where({ id }).update({ active: false });Known limitations
This rule reports only what it can prove. Scope that cannot be read statically is treated as present, so the rule stays silent rather than guessing.
When not to use it
Disable this rule in maintenance scripts, seeders, and test fixtures whose job is
to clear a table. Prefer a scoped eslint-disable-next-line on the specific call
over switching the rule off for the whole project.
How a mutation is recognised (2026-10 review)
The receiver is judged by where it is written — an import of the driver, a
client built from it, a transaction handle it passes to a callback, a typed
parameter or class member, or a relative module that exports one — never by
its name. A Map or Set is never a table; an untraceable handle reports when the chain names its table (db('users'), .from('users')).
A builder that another statement scopes
(const q = knex('t'); if (id) q.where({ id }); await q.del();) or hands to a
helper is not reported, and neither is a full-table write inside a knex
migration's up / down or a seed's seed export — a backfill and the
del() that starts a seed are the module's purpose.
Test files are skipped.
Further reading
Use this rule
npm install -D oxlint-plugin-knex-security{ "jsPlugins": ["oxlint-plugin-knex-security"], "rules": { "knex-security/no-unscoped-mutation": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-knex-security'], rules: { 'knex-security/no-unscoped-mutation': 'error', }, },});oxlint-plugin-knex-security re-exports eslint-plugin-knex-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
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.