agentleFS
cindovahqcindovahq/security-skills/skills/nestjs-security/SKILL.md

nestjs-security

Skill0 starsChanged 42 days agoApache-2.0View on GitHub

Security review and secure-coding guidance for NestJS 10.x, 11.x and 12.x applications on the Express or Fastify adapter. Use when auditing, reviewing, pentest-prepping or hardening a NestJS backend or API, or when writing or changing its controllers, guards, pipes, DTOs, modules, authentication, JWT handling, GraphQL resolvers, WebSocket gateways, microservice handlers, uploads or main.ts configuration. Triggers on projects whose package.json depends on @nestjs/core or @nestjs/common, or that contain nest-cli.json, main.ts with NestFactory.create, or *.module.ts and *.controller.ts files. Covers guard and middleware ordering, @Public fail-open patterns, ValidationPipe whitelist and mass assignment, class-transformer data exposure, Passport and JWT, IDOR and CASL, throttler, CORS, CSRF, helmet, GraphQL field authorization and introspection, gateway and TCP transport authentication, ORM injection, multer and FileTypeValidator, SSRF, Swagger exposure, NestJS advisories, and supertest verification.

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 4 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All paths in this skill (references/...) are relative to the directory containing this SKILL.md.

    line 18
  2. Very specific:

    Read package.json and the lockfile for @nestjs/* versions, the adapter (@nestjs/platform-express or @nestjs/platform-fastify), @nestjs/jwt, …

    line 31
  3. Distinctive:

    Every finding needs evidence: file, line, the code or config, and the path from attacker input to the sensitive operation, including which …

    line 65
  4. Distinctive:

    Do not report these without further evidence:

    line 96
  5. Very specific:

    A controller without @UseGuards: when a global APP_GUARD authenticates every non-@Public() route.

    line 99
  6. Distinctive:

    When writing Nest code, default to:

    line 136
  7. Very specific:

    Test the boundary with supertest using the same configureApp(app) as main.ts.

    line 150
  8. Very specific:

    Use these when appsec-review is not installed.

    line 154
  9. Distinctive:

    Hardening: not demonstrably exploitable, but a stronger control is recommended.

    line 160
  10. Very specific:

    Rules: never invent files, routes, packages, advisory IDs or config values.

    line 178

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

NestJS Security

Find, explain, fix and verify security issues in NestJS applications, and write new Nest code that does not introduce them.

All paths in this skill (references/...) are relative to the directory containing this SKILL.md.

Modes

  • Review mode: the user asks for an audit, security review, pentest prep, or "is this secure?". Follow the workflow below and produce findings.
  • Build mode: you are writing or modifying Nest code. Apply the build-mode guardrails to every change and load only the reference for the area you're touching.

NestJS sits on Express or Fastify. For generic Node topics (prototype pollution, SSRF allow-lists, SQL/NoSQL injection basics, sessions, ReDoS, npm supply chain, Node EOL status) use the nodejs-security skill; this skill is self-contained for everything Nest-specific. If the appsec-review skill is installed, it owns the overall methodology and report format; otherwise use the evidence and reporting rules below.

Review workflow

1. Confirm the stack and versions

  1. Read package.json and the lockfile for @nestjs/* versions, the adapter (@nestjs/platform-express or @nestjs/platform-fastify), @nestjs/jwt, @nestjs/passport, @nestjs/throttler, @nestjs/graphql and @nestjs/apollo, @nestjs/websockets, @nestjs/microservices, @nestjs/swagger, the ORM, class-validator, class-transformer, multer, helmet.
  2. On 2026-10-02 the lines are 12.x (12.0.0 on 2026-08-27, 12.1.2 latest), 11.x (11.2.7) and 10.x (last release 10.4.22 on 2026-01-10; late-2026 advisories list it as unpatched). Check the installed version against references/dependencies.md: several 2026 advisories are authentication bypasses on the Fastify adapter, and multer DoS fixes arrive with @nestjs/platform-express 11.2.6 and 12.0.3.
  3. Find main.ts. Everything global happens there: useGlobalPipes, useGlobalGuards, enableCors, helmet/useSecurityHeaders, enableCsrfProtection, Swagger, rawBody, hybrid connectMicroservice. Then app.module.ts for APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, ThrottlerModule, ConfigModule, GraphQLModule, TypeOrmModule.

2. Map the attack surface

  • List controllers, resolvers, gateways and @MessagePattern/@EventPattern handlers. For each, write down the effective guards (global, controller, route), pipes and interceptors that actually run (see references/request-pipeline.md).
  • Note what global enhancers do not reach: hybrid microservices, GraphQL field resolvers, gateway handleConnection(), Swagger and static files.
  • Flag every @Public(), every route that takes an ID, URL, filename, sort field or whole body, and every @Body() typed any, an interface or an array.

3. Review each area

Load the reference for each area as you reach it. Don't load all of them up front.

AreaReferenceStart by looking for
Pipeline, guards, adaptersreferences/request-pipeline.mdUnbound RolesGuard, handleRequest returning user, getAllAndMerge, path-scoped middleware auth, Fastify adapter version, inheritAppConfig
Authentication, JWTreferences/authentication.md?? 'secret' fallbacks, ignoreExpiration, decode, missing algorithms/expiresIn, @Public() logic, unsalted hashes, cookie flags
Authorization, IDORreferences/authorization.md@Roles without guard, findOneBy({ id }) without owner, PATCH :id without caller check, singleton fields holding the user, CASL checks by type
Validation, mass assignmentreferences/validation-mass-assignment.mdnew ValidationPipe() without whitelist, @Body() any, PartialType(CreateDto) with role, missing @Type, array bodies
Response exposurereferences/serialization-data-exposure.md@Exclude without ClassSerializerInterceptor, entities returned directly, @Res()
Injectionreferences/injection.mdquery(\...${}`), QueryBuilder template strings, undefinedfilters, Mongo operators,$queryRawUnsafe`
CORS, CSRF, headers, proxyreferences/cors-csrf-headers.mdorigin: true + credentials, cookie auth without CSRF defense, missing helmet/useSecurityHeaders, trust proxy
Rate limiting, DoSreferences/rate-limiting-dos.mdThrottlerModule without ThrottlerGuard, @SkipThrottle, multer without limits, body limits
GraphQLreferences/graphql.mdField resolver guards without fieldResolverEnhancers, introspection, no depth/complexity limit, GqlExecutionContext
WebSockets, microservicesreferences/websockets-microservices.mdEmpty handleConnection, client-supplied userId, rooms by input, TCP host: '0.0.0.0', unguarded @MessagePattern
Uploads, downloads, staticreferences/file-uploads.mdFileInterceptor without limits, originalname as path, fallbackToMimetype, createReadStream(join(dir, param)), public upload dirs
SSRF, redirectsreferences/ssrf-redirects.mdHttpService.get(dto.url), @IsUrl() as the only control, @Redirect with input
Secrets, Swagger, errorsreferences/secrets-config.mdSwaggerModule.setup in production, config.get(..., 'default'), filters returning stack, DevtoolsModule, synchronize: true
Dependenciesreferences/dependencies.mdEOL Nest 10, Fastify middleware advisories, multer, TypeORM, @nestjs/devtools-integration
Verificationreferences/verification.mdTest apps that skip main.ts configuration, proof tests per finding class

4. Classify and report

Every finding needs evidence: file, line, the code or config, and the path from attacker input to the sensitive operation, including which guards, pipes and interceptors run on that route. Classify using the rules below.

5. Remediate and verify (when asked to fix)

Explain the issue first. Make the smallest change that uses Nest's own mechanism (a bound guard, whitelist: true, a response DTO, getOrThrow, an APP_GUARD order). Then verify with references/verification.md: the attack fails, legitimate use works, siblings (other methods, GraphQL, gateways, microservice handlers) are fixed, and the test app applies the same bootstrap configuration as main.ts.

High-signal patterns

These are investigation signals, not findings. Trace each one before reporting.

# Guards / auth
handleRequest(err, user) { return user }   getAllAndMerge(   reflector.get(   @Roles( without RolesGuard   @Public()
ignoreExpiration   ?? 'secret'   get('JWT_SECRET', '   jwtService.decode(   secretOrKey   createHash('sha256'|'md5')
APP_GUARD order   useGlobalGuards   connectMicroservice( without inheritAppConfig   handleConnection(   fieldResolverEnhancers

# Validation / exposure
new ValidationPipe()   @Body() body: any   @Body() dtos: Dto[]   import type { Dto }   PartialType(Create   Object.assign(entity, dto)
repo.update(id, body)   repo.create(dto)   @Exclude(   ClassSerializerInterceptor   @Res()   returns entity   passwordHash

# Injection / data layer
query(`   createQueryBuilder   .where(`   .orderBy(   $queryRawUnsafe   findOne({ where: { id: undefined }   find(req.body   $where

# Config / surface
SwaggerModule.setup   introspection:   graphiql: true   cors: true   origin: true   credentials: true   enableCors(   helmet
ThrottlerModule.forRoot without ThrottlerGuard   @SkipThrottle   FileInterceptor( without limits   fallbackToMimetype   originalname
HttpService   this.http.get(   @Redirect   host: '0.0.0.0'   Transport.TCP   synchronize: true   DevtoolsModule   exception.stack

Common false positives

Do not report these without further evidence:

  • @Public() on login, register, refresh, health and signature-verified webhook routes. Report only when the route exposes sensitive data or state change.
  • A controller without @UseGuards when a global APP_GUARD authenticates every non-@Public() route.
  • getAllAndOverride(KEY, [handler, class]) public checks and AuthGuard subclasses that keep the default throwing handleRequest.
  • JwtService.decode() on a token this process just signed, or after verify.
  • Parameterized queries ($1, :name bindings, tagged Prisma $queryRaw) and interpolated identifiers taken from a constant allow-list map.
  • ParseUUIDPipe ids that are then looked up with an owner or tenant filter.
  • @Excluded fields on responses covered by a bound ClassSerializerInterceptor, and explicit response DTOs with @Expose plus excludeAll.
  • FileTypeValidator with an anchored type on memory storage (magic-number check) plus limits, random file names and private storage.
  • Signature-verified webhooks (HMAC over rawBody with timingSafeEqual) and Bearer-only APIs without CSRF middleware.
  • Swagger or GraphiQL in non-production and Apollo introspection when NODE_ENV=production is verified.
  • Default Nest 500 responses (Internal server error) and X-Powered-By: Express alone (Informational).
  • TCP microservices on localhost/private networks called only by trusted services (state the assumption; Hardening).

Severity calibration

PatternTypical severity
Auth bypass: fail-open global guard, forgeable JWT secret, middleware bypass on Fastify protecting auth, unauthenticated admin controllerCritical (High if it needs a low-privilege account)
Unauthenticated TCP/broker handler performing privileged workCritical when reachable beyond localhost
SQL/NoSQL injectionCritical unauthenticated; High authenticated
IDOR / BFLA, role escalation by mass assignmentHigh (Critical across tenants, for roles, money or credentials)
Credential/hash exposure in responsesHigh (Medium when the hash is the caller's own)
SSRF returning responsesHigh (Critical if cloud metadata is reachable)
Path traversal read / arbitrary file writeHigh / Critical
GraphQL field-level auth gap on sensitive fieldsHigh; introspection and no cost limits Medium/Low
WebSocket auth by client-supplied identityHigh
CORS reflection with credentials on cookie auth; missing CSRF on cookie authHigh / Medium
Weak password hashing, throttler not bound on loginMedium (High for unsalted fast hashes with a data leak path)
Swagger UI in production, verbose errors, missing headersLow/Medium; Hardening for headers
Open redirect aloneLow

Common under-ratings to avoid:

  • ThrottlerModule imported but ThrottlerGuard never bound is no rate limiting at all.
  • Singleton services holding per-request user state cause cross-user data exposure under concurrency.
  • Hybrid apps: a global JWT guard does not protect the TCP/broker handlers.

Build-mode guardrails

When writing Nest code, default to:

  1. Default-deny auth: one global auth guard (APP_GUARD) plus an explicit @Public(); read it with getAllAndOverride<boolean>(KEY, [getHandler(), getClass()]); handleRequest must throw when there is no user.
  2. Order guards deliberately: authentication, then roles/permissions, then throttling per need; bind RolesGuard wherever @Roles() is used.
  3. Global ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }), DTO classes (never interfaces/any/type-only imports), @ValidateNested() + @Type(), array bodies via ParseArrayPipe, separate create/update DTOs without privileged fields.
  4. Authorize objects in the service: scope every query by the caller or tenant from the verified identity; never keep the current user in a singleton field.
  5. Return response DTOs or bind ClassSerializerInterceptor globally with @Exclude/@Expose; never return raw entities with credential columns.
  6. Parameterize queries; allow-list sort fields; guard against undefined filters (dropped silently on TypeORM 0.3.x and Prisma); no string-built where/orderBy.
  7. JWT: secret from validated config (getOrThrow), pinned algorithms, expiresIn, issuer/audience, ignoreExpiration left false; Argon2id/bcrypt for passwords.
  8. main.ts: explicit CORS origins, helmet() or useSecurityHeaders() (12.1+), enableCsrfProtection() (12.1+) or csrf-csrf for cookie auth, correct trust proxy, body limits, Swagger/GraphiQL only outside production.
  9. Rate limit with ThrottlerGuard bound and strict @Throttle on login/reset/OTP, shared storage when multi-instance.
  10. Uploads: multer limits, memory or private storage, random names, anchored FileTypeValidator without fallback, no public upload directory.
  11. Gateways and microservices: authenticate in handleConnection() and per message, derive rooms from identity, bind TCP to localhost or use TLS plus a verified caller identity, inheritAppConfig: true in hybrid apps.
  12. Outbound requests: fixed hosts or allow-lists with resolved-IP checks, no redirects to unvalidated hosts; @Redirect only to validated relative paths.
  13. Test the boundary with supertest using the same configureApp(app) as main.ts; do not override the guard under test.

Evidence and reporting rules

Use these when appsec-review is not installed.

Classification

  • Confirmed: the path from attacker input to impact is fully traced in code/config (including the guards, pipes and interceptors on the route), or it was safely demonstrated.
  • Likely: strong evidence, but one runtime condition (production NODE_ENV, a proxy or gateway, an env var, middleware applied outside the repo) couldn't be verified. Name it.
  • Hardening: not demonstrably exploitable, but a stronger control is recommended.
  • Informational: context with no direct security impact.

Severity follows impact x exploitability x required privileges x exposure. Don't raise severity because a scary keyword appears.

Finding format

### [SEVERITY] Title — Confirmed|Likely|Hardening
- **Location:** `src/invoices/invoices.controller.ts:42` (`GET /invoices/:id`)
- **Evidence:** the exact code/config, and how attacker input reaches it (effective guards, pipes, interceptors)
- **Impact:** what an attacker gains, and who the attacker must be
- **Preconditions:** auth level, configuration, deployment assumptions
- **Fix:** smallest Nest-native change (code snippet)
- **Verify:** supertest or request that proves the fix
- **Refs:** CWE / OWASP / official docs link

Rules: never invent files, routes, packages, advisory IDs or config values. Redact secrets (JWT_SECRET=ab****). Say explicitly when runtime verification wasn't performed. Only test applications the user is authorized to assess, using non-destructive checks.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.