Skip to main content
interlace
Plugin: express-security

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.

Live changelogfrom eslint-plugin-express-security/CHANGELOG.md, cached for 2 hours.Edit on GitHub

1.5.3

Patch Changes

  • #364 86baa02 Thanks @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 1b8c0df Thanks @ofri-peretz! - Fix SDK peer declarations that npm silently ignored

    Seven plugins listed their target SDKs under peerDependenciesMeta with {"optional": true} but never declared them in peerDependencies. npm drops any peerDependenciesMeta entry that has no matching peerDependencies key, 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-pg and eslint-plugin-mongodb-security already use — a supported major range in peerDependencies, optional: true in peerDependenciesMeta:

    PluginSDKRange
    express-securityexpress^4.0.0 || ^5.0.0
    helmet^6.0.0 || ^7.0.0 || ^8.0.0
    cors^2.0.0
    csurf^1.0.0
    express-rate-limit^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
    jwtjsonwebtoken^8.0.0 || ^9.0.0
    @nestjs/jwt^9.0.0 || ^10.0.0 || ^11.0.0
    express-jwt^7.0.0 || ^8.0.0
    jose^4.0.0 || ^5.0.0 || ^6.0.0
    jwks-rsa^3.0.0 || ^4.0.0
    jwt-decode^3.0.0 || ^4.0.0
    lambda-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.0
    maintainabilitytypescript>=4.8.4
    nestjs-security@nestjs/common^9.0.0 || ^10.0.0 || ^11.0.0
    @nestjs/throttler^4.0.0 || ^5.0.0 || ^6.0.0
    class-validator^0.14.0 || ^0.15.0
    class-transformer^0.5.0
    react-featurestypescript>=4.8.4
    vercel-ai-securityai^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0

    Ranges 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-transformer and @aws-sdk/client-lambda have only ever shipped one usable major. The ai range spans v4 because require-max-steps deliberately accepts both the v4 maxSteps option and the v5+ stopWhen form. The two typescript entries reuse the >=4.8.4 bound @interlace/eslint-devkit already 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 dc25c81 Thanks @ofri-peretz! - Re-publish every package so npm carries the optimised artifact

    No source changed. This is a no-op patch whose entire purpose is to ship the artifact the current build already produces.

    Manifests. scripts and devDependencies are now stripped from every published package.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.ts tree:

    packagepublishedrebuiltsaving
    eslint-plugin-react-features547 kB320 kB−227 kB
    eslint-plugin-secure-coding653 kB477 kB−176 kB
    eslint-plugin-conventions241 kB116 kB−125 kB
    eslint-plugin-browser-security380 kB291 kB−89 kB
    eslint-plugin-maintainability178 kB116 kB−62 kB
    eslint-plugin-react-a11y232 kB173 kB−59 kB
    eslint-plugin-reliability148 kB90 kB−58 kB
    eslint-plugin-vercel-ai-security187 kB130 kB−57 kB
    eslint-plugin-operability90 kB43 kB−47 kB
    eslint-plugin-jwt140 kB95 kB−45 kB
    eslint-plugin-modularity98 kB58 kB−40 kB
    eslint-plugin-nestjs-security122 kB86 kB−36 kB
    eslint-plugin-sqlite-security54 kB20 kB−34 kB
    eslint-plugin-sequelize-security54 kB21 kB−34 kB
    eslint-plugin-prisma-security52 kB19 kB−33 kB
    eslint-plugin-mysql-security52 kB19 kB−33 kB
    eslint-plugin-typeorm-security52 kB19 kB−33 kB
    eslint-plugin-drizzle-security52 kB19 kB−33 kB
    eslint-plugin-knex-security51 kB19 kB−32 kB
    eslint-plugin-modernization45 kB38 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.ts fails the build if scripts or devDependencies ever reappear in a published manifest, so the strip cannot silently regress.

    The dependency ranges did not need updating: every plugin pins @interlace/eslint-devkit with 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 no typescript in the tree.

  • Updated dependencies [dc25c81]:

    • @interlace/eslint-devkit@1.6.1

1.5.0

Minor Changes

  • #313 1f4fc05 Thanks @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-helmet only 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 spellings
    • require-strict-transport-security (CWE-319) — HSTS disabled, max-age below the 180-day preload floor, or includeSubDomains: false
    • no-unsafe-csp-directives (CWE-79 / 1021 / 311) — 'unsafe-inline', 'unsafe-eval', wildcard sources, frame-ancestors '*', missing frame-ancestors under useDefaults: false, and upgradeInsecureRequests: null
    • no-permissive-trust-proxy (CWE-348) — app.set('trust proxy', true), which makes req.ip client-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 handler
    • no-client-controlled-authorization (CWE-863) — if (req.body.role === 'admin'): the check runs, and passes for anyone who sets the field
    • no-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 as error; the three access-control rules ship as warn — 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 5664efd Thanks @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') and if (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 just get / post / use, plus the chained route builder (app.route('/admin').delete(handler)), so app.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 d6e2b3c Thanks @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 construction
    • no-error-details-in-response (CWE-209) — stack traces / raw error objects sent to clients
    • no-sensitive-data-in-query (CWE-598) — passwords/tokens read from GET query strings
    • no-user-controlled-render-locals (CWE-73) — res.render(view, req.body) template object injection
    • no-static-root-exposure (CWE-548) — express.static(__dirname) / serve-index directory exposure
    • require-case-insensitive-path-guard (CWE-178) — case-sensitive path guards bypassed by /ADMIN
    • require-query-type-guard (CWE-843) — string methods on req.query members 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 as warn — the two require-* guard heuristics plus no-sensitive-data-in-query, which matches on parameter names and so never gets enforcement severity.

Patch Changes

  • #298 a53887f Thanks @ofri-peretz! - express-security/no-missing-security-headers.set() on a non-response receiver is not a header call. The rule matched setHeader / header / set on the method name alone, so url.searchParams.set('page', '2') and app.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, including ctx.res.set(…) and this.response.header(…)). The same predicate gates header collection, so a Content-Security-Policy string passed to an unrelated .set() no longer satisfies the requirement for a real response in the same scope.

  • #294 659f6dc Thanks @ofri-peretz! - Rewrite description and keywords on 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-next claimed "100x faster no-cycle detection". No 100x measurement exists: CLAIMS.md records 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-coding claimed 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") and eslint-plugin-react-a11y ("37 rules") hard-coded rule counts that had drifted from reality. Counts are generated into interlace-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-n and eslint-plugin-import all carry the eslint / eslintplugin / eslint-plugin trio — six of our packages were missing eslintplugin, and every one now carries all three plus static-analysis, linting and code-quality. Security plugins add sast, appsec and vulnerability; node-security and secure-coding also carry nodesecurity, the exact keyword eslint-plugin-security ranks on. Each plugin gained the CWE identifiers and attack names for what it detects (cwe-78 command injection, cwe-22 path traversal, cwe-89 SQL injection, cwe-79 XSS, cwe-347 JWT algorithm confusion, cwe-352 CSRF, cwe-943 NoSQL injection), and node-security gained 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 7028fe2 Thanks @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 d67e395 Thanks @ofri-peretz! - Fix Codecov badge showing "unknown" — switch from flag to component URL format

1.3.2

Patch Changes

  • #225 34ff5a8 Thanks @ofri-peretz! - CI-only: pin all coverage thresholds at 100% (integration target, merges last).

1.3.1

Patch Changes

  • #213 391dbe6 Thanks @ofri-peretz! - Align every security rule's meta.docs.cvss to the CVSS its finding actually emits. The emitted machine-readable message sources its CVSS:x from CWE_MAPPING via formatLLMMessageenrichFromCWE, but the static meta.docs.cvss documentation field had drifted on 45 rules across these 7 plugins — e.g. no-hardcoded-credentials documented 9.5 while emitting CVSS: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's meta.docs.cvss must equal the CVSS it emits), the no-hardcoded-credentials rule lock (real ESLint Linter emission), and a devkit enrichFromCWE contract test pinning CWE-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 ae39ec5 Thanks @ofri-peretz! - feat: add no-user-controlled-redirect rule — structural CWE-601 open redirect detection

    Fires on res.redirect(req.query.*), res.redirect(req.body.*), and res.redirect(req.params.*) — an AST-structural check that passes the naming-heuristic litmus test (rename res/req to any identifier and the rule still fires, because detection is on the member-access chain, not on variable names). Severity: error in flagship config.

Patch Changes

  • #143 213cde1 Thanks @ofri-peretz! - fix(no-missing-null-checks): eliminate 53 false positives via three new narrowing patterns

    Rules that were recognized as null guards are now correctly identified as safe:

    1. Truthy if guardif (obj) { obj.prop } — direct truthy check proves non-null. Also covers chains: if (response) protects response.data.items.
    2. Short-circuit ANDobj && obj.prop — right side of && only runs when left is truthy.
    3. Ternary consequentobj ? obj.prop : fallback — truthy test guards the consequent.

    Also: bumped beforeAll timeout 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.

  • 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

[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 defaults
  • strict - All 9 rules as errors
  • api - HTTP/API security rules only
  • graphql - 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.

On this page

No Headings