Changelog
Release history and version updates for eslint-plugin-express-security
Live from GitHub
This changelog is fetched directly from CHANGELOG.md on GitHub and cached for 2 hours.
1.5.3
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.
1.5.2
Patch Changes
-
#358
1b8c0dfThanks @ofri-peretz! - Fix SDK peer declarations that npm silently ignoredSeven plugins listed their target SDKs under
peerDependenciesMetawith{"optional": true}but never declared them inpeerDependencies. npm drops anypeerDependenciesMetaentry that has no matchingpeerDependencieskey, so the metadata was inert — these packages effectively declared no SDK peer at all. Nothing warned: the failure mode of a dependency you never declared is silence.Each SDK now appears in both maps, matching the shape
eslint-plugin-pgandeslint-plugin-mongodb-securityalready use — a supported major range inpeerDependencies,optional: trueinpeerDependenciesMeta:Plugin SDK Range express-securityexpress^4.0.0 || ^5.0.0helmet^6.0.0 || ^7.0.0 || ^8.0.0cors^2.0.0csurf^1.0.0express-rate-limit^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0jwtjsonwebtoken^8.0.0 || ^9.0.0@nestjs/jwt^9.0.0 || ^10.0.0 || ^11.0.0express-jwt^7.0.0 || ^8.0.0jose^4.0.0 || ^5.0.0 || ^6.0.0jwks-rsa^3.0.0 || ^4.0.0jwt-decode^3.0.0 || ^4.0.0lambda-security@aws-sdk/client-lambda^3.0.0@middy/core^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-cors^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-security-headers^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/validator^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0maintainabilitytypescript>=4.8.4nestjs-security@nestjs/common^9.0.0 || ^10.0.0 || ^11.0.0@nestjs/throttler^4.0.0 || ^5.0.0 || ^6.0.0class-validator^0.14.0 || ^0.15.0class-transformer^0.5.0react-featurestypescript>=4.8.4vercel-ai-securityai^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0Ranges were taken from each SDK's real release history, bounded below by the oldest major whose call shape the rules still match and above by the current major.
cors,csurf,class-transformerand@aws-sdk/client-lambdahave only ever shipped one usable major. Theairange spans v4 becauserequire-max-stepsdeliberately accepts both the v4maxStepsoption and the v5+stopWhenform. The twotypescriptentries reuse the>=4.8.4bound@interlace/eslint-devkitalready declares, since these are the same type-aware-graceful rules behind the same optional TS program.Every range admits the version this repo's
__compatibility__specs are actually tested against, so the declaration cannot drift from what CI proves.Nothing to migrate. Every entry stays optional, so no install adds a package or emits a warning when the SDK is absent. What changes is that a consumer on an unsupported major now gets a peer warning instead of nothing — which was the point of the metadata in the first place.
-
Updated dependencies [
e8e9ee6]:- @interlace/eslint-devkit@1.7.0
1.5.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
1.5.0
Minor Changes
-
#313
1f4fc05Thanks @ofri-peretz! - Eight new rules closing the two fixable gaps found by the F#24/F#26 coverage benchmark (CWE Top 25 map + framework-depth matrix).Express — the helmet header family (the depth gap where SonarJS led 17 rules to our 14;
require-helmetonly proved the middleware was mounted, never that its protections were still on):no-disabled-helmet-protections(CWE-693) —helmet({ contentSecurityPolicy: false })and the rest of the disabled-default family, helmet 6 and 7 spellingsrequire-strict-transport-security(CWE-319) — HSTS disabled,max-agebelow the 180-day preload floor, orincludeSubDomains: falseno-unsafe-csp-directives(CWE-79 / 1021 / 311) —'unsafe-inline','unsafe-eval', wildcard sources,frame-ancestors '*', missingframe-ancestorsunderuseDefaults: false, andupgradeInsecureRequests: nullno-permissive-trust-proxy(CWE-348) —app.set('trust proxy', true), which makesreq.ipclient-controlled and every rate-limit bucket forgeable
Express — CWE Top 25 (2025) access-control adjacency (three of the four JS-applicable entries we did not cover):
require-route-authentication(CWE-306) — critical-function routes with no auth middleware and no principal read in the handlerno-client-controlled-authorization(CWE-863) —if (req.body.role === 'admin'): the check runs, and passes for anyone who sets the fieldno-idor-resource-access(CWE-639) —Invoice.findById(req.params.id)in a handler that never mentions the caller
Node — the fourth adjacency (CWE-77, generic command injection, previously covered only as CWE-78):
no-dynamic-command-string(CWE-77) — an assembled command string handed to a shell flag (spawn('bash', ['-c', …])) or to a command-runner that does not escape (execaCommand,$.raw)
In
recommended, the five structural rules ship aserror; the three access-control rules ship aswarn— their critical-path / authorization-attribute / lookup-method vocabularies are name-based, and naming heuristics never carry enforcement severity (plugin scope-audit invariant I3).
Patch Changes
1.4.0
Minor Changes
-
#292
5664efdThanks @ofri-peretz! -express-security/no-exposed-debug-endpoints— only route registrations count. The rule had a second listener that reported any bare string literal equal to a debug path (/admin,/health,/debug, …) anywhere in a file. A redirect-URL constant tripped it while authoring benchmark corpus fixtures —const ADMIN_PATH = '/admin',res.redirect('/admin')andif (req.path === '/health')were all CWE-489 "Exposed Debug Endpoint" findings, none of which registers an endpoint. That listener is gone: a literal is reported only as the path argument of an express route registration.The registration check also now covers every express routing method (
put,patch,delete,head,options,all) rather than justget/post/use, plus the chained route builder (app.route('/admin').delete(handler)), soapp.delete('/admin/users/:id', handler)is caught where it previously was not. Conversely,app.get(name)with a single argument is an application-setting lookup rather than a route registration and is no longer reported. -
#293
d6e2b3cThanks @ofri-peretz! - Seven new rules closing benchmark-corpus coverage gaps (A-lite research wave):no-host-header-in-links(CWE-640) — Host-header poisoning in password-reset/email link constructionno-error-details-in-response(CWE-209) — stack traces / raw error objects sent to clientsno-sensitive-data-in-query(CWE-598) — passwords/tokens read from GET query stringsno-user-controlled-render-locals(CWE-73) —res.render(view, req.body)template object injectionno-static-root-exposure(CWE-548) —express.static(__dirname)/serve-indexdirectory exposurerequire-case-insensitive-path-guard(CWE-178) — case-sensitive path guards bypassed by/ADMINrequire-query-type-guard(CWE-843) — string methods onreq.querymembers without type guards
In the recommended preset four ship as
error(no-host-header-in-links,no-error-details-in-response,no-user-controlled-render-locals,no-static-root-exposure) and three aswarn— the tworequire-*guard heuristics plusno-sensitive-data-in-query, which matches on parameter names and so never gets enforcement severity.
Patch Changes
-
#298
a53887fThanks @ofri-peretz! -express-security/no-missing-security-headers—.set()on a non-response receiver is not a header call. The rule matchedsetHeader/header/seton the method name alone, sourl.searchParams.set('page', '2')andapp.set('view engine', 'ejs')were reported as CVSS 7.5 missing-security-header findings — a false positive on two of the most common calls in an Express codebase. The receiver must now be an HTTP response (res/resp/response/reply, includingctx.res.set(…)andthis.response.header(…)). The same predicate gates header collection, so aContent-Security-Policystring passed to an unrelated.set()no longer satisfies the requirement for a real response in the same scope. -
#294
659f6dcThanks @ofri-peretz! - Rewritedescriptionandkeywordson every published package for npm search discovery. npm ranks on name, description, and keywords, and the registry only picks up these fields at publish — so this is metadata-only and takes effect for each package on its next release.Descriptions now lead with the search phrase. Every one starts
ESLint plugin for <the thing you'd search>instead of a brand-first or category-first framing, and names the concrete vulnerabilities the plugin actually detects. Three were corrected while doing so:eslint-plugin-import-nextclaimed "100x faster no-cycle detection". No 100x measurement exists:CLAIMS.mdrecords 3.1x end-to-end (8x in pure rule execution) on a 5,483-file React codebase, and the highest number in any benchmark result is 54.9x on the synthetic corpus. The description now states the real-codebase figure.eslint-plugin-secure-codingclaimed SQL injection, XSS and CSRF coverage — none of which are its rules. It now names what it does detect: LDAP, XPath, XXE, GraphQL and template injection, unsafe deserialization, ReDoS, missing authentication, and PII in logs.eslint-plugin-secure-coding("89 rules") andeslint-plugin-react-a11y("37 rules") hard-coded rule counts that had drifted from reality. Counts are generated intointerlace-numbers.json; hand-typed copies are removed rather than corrected.
Keywords now match the vocabulary of the plugins that rank.
eslint-plugin-security,eslint-plugin-jsx-a11y,eslint-plugin-nandeslint-plugin-importall carry theeslint/eslintplugin/eslint-plugintrio — six of our packages were missingeslintplugin, and every one now carries all three plusstatic-analysis,lintingandcode-quality. Security plugins addsast,appsecandvulnerability;node-securityandsecure-codingalso carrynodesecurity, the exact keywordeslint-plugin-securityranks on. Each plugin gained the CWE identifiers and attack names for what it detects (cwe-78command injection,cwe-22path traversal,cwe-89SQL injection,cwe-79XSS,cwe-347JWT algorithm confusion,cwe-352CSRF,cwe-943NoSQL injection), andnode-securitygained the crypto vocabulary it had been missing entirely despite absorbing the crypto rule set (crypto,cryptography,weak-hash,md5,sha1,timing-attack).No rule behavior, exports, or configuration changes.
-
Updated dependencies [
e1cdf83,659f6dc]:- @interlace/eslint-devkit@1.4.3
1.3.4
Patch Changes
-
#269
7028fe2Thanks @ofri-peretz! - docs: dual-logo README header (Interlace mark + ESLint mark side by side) and closing Interlace footer — refreshes the README rendered on npmjs.com. No runtime changes. -
Updated dependencies [
7028fe2]:- @interlace/eslint-devkit@1.4.2
1.3.3
Patch Changes
- #252
d67e395Thanks @ofri-peretz! - Fix Codecov badge showing "unknown" — switch from flag to component URL format
1.3.2
Patch Changes
- #225
34ff5a8Thanks @ofri-peretz! - CI-only: pin all coverage thresholds at 100% (integration target, merges last).
1.3.1
Patch Changes
-
#213
391dbe6Thanks @ofri-peretz! - Align every security rule'smeta.docs.cvssto the CVSS its finding actually emits. The emitted machine-readable message sources itsCVSS:xfromCWE_MAPPINGviaformatLLMMessage→enrichFromCWE, but the staticmeta.docs.cvssdocumentation field had drifted on 45 rules across these 7 plugins — e.g.no-hardcoded-credentialsdocumented9.5while emittingCVSS:9.8(the value the published article and SARIF/LLM consumers already read).This corrects the documentation metadata only — no emitted finding changes. Locked by
security-cvss-docs-consistency.lock.test.ts(cross-plugin: every security rule'smeta.docs.cvssmust equal the CVSS it emits), theno-hardcoded-credentialsrule lock (real ESLintLinteremission), and a devkitenrichFromCWEcontract test pinningCWE-798 → 9.8.Follow-up (not in scope): 50 security rules document a CVSS that never appears in any emitted message (their messages carry no CWE), and several rules emit the generic CWE score where a rule-specific score may be warranted — both change emitted output and are separate decisions.
1.3.0
Minor Changes
-
#169
ae39ec5Thanks @ofri-peretz! - feat: addno-user-controlled-redirectrule — structural CWE-601 open redirect detectionFires on
res.redirect(req.query.*),res.redirect(req.body.*), andres.redirect(req.params.*)— an AST-structural check that passes the naming-heuristic litmus test (renameres/reqto any identifier and the rule still fires, because detection is on the member-access chain, not on variable names). Severity:errorin flagship config.
Patch Changes
-
#143
213cde1Thanks @ofri-peretz! - fix(no-missing-null-checks): eliminate 53 false positives via three new narrowing patternsRules that were recognized as null guards are now correctly identified as safe:
- Truthy if guard —
if (obj) { obj.prop }— direct truthy check proves non-null. Also covers chains:if (response)protectsresponse.data.items. - Short-circuit AND —
obj && obj.prop— right side of&&only runs when left is truthy. - Ternary consequent —
obj ? obj.prop : fallback— truthy test guards the consequent.
Also: bumped
beforeAlltimeout to 30 seconds in 7 compatibility test files (__compatibility__/*.spec.ts). Native-addon packages routinely exceed the previous 10-second default on a cold ESM load. - Truthy if guard —
-
Updated dependencies [
736a5fe]:- @interlace/eslint-devkit@1.4.1
[1.2.3] - 2026-02-08
Bug Fixes
- align codecov component IDs with full package names (2831b968)
Documentation
- fix changelog header format across all packages (c3a15082)
❤️ Thank You
- Ofri Peretz
[1.2.2] - 2026-02-06
Bug Fixes
- align codecov component names and update docs components (0a59a86c)
❤️ Thank You
- Ofri Peretz
[1.2.1] - 2026-02-02
This was a version bump only for eslint-plugin-express-security to align it with other projects, there were no code changes.
Changelog
All notable changes to eslint-plugin-express-security will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Documentation
- 📘 Launched new documentation site: eslint.interlace.tools
[1.0.0] - 2025-12-29
Added
Headers & CORS Rules (4)
require-helmet- Require helmet middleware for security headers (CWE-693)no-permissive-cors- Detect wildcard CORS origins (CWE-942)no-cors-credentials-wildcard- Block credentials: true with wildcard origin (CWE-942)require-express-body-parser-limits- Require body parser size limits (CWE-770)
CSRF & Cookies Rules (2)
require-csrf-protection- Require CSRF middleware for state-changing routes (CWE-352)no-insecure-cookie-options- Detect missing Secure/HttpOnly cookie attributes (CWE-614)
Rate Limiting & DoS Rules (2)
require-rate-limiting- Require rate limiting middleware (CWE-770)no-express-unsafe-regex-route- Detect ReDoS-vulnerable regex patterns (CWE-1333)
GraphQL Rules (1)
no-graphql-introspection-production- Disable GraphQL introspection in production (CWE-200)
Presets (4)
recommended- Balanced security defaultsstrict- All 9 rules as errorsapi- HTTP/API security rules onlygraphql- GraphQL-specific rules only
Features
- LLM-optimized error messages with CWE references
- OWASP Top 10 2021 alignment (A01, A03, A05, A07)
- Middleware-aware detection (helmet, cors, csurf, express-rate-limit)
- TypeScript support with exported option types
- Comprehensive test coverage (132 tests, 93.15% line coverage)
Security
- Covers 6 CWEs: 200, 352, 614, 693, 770, 942, 1333
- Maps to OWASP Top 10 2021: A01, A03, A05, A07
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.