security-checklist · git:20260610.47ec162 · 2026-06-10 · sha256 d809b970ddde9c87
security-checklist git:20260610.47ec162A
Immutable. This exact content never changes and is served at /api/v1/blob/d809b970ddde9c87.
--- name: security-checklist description: Security review checklist covering OWASP Top 10, GraphQL-specific attacks, auth/authz validation, and dependency auditing for a NestJS + GraphQL backend --- # Security Checklist Skill ## Scope Apply this skill during security reviews of code changes. This checklist is tailored for a **NestJS + GraphQL + Prisma + TypeScript** backend that handles authentication, authorization (RBAC), and user data. --- ## OWASP Top 10 — Applied to This Stack ### 1. Injection **GraphQL/Prisma context**: Prisma parameterizes all queries by default, but raw queries (`$queryRaw`, `$executeRaw`) bypass this. - [ ] No use of `$queryRaw` or `$executeRaw` with string interpolation - [ ] All user input passed through Prisma's parameterized API - [ ] No dynamic field names derived from user input in queries - [ ] No `eval()`, `new Function()`, or `child_process.exec()` with user input ### 2. Broken Authentication - [ ] Passwords hashed with bcrypt (cost factor >= 10) - [ ] JWT tokens have reasonable expiration (access: 15min, refresh: 7d) - [ ] Refresh token rotation — old refresh tokens are invalidated on use - [ ] Failed login attempts are rate-limited - [ ] Password reset tokens are single-use and time-limited - [ ] No credentials (passwords, tokens, API keys) in logs, responses, or error messages ### 3. Broken Access Control - [ ] Every GraphQL resolver has appropriate auth guards (`@UseGuards(GqlAuthGuard)`) - [ ] Public endpoints are explicitly marked with `@Public()` decorator - [ ] Permission checks use `@RequirePermissions()` decorator for RBAC - [ ] Users cannot access other users' data — verify ownership in service layer - [ ] Admin-only operations verify admin role, not just authentication - [ ] No horizontal privilege escalation — user A cannot modify user B's resources ### 4. Security Misconfiguration - [ ] CORS is configured to allow only known origins — never `*` in production - [ ] GraphQL introspection is disabled in production - [ ] Debug/verbose error messages are not exposed in production responses - [ ] Environment variables are validated at startup (Zod schema in `config.validation.ts`) - [ ] Default credentials are not present in any configuration ### 5. Sensitive Data Exposure - [ ] Passwords are never returned in GraphQL responses - [ ] Email addresses are masked in logs (`mask-email.ts`) - [ ] JWT secret is not hardcoded — sourced from environment variables - [ ] No PII (emails, names, addresses) in info-level logs - [ ] Database connection strings are not logged ### 6. Rate Limiting - [ ] GraphQL endpoint has rate limiting via `GqlThrottlerGuard` - [ ] Login endpoint has stricter rate limits than general queries - [ ] Password reset has rate limiting to prevent abuse - [ ] Rate limit headers are returned to clients ### 7. Cross-Site Scripting (XSS) - [ ] GraphQL responses do not render user input as HTML - [ ] Input validation strips or rejects HTML/script tags where inappropriate - [ ] Content-Type headers are set correctly on all responses --- ## GraphQL-Specific Security ### Query Depth and Complexity - [ ] Query depth limit is enforced (max 10 levels) - [ ] Query complexity limit is enforced (max 1000 points) - [ ] Expensive fields have complexity annotations - [ ] No unbounded list queries — all collections are paginated ### Batching Attacks - [ ] Batch queries are limited (no more than 5 operations per request) - [ ] Alias-based attack prevention — same field with multiple aliases is counted toward complexity ### Schema Exposure - [ ] Introspection is disabled in production - [ ] Internal fields marked with `@inaccessible` in federation - [ ] Error messages do not reveal schema structure or field names ### N+1 and Resource Exhaustion - [ ] DataLoaders are used for all `@ResolveField` that can appear in lists - [ ] No unbounded database queries (always use `take`/`limit`) - [ ] Pagination enforces a maximum page size --- ## Authentication & Authorization Deep Checks ### JWT Validation - [ ] JWT signature is verified on every request - [ ] JWT expiration (`exp`) is checked - [ ] JWT audience (`aud`) and issuer (`iss`) are validated if set - [ ] Revoked tokens are rejected (check against refresh token store) ### RBAC Validation - [ ] Permissions are checked at the resolver level, not just the client level - [ ] Role assignments require admin permission - [ ] Permission changes take effect immediately (no stale cache) - [ ] Superadmin/system roles cannot be deleted or modified through the API ### Password Security - [ ] Minimum password length enforced (>= 8 characters) - [ ] Password is validated with Zod schema before hashing - [ ] Old password is required for password change (not reset) - [ ] Password reset does not reveal whether an email exists in the system --- ## Dependency Security - [ ] `npm audit` reports no critical or high vulnerabilities - [ ] No dependencies with known CVEs in production - [ ] `package-lock.json` is committed and matches `package.json` - [ ] No unnecessary permissions granted to dependencies --- ## Data Protection ### Database - [ ] Prisma schema enforces `@unique` constraints where appropriate - [ ] Cascade deletes are intentional and documented - [ ] Sensitive fields (password hashes) are excluded from default selects - [ ] Database credentials use least-privilege access ### Input Validation - [ ] All GraphQL inputs are validated with Zod schemas - [ ] String inputs have maximum length constraints - [ ] Numeric inputs have range constraints where applicable - [ ] Email inputs are validated with proper email regex - [ ] UUID inputs are validated as proper UUIDs --- ## Review Output Format Present security findings using the same priority system as the Code Reviewer: ``` 🔴 **SECURITY: [Category]** File: path/to/file.ts, Line XX Description of the vulnerability and its impact. **Risk**: What an attacker could do. **Fix**: Specific remediation steps. ``` | Priority | Criteria | |---|---| | 🔴 Blocker | Exploitable vulnerability, data exposure, auth bypass | | 🟡 Suggestion | Defense-in-depth improvement, hardening, best practice gap | | 💭 Nit | Minor improvement, style preference, documentation gap | --- ## Hard Rules - Every resolver must have an auth guard — `@Public()` is the explicit opt-out - Never log credentials, tokens, or secrets - Never return password hashes in API responses - Never use `$queryRaw` with string interpolation - Never disable CORS, rate limiting, or auth guards in production - Always validate all user input at the resolver boundary - Always run `npm audit` as part of the security review