Changelog
Release history and version updates for eslint-plugin-knex-security
Live from GitHub
This changelog is fetched directly from CHANGELOG.md on GitHub and cached for 2 hours.
0.3.0
Minor Changes
-
#373
e5d31abThanks @ofri-peretz! - Addrequire-tls(CWE-319) to the Knex, mysql2, Sequelize and TypeORM security plugins.Reports two distinct failures, because they do not share a remediation:
tlsDisabled— the connection is plaintext (ssl: false,?sslmode=disable). Every query, every row and the credentials that open the session cross the network in the clear.certificateValidationDisabled—rejectUnauthorized: false(ortrustServerCertificate: trueon mssql, which inverts the polarity). The traffic is encrypted but the server is never authenticated, so the client completes a handshake just as willingly with whoever answered in the database's place. The fix is to supply the CA, never to switch the check off.
The detection gate is a database connection config — driver import plus a connection-shaped sibling key — which is what keeps the rule out of
eslint-plugin-node-security, where a barerejectUnauthorized: falsewould also match every https agent and fetch option in the repo, and double-report this line from two plugins.A value the rule cannot read statically (
ssl: useTls) is never reported. That is a deliberate false negative in exchange for findings that are always real.Not shipped for
prisma-security(connection config lives inschema.prisma, not JavaScript),drizzle-security(delegates connection setup to the underlying driver, which its own plugin covers) orsqlite-security(a local file, no network to protect).
Patch Changes
-
#381
74bbf60Thanks @ofri-peretz! - Load rule modules on demand instead of at plugin load.Every plugin barrel used to
requireall of its rules the moment ESLint loaded the plugin, whether or not your config enabled them.plugin.rules[id]is only ever read for rules a config turns on, so the rest was parse-and-compile cost for code that never ran.The published entry now exposes each rule behind a getter, so a rule module is read the first time something asks for it. Measured on a 7-plugin config with 34 rules enabled: 163 rule modules loaded and 251 ms of plugin load, against 34 modules and 8.5 ms — total ESLint wall time 251 ms → 109 ms. On a preset that enables most of a plugin (
node-security/recommended, 25 of 37) it is a wash, 72 ms → 65 ms. It is never slower; the win scales with how many plugins you stack and how few of their rules you use.Nothing about the plugin API changes.
Object.keys(plugin.rules)still lists every rule without loading any of them, repeated reads return the same object, and the./oxlintsub-export is the same plugin object it always was.eslint-plugin-jwtandeslint-plugin-vercel-ai-securityalso re-export their rule objects as named top-level exports, which cannot be deferred — those two keep loading eagerly. -
#381
74bbf60Thanks @ofri-peretz! - Declare what we support, load only what we usetslibis gone from every package. It was a NON-optional peer of@interlace/eslint-devkit, so all 26 plugins declared it as a dependency to satisfy that peer — 124 kB every consumer installed so twelverequire("tslib")calls could resolve. The shipped JavaScript now inlines the TypeScript helpers instead (--importHelpers falseon the emit pass that already re-writes it), costing ~9.5 kB in devkit. Zerotslibrequires remain anywhere; verified by installing every plugin with notslibin the tree and loading all 26 with every rule intact.eslint-plugin-import-nexthad a phantom dependency. Its rulesrequire("typescript")at module load, but it was declared in neitherdependenciesnorpeerDependencies— it worked only because something else in the tree happened to install it. A clean install crashed the whole plugin, not just the type-aware rules.typescriptis now a required peer, which is what the code actually needs.23 "technologies we support" declarations did nothing. Seven plugins listed their target libraries in
peerDependenciesMetawith no matchingpeerDependenciesentry, and npm ignores meta for a package that is not declared a peer — verified by installingeslint-plugin-express-securityand watching nothing install and nothing warn.eslint-plugin-jwtappeared to support six JWT libraries and formally supported none. All 23 are now real optional peers, matching the conventionpg,mongodb,prismaand the other nine already followed:plugin technologies now actually declared eslint-plugin-jwtjsonwebtoken, @nestjs/jwt, express-jwt, jose, jwks-rsa, jwt-decode eslint-plugin-lambda-security@aws-sdk/client-lambda, @middy/core, @middy/http-cors, @middy/http-security-headers, @middy/validator eslint-plugin-express-securityexpress, helmet, cors, csurf, express-rate-limit eslint-plugin-nestjs-security@nestjs/common, @nestjs/throttler, class-validator, class-transformer eslint-plugin-vercel-ai-securityai eslint-plugin-maintainability,eslint-plugin-react-featurestypescript All optional, so nothing is installed on the consumer’s behalf — the declaration is the supported-technology signal, which is exactly what it was meant to be.
A new gate compares declared dependencies against what the emitted JavaScript actually loads, in both directions: a
requirewith no declaration (works until someone installs cleanly) and a declaration nothing requires (weight every consumer pays). It understands that a dependency may exist to satisfy an optional peer of another dependency, which is whyeslint-plugin-import-nextlegitimately declaresoxc-resolverthat devkit lazily loads. -
#335
47cde07Thanks @ofri-peretz! - Fix the./oxlintsubpath export, which pointed atsrc/oxlint.js— a file no build produces.require('<package>/oxlint')threw MODULE_NOT_FOUND on every published package, while every README documented that exact wiring for oxlint'sjsPlugins. The export now points at the build output,dist/src/oxlint.js.The path was hardcoded in
scripts/generate-oxlint-shims.ts, so the generator rewrote any manual correction back to the broken value on the next drift check — fixed there rather than per package.This release also carries npm provenance: the affected packages were last published from a workstation, which has no OIDC token to attest with, so the published tarballs had no attestation. Publishing through the release workflow signs them.
-
Updated dependencies [
85e57a7,74bbf60,e5d31ab,1fb1cad,d1a3d8c]:- @interlace/eslint-devkit@1.8.0
0.2.1
Patch Changes
- #364
86baa02Thanks @ofri-peretz! - Add the ecosystem and oxlint marks to the README logo row. Each plugin now leads with Interlace -> its ecosystem (node, nestjs, express, react, mongodb, postgresql, mysql, sqlite, prisma, drizzle, knex, typeorm, sequelize, lambda, vercel, jwt) -> oxlint -> ESLint; the generic quality plugins carry the row without an ecosystem mark. README-only change - no rule behaviour is affected. The patch bump is what carries the new README onto npm, which only refreshes a package README on publish.
0.2.0
Minor Changes
-
#353
e8e9ee6Thanks @ofri-peretz! - Addno-unscoped-mutation(CWE-284) to the Prisma, Drizzle and Knex pluginsEvery ORM ships a bulk mutation whose unscoped form rewrites or deletes the whole table.
prisma.user.deleteMany(),db.delete(users),knex('users').del()— each one type-checks, passes review, and only shows up once it has run against production data.eslint-plugin-drizzle's entire published surface is this single check for a single ORM; this generalizes it.The detection lives in one place,
createUnscopedMutationRulein@interlace/eslint-devkit, and each plugin instantiates it with its own sinks and remediation copy — the same shapecreateSqlInjectionRulealready uses. Each plugin declares where its scope lives: an options-object filter for Prisma, a chained.where*()for Drizzle and Knex.Every instantiation is gated on the driver: the rule is silent in files that never import it, and silent on receivers that do not read as a driver handle. Without that gate,
deleteandupdatewould matchmap.delete(key)andstore.update(patch)— method names alone are not discriminators.Plugin Sinks Where scope comes from prisma-securitydeleteMany,updateMany{ where }in the options objectdrizzle-securitydelete,updatea chained .where()knex-securitydel,delete,updateany of the chained where*familyargumentRoleis the one thing that cannot be inferred from the AST. A lone identifier argument is the filter for Prisma (deleteMany(opts)) and the table for Drizzle (db.delete(users)); reading it wrong either suppresses the headline Drizzle finding or invents a false positive on every dynamically built filter.Not shipped for Sequelize or TypeORM. Sequelize gives its instance and static forms the same names and both accept an options object, so
user.destroy({ transaction: t })(one row) andUser.destroy({})(the whole table) are the same AST. Two false positives surfaced in its test suite, and the rule was withdrawn from that package rather than shipped with them — a rule that fires on correct code is the one users disable. The genuinely detectable case,destroy({ truncate: true }), becomes its own rule. TypeORM's bare-criteria shape (repo.delete({ id }), with nowherekey) is a third detection shape and is deferred for the same reason.Scope that cannot be read statically is treated as present, so the rule stays silent rather than guessing. Ships in
strictonly — promotion torecommendedandflagshipwaits on a measured false-positive profile against the benchmark corpus.
Patch Changes
- Updated dependencies [
e8e9ee6]:- @interlace/eslint-devkit@1.7.0
0.1.1
Patch Changes
-
#338
dc25c81Thanks @ofri-peretz! - Re-publish every package so npm carries the optimised artifactNo source changed. This is a no-op patch whose entire purpose is to ship the artifact the current build already produces.
Manifests.
scriptsanddevDependenciesare now stripped from every publishedpackage.json. Neither can do anything in a consumer’s node_modules — npm never runs one and never installs the other — but they shipped in all 27 manifests, cluttered the npm page, and were read by SCA tools scanning installed manifests. No package declares a lifecycle hook, so nothing observable changes. Every published package is bumped so this applies uniformly rather than to a subset.Tarballs. 20 packages were last published before the build pipeline changed and still ship
AGENTS.md,CHANGELOG.md, JSDoc in the emitted.js, and the full generated.d.tstree:package published rebuilt saving eslint-plugin-react-features547 kB 320 kB −227 kB eslint-plugin-secure-coding653 kB 477 kB −176 kB eslint-plugin-conventions241 kB 116 kB −125 kB eslint-plugin-browser-security380 kB 291 kB −89 kB eslint-plugin-maintainability178 kB 116 kB −62 kB eslint-plugin-react-a11y232 kB 173 kB −59 kB eslint-plugin-reliability148 kB 90 kB −58 kB eslint-plugin-vercel-ai-security187 kB 130 kB −57 kB eslint-plugin-operability90 kB 43 kB −47 kB eslint-plugin-jwt140 kB 95 kB −45 kB eslint-plugin-modularity98 kB 58 kB −40 kB eslint-plugin-nestjs-security122 kB 86 kB −36 kB eslint-plugin-sqlite-security54 kB 20 kB −34 kB eslint-plugin-sequelize-security54 kB 21 kB −34 kB eslint-plugin-prisma-security52 kB 19 kB −33 kB eslint-plugin-mysql-security52 kB 19 kB −33 kB eslint-plugin-typeorm-security52 kB 19 kB −33 kB eslint-plugin-drizzle-security52 kB 19 kB −33 kB eslint-plugin-knex-security51 kB 19 kB −32 kB eslint-plugin-modernization45 kB 38 kB −7 kB Those 20 go from 3428 kB to 2169 kB — −36.7%. The remaining 7 were released after the pipeline change and only gain the manifest strip.
A new check in
scripts/check-published-artifacts.tsfails the build ifscriptsordevDependenciesever reappear in a published manifest, so the strip cannot silently regress.The dependency ranges did not need updating: every plugin pins
@interlace/eslint-devkitwith a caret that 1.6.0 satisfies, verified by a clean install of an unchanged plugin resolving devkit 1.6.0 with zero dependencies and notypescriptin the tree. -
Updated dependencies [
dc25c81]:- @interlace/eslint-devkit@1.6.1
0.1.0
Minor Changes
Six new driver-scoped SQL-injection plugins (CWE-89), each shipping one rule —
no-unsafe-query at error in recommended:
eslint-plugin-mysql-security— mysql2 / mysql. Sinks:.query(),.execute()(gated on SQL keywords in the static text, since these are common method names outside MySQL). Remediation names MySQL's own safe API.eslint-plugin-prisma-security— @prisma/client. Sinks:.$queryRawUnsafe(),.$executeRawUnsafe(). Remediation names Prisma's own safe API.eslint-plugin-drizzle-security— drizzle-orm. Sinks:.raw(). Remediation names Drizzle's own safe API.eslint-plugin-knex-security— knex. Sinks:.raw(). Remediation names Knex's own safe API.eslint-plugin-sqlite-security— better-sqlite3 / sqlite3. Sinks:.prepare(),.exec(),.run(),.all(),.get()(gated on SQL keywords in the static text, since these are common method names outside SQLite). Remediation names SQLite's own safe API.eslint-plugin-typeorm-security— typeorm. Sinks:.query(). Remediation names TypeORM's own safe API.
All six instantiate the shared createSqlInjectionRule from
@interlace/eslint-devkit, so detection is one implementation and each
plugin differs only in sinks, precision gate and remediation copy. Install the
one matching your stack and you get exactly one finding per line.
None are added to eslint-config-interlace's aggregated presets: sink names
overlap across drivers (.query(), .raw()), so bundling them would report the
same line more than once.
View on GitHub →
Building secure JavaScript with Interlace? Star the repo to get new rules and CWE coverage as we ship them — or follow the AI-code-security benchmarks behind them.