Changelog
Release history and version updates for eslint-plugin-nestjs-security
Live from GitHub
This changelog is fetched directly from CHANGELOG.md on GitHub and cached for 2 hours.
2.0.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.
2.0.0
Major Changes
-
#336
e190212Thanks @ofri-peretz! - Two rules removed, two added, and every remaining rule narrowed against a 32,251-file corpus. The plugin reports 55 errors where it used to report 3,955 findings, and nothing true was lost.Breaking:
require-class-validatorandno-exposed-debug-endpointsare gone;no-res-bypass-serializationandno-unguarded-swaggerjoinrecommendedatwarn. Every other rule got materially narrower. Nothing got broader.Removed — 10 rules to 8
no-exposed-debug-endpoints— 0 findings across 401 controller files in both corpora. 311 lines, 5 options and 2 name lists to detect nothing. Its one real case, an unguarded admin route, is alreadyrequire-guards.require-class-validator— 317 findings, 72% of every warning emitted. It has to answer "is this class inbound?", and the evidence for that — being the declared type of a@Body()parameter — lives in another file. Two of its six worst files were categories that should never have been in scope: an outbound response base class and a CQRS command. It had grown 8 name-based lists and 5 options, and it is redundant withrequire-validation-pipe-whitelist, which covers the same risk with a decidable fact: withwhitelist: truean undecorated property is stripped and never arrives. One finding per application beats one guess per property.
New rules
no-unguarded-swagger(CWE-200) —SwaggerModule.setup()straight-line in a bootstrap publishes every route, DTO shape and auth scheme to anonymous callers. Reports only where the whole bootstrap is visible; abstains on any conditional and on thesetupSwagger(app)helper shape, which is guarded at a call site in another file. 9 unsafe of 16 sites, 4 repositories.no-res-bypass-serialization(CWE-200) —@Res()withoutpassthrough: truestops every interceptor, soClassSerializerInterceptornever runs and@Exclude()silently stops applying. Reported only when the handler writes a non-literal body; streams, redirects and status literals have nothing to serialize.
require-guards: 94 findings to 14, ~10% precision to ~70%Triaged one by one. Nine of the original 94 were real. Every false class was a legitimate authentication mechanism the rule could not see:
- No authentication system at all (-38). "You forgot a guard" only means
something where guards are the mechanism. 38 findings were NestJS's own
sample/*apps; only19-auth-jwtof 25 declares an auth dependency. Silence requires the manifest and the module scan to come up empty, so an unreadable manifest or a hand-rolledCanActivatekeeps the rule reporting. - Auth applied as middleware (-20). The canonical RealWorld NestJS app
authenticates through
configure(consumer).apply(AuthMiddleware).forRoutes()with no@UseGuardsanywhere, and the rule reported all 20 of its routes. The project scan now reads those registrations. - The
nest newscaffold (-15).@Controller()+@Get()+ a handler taking nothing isGET /.@Controller(ADMIN_PREFIX)is still reported — the test is no argument, not no readable path. - Qualified auth entry points (-7).
auth0Login,githubCallback,awsMarketplaceCallback. Matched on the trailing token only, sogetLoginHistorystays in scope. - Signature-verified webhooks. A handler taking
@Headers('…-secret')is authenticating the way Stripe, GitHub and Stigg document. - Password recovery and activation. Matched as a combination — "password" plus a recovery verb — rather than as four more list entries.
What survives earns the severity: awesome-nest-boilerplate carries
@Authon threePostControllerhandlers and none on@Put(':id')or@Delete(':id'); nestjs-starter-rest-api has two routes whose own comment says// TODO: ADD RoleGuard.no-exposed-private-fields: 58 to 37@ObjectType()shared a set with@Entity, and that set was checked before the credential-delivery name check — so@ObjectType() class ApiKeyTokenshort-circuited into scope and twenty's whole auth DTO directory was reported for carrying the token it exists to return. Persistence still outranks a name; transport does not.@HideField()now counts as an exclusion, as it should. ORM projection (select: false,hidden: true) is accepted as exclusion too.no-permissive-corsExtended to
NestFactory.create({ cors }). Nest routes a non-objectcorsoption into the sameenableCors()this rule already watched, so{cors: true}isAccess-Control-Allow-Origin: *— 7 unsafe sites across 3 repos, including amplication's code-generator template, which emits the flaw into every service it produces.origin: ['*']is not reported: thecorspackage compares array entries with===, so a literal'*'in an array denies rather than allows.Decorators are classified by import origin, not by name
The module a binding came from is a fact in the AST, and it is what a decorator is. A decorator from
@nestjs/graphql,typeormor@nestjs/swaggeris not access control whatever it is named; one from a project module likesrc/middleware/auth.guardis, with no name recognition needed. Naming conventions are only a fallback for project-local modules whose role cannot be resolved.This is what makes the plugin survive the wrapper pattern every real codebase uses —
@Authenticated(immich),@Auth(awesome-nest-boilerplate),@RequireAuthentication(novu) — and it stops@ApiBearerAuth()being read as enforcement when it only documents a scheme.Also
- Options with array defaults no longer declare them in the schema. ESLint
validates with Ajv in
useDefaultsmode, so a schema default is written into the options object the moment a config passes{}— which made['error', {}]behave differently from['error']and reportPOST /auth/login. no-missing-validation-pipereports only shapes noValidationPipecan validate (missing annotation,any,unknown,object, inline type literals). The previous strict behaviour is available viarequireExplicitPipe: true.require-throttlertargets unauthenticated sensitive routes only, making it the complement ofrequire-guardsrather than an overlap. Its route matching is now token-aligned:'authors'.includes('auth')and'tokenize'.includes('token')are both true, so an author listing was being told to rate-limit itself. A sensitive token still counts in any position, soverifyEmailandresendVerifyEmailremain in scope.no-missing-validation-pipeno longer skips a whole file when a globalValidationPipeis registered. A global pipe is evidence about typed DTOs and nothing else — it has no metatype forany,unknown,object, a type literal or an unannotated parameter, and passes those through exactly as a local pipe would. It also accepts any parameter-scoped pipe rather than one literally namedValidationPipe, since@Param('id', UserByIdPipe)resolves and throws; the built-inParse*Pipefamily is excluded because it coerces a scalar and cannot check a DTO's shape.
Tests
629 tests at 100% statement / branch / function / line coverage, including a detection contract (every rule must still fire on the vulnerability it exists for, and stay silent on the minimally-different safe twin) and regression locks pinning exact findings for shapes taken from the measured codebases.
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.4.0
Minor Changes
-
#327
d2a24c6Thanks @ofri-peretz! - Two new rules, both inrecommendedaterror:no-permissive-corsandrequire-validation-pipe-whitelist.They were chosen by measurement, not intuition. Scanning five real NestJS applications (lujakob/nestjs-realworld-example-app, notiz-dev/nestjs-prisma-starter, squareboat/nestjs-boilerplate, ack-nestjs-boilerplate, brocoders/nestjs-boilerplate) for candidate patterns produced two with a high defect rate and a narrow, statically-decidable signature:
require-validation-pipe-whitelist(CWE-915). Three of the five used a barenew ValidationPipe(). Withoutwhitelist: truethe pipe validates the properties the DTO declares and keeps the ones it doesn't, so{ …, "isAdmin": true }passes validation withisAdminstill attached and anysave(dto)downstream carries it into the record. The existingno-missing-validation-pipeasks whether a pipe exists; this asks whether the pipe strips anything.no-permissive-cors(CWE-942). Both CORS call sites in the corpus were permissive — one bareenableCors()(defaults to*) and oneenableCors({ origin: true }). The second is the subtle one: it reflects the request's ownOriginheader back, so every site passes, and unlike'*'it stays valid withcredentials: true, letting any page read authenticated responses.Precision was verified against the same corpus: 6 findings, 6 true positives, 0 false positives. Both mature boilerplates (ack, brocoders) come back clean — brocoders imports its
validationOptionsfrom another module, and the rule deliberately does not resolve across files rather than guess. Anything not statically decidable is left alone: config lookups, callbacks, imported options objects, and objects with a spread that could supply the missing key.
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.3.0
Minor Changes
-
#287
5184a12Thanks @ofri-peretz! - Eliminate the false-positive storm on real NestJS codebases. Scanning two popular boilerplates withrecommendedproduced 582 findings on ack-nestjs-boilerplate and 109 on brocoders/nestjs-boilerplate; after this change they produce 0 and 11, and every remaining finding is a genuine gap (an unauthenticated file-download route, entities exposingpassword/hash, unvalidated request-DTO properties, and one missingThrottlerModule).- Cross-file global registration is now detected. Guards, pipes and rate
limiting registered through DI (
{ provide: APP_GUARD | APP_PIPE | APP_INTERCEPTOR, ... }), throughapp.useGlobalPipes()/app.useGlobalGuards(), or throughThrottlerModule.forRoot(Async)suppress the corresponding per-controller findings. The project root is resolved from the linted file and its module files are scanned once and cached. AThrottlerGuardregistered asAPP_GUARDcounts as rate limiting, not authentication. Opt out per rule withdetectGlobalGuards/detectGlobalPipes: false. require-guardsno longer asserts "unguarded" on a route carrying a decorator it cannot resolve — projects wrap@UseGuardsin composites such as@AuthJwtAccessProtected()viaapplyDecorators(). It also stops reporting credential-issuing routes (login,register,forgotPassword,resetPassword,confirmEmail,refresh, health checks, webhooks), which cannot require the credential they hand out. New options:allowCustomDecorators,detectGlobalGuards,publicRoutePatterns.requiredGuards— documented since 1.0 but never read — is now actually enforced: with it set,@UseGuards(RolesGuard)no longer satisfiesrequiredGuards: ['JwtAuthGuard'], and the newmissingRequiredGuardsmessage names the guards that would. Guard arguments are read syntactically (AuthGuard('jwt'),guards.JwtAuthGuard); anything with no static name, an unresolved composite decorator, or a globalAPP_GUARDstill suppresses the report, since none of them can be proven not to apply the guard.require-throttlernow reports once per project, on the root module, instead of once per route handler. Rate limiting is adopted with a singleThrottlerModuleregistration, so 24 (and 93) per-route errors described a one-line fix. New options:rootModuleNames,rootModuleFiles;skipRoutesis deprecated and ignored. In-file detection requires an actual registration (ThrottlerModuleinimports, or aThrottlerGuardbehindAPP_GUARD) — a bareimport { ThrottlerGuard }no longer silences the rule.no-missing-validation-pipehonours parameter-bound pipes (@Body(new ValidationPipe()),@Param('id', ParseIntPipe)) and globally registered pipes.require-class-validatorno longer fires on response/serialization DTOs (name pattern, superclass name, or class-transformer@Expose/@Exclude), onformat: 'binary'multipart upload slots, or on@Allow()-marked properties, and recognises ~40 more class-validator decorators. New options:checkResponseDtos,responseDtoPattern.no-exposed-private-fieldsis scoped to persistence entities and domain models. ALoginResponseDtocarrying a token is a declared contract; an@Entity()exposingpasswordwithout@Exclude()is an accident. New option:includeDtosrestores the previous behaviour. GraphQL@InputType()/@ArgsType()classes followincludeDtostoo — they are request contracts (LoginInputmust carry a password);@ObjectType()stays an entity.no-exposed-debug-endpointsinspects route paths only, instead of every string literal in the file (it was flagging enum members, seed data and config values).admin,testandhealthare no longer default debug paths, and a guarded debug route is no longer reported. New option:detectGlobalGuards.
- Cross-file global registration is now detected. Guards, pipes and rate
limiting registered through DI (
Patch Changes
-
#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.2.6
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.2.5
Patch Changes
- #252
d67e395Thanks @ofri-peretz! - Fix Codecov badge showing "unknown" — switch from flag to component URL format
1.2.4
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-nestjs-security to align it with other projects, there were no code changes.
Changelog
All notable changes to eslint-plugin-nestjs-security will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.0.0] - 2025-12-29
Added
Authorization & Access Control Rules (2)
require-guards- Require @UseGuards decorator on controllers/handlers (CWE-284)no-exposed-private-fields- Detect exposed sensitive fields in DTOs/entities (CWE-200)
Input Validation Rules (2)
no-missing-validation-pipe- Require ValidationPipe for DTO parameters (CWE-20)require-class-validator- Require class-validator decorators on DTO properties (CWE-20)
Rate Limiting & DoS Rules (1)
require-throttler- Require ThrottlerGuard/@Throttle for rate limiting (CWE-770)
Presets (2)
recommended- Balanced security defaultsstrict- All 5 rules as errors
Features
- LLM-optimized error messages with CWE references
- OWASP Top 10 2021 alignment (A01, A03, A05)
- Decorator-aware detection (@UseGuards, @UsePipes, @Throttle, @Exclude)
assumeGlobal*options for teams using global configuration- Support for public/skip decorators (@Public, @SkipAuth, @AllowAnonymous, @SkipThrottle)
- TypeScript support
- Comprehensive test coverage (79 tests, 96.09% line coverage)
Security
- Covers 4 CWEs: 20, 200, 284, 770
- Maps to OWASP Top 10 2021: A01, A03, A05
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.