From security-review
Review JWT strategies, auth guards, middleware, and security config for Lety 2.0 Backend. Flags vulnerabilities against NestJS docs and OWASP best practices. Triggered when the user asks to review, audit, or fix auth/security code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/security-review:security-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are performing a **security audit** of the Lety 2.0 Backend (NestJS + Fastify + gRPC + RabbitMQ).
You are performing a security audit of the Lety 2.0 Backend (NestJS + Fastify + gRPC + RabbitMQ).
Priority rule: Always follow NestJS official docs, OWASP, and JWT best practices. If existing code has a vulnerability or misconfiguration, flag it with severity and provide the corrected version — even if it's in production today.
HTTP Request
└── Fastify middleware (CORS, Helmet, Cookie, CSRF)
└── DomainResolutionMiddleware (multi-tenant routing)
└── JwtAuthGuard (global) — validates access token
└── PermissionsGuard (global) — CASL ability check
└── ThrottlerGuard (global) — rate limiting
└── Controller → gRPC → Microservice
Authentication cookie or Authorization: Bearer headerEncryptionTransformer)Determine what the user has provided:
| Scope | What to check |
|---|---|
main.ts | CORS, Helmet, Cookie config, CSRF, ValidationPipe, global guards order |
JWT strategy (jwt.strategy.ts) | Token extraction sources, signature algorithm, expiration check, session validation |
Auth guard (jwt-auth.guard.ts, permissions.guard.ts) | canActivate logic, public route bypass, impersonation handling |
| API key guard | Plaintext vs hashed comparison, timing-safe equality |
Token generation (auth.service.ts) | Payload claims, algorithm, expiration, session storage |
| Refresh token flow | Hash comparison, device binding, rotation |
| WebSocket guard | Token extraction from handshake, fallback risks |
| Webhook guard | HMAC signature verification, timing-safe comparison |
| Generic service/controller | Missing @UseGuards, exposed endpoints, improper error leakage |
main.ts)@fastify/helmet with strict CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosnifforigin: true is a critical vulnerability; must whitelist specific domains:
app.enableCors({
origin: configService.get<string>('ALLOWED_ORIGINS').split(','),
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
});
secure flag — cookies must have secure: true and sameSite: 'strict' (or 'lax' minimum) in productionwhitelist: true (strips unknown properties) and forbidNonWhitelisted: true@fastify/csrf-protection must be registered; verify it's not disabled for API-key routes unintentionallyJwtAuthGuard → PermissionsGuard → ThrottlerGuardalgorithms: ['HS256', 'RS256'] together; pin one via env JWT_ALGORITHMverify() must check exp claim; never use ignoreExpiration: true outside of refresh strategyuserId + sessionId + agencyId match the Redis sessiondecoded.isPlatformUser flag must be validated against the correct secret, not just decoded without verification first@IsPublic() bypass scope — public endpoints must be explicitly listed; default must be authenticatedcanActivate returns boolean — never throw untyped errors inside guards; use UnauthorizedException or ForbiddenException onlyAbilityFactory)crypto.timingSafeEqual(); plaintext === is vulnerable to timing attacks:
// BAD
if (apiKey === storedKey) { ... }
// GOOD
const a = Buffer.from(apiKey);
const b = Buffer.from(storedKey);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new UnauthorizedException();
}
jti (JWT ID) to enable per-token revocation without invalidating the full sessioniat claim present — tokens must include iat for audit and replay detectionhandshake.auth — prefer socket.handshake.auth.token; avoid falling back to query params (logged by proxies)WsGuard.canActivate, not inside event handlerssocket.disconnect(true) if token invalid; do not let connection lingercrypto.timingSafeEqual() for signature comparisonapp.useGlobalFilters() must strip stack traces in production; NODE_ENV=production check"Invalid credentials" not "User not found" or "Wrong password" (user enumeration)RpcToHttpInterceptor must map all errors; uncaught gRPC errors must not expose internal details| Severity | Criteria | Example |
|---|---|---|
| CRITICAL | Exploitable without auth, data exposure, auth bypass | origin: true CORS, plaintext API key comparison |
| HIGH | Exploitable with valid session, privilege escalation | Missing Helmet, algorithm confusion attack, no token rotation |
| MEDIUM | Increases attack surface, weakens defense-in-depth | Missing jti, no device binding warning, long-lived access tokens |
| LOW | Best practice gap, no direct exploit path | Missing iat claim, audit logging gaps |
| Anti-pattern | Issue | Fix |
|---|---|---|
origin: true in CORS | Any origin can make credentialed requests | Whitelist ALLOWED_ORIGINS env |
No @fastify/helmet | Missing security headers (CSP, XFO, etc.) | Register helmet with strict config |
ignoreExpiration: true on access token strategy | Tokens never expire | Remove; only valid on refresh strategy |
=== for API key comparison | Timing attack leaks key length/prefix | Use crypto.timingSafeEqual() |
| JWT payload contains email/role name strings | Increases payload size, avoid PII in tokens | Use IDs + enum codes only |
algorithms: ['HS256', 'RS256'] list | Algorithm confusion attack vector | Pin single algorithm from env |
| Refresh token stored unhashed | Token theft = account takeover | Hash with PBKDF2/argon2 before storage |
| Stack trace in error response | Internal path/dependency exposure | Filter in global exception filter |
"User not found" auth error | User enumeration | Return generic "Invalid credentials" |
| Guard registered without dependency order | PermissionsGuard before JwtAuthGuard | Fix APP_GUARD provider order |
| No CSRF on cookie-based auth | Cross-site request forgery | Ensure @fastify/csrf-protection active |
| Public WebSocket query param token | Token logged by proxies/CDNs | Use handshake.auth.token only |
For reviews: list all findings grouped by severity (CRITICAL → LOW), then show corrected code for each.
For new code: show complete implementation with all security properties applied.
Use this format per finding:
[SEVERITY] File: path/to/file.ts — Line: N
Issue: <what is wrong>
Risk: <what an attacker can do>
Fix:
<corrected code snippet>
origin: true in production CORS — it is always a vulnerability@IsPublic() must be an opt-in exception, never the defaultProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub lety-ai/lety-skill-hub --plugin security-review