Security Review
How to perform a security review of a codebase: dependency auditing, static analysis, code-level vulnerability checks, and infrastructure review patterns.
Security Review Overview
A security review has five layers, from automated tooling (fast, catches common issues) to manual code review (slow, catches logic vulnerabilities). An AI can effectively perform the first four layers. The fifth layer (threat modeling) benefits from human domain knowledge but AI can assist.
Dependency Audit
Check for known vulnerabilities in npm/pip/cargo packages
Static Analysis
Full quality + security ESLint config: naming conventions, complexity limits, type imports, hooks rules, security plugins
Code Review
Manual review of auth, input validation, data handling, secrets
Configuration Review
Environment variables, CORS, headers, TLS, cookie settings
Threat Modeling
Attack surface mapping, data flow analysis, trust boundaries
Layer 1: Dependency Audit
Check all third-party packages for known CVEs (Common Vulnerabilities and Exposures). This is the fastest check and catches the most common attack vector — vulnerable dependencies.
Dependency Audit Commands
# Node.js / npm
npm audit # Check for known vulnerabilities
npm audit --audit-level=high # Only show high/critical severity
npm audit fix # Auto-fix where possible (minor/patch updates)
# Python / pip
pip audit # Requires pip-audit package
safety check # Alternative: safety package
# Rust / cargo
cargo audit # Requires cargo-audit
# General (works with any ecosystem)
# Use Snyk, Dependabot, or Trivy for cross-ecosystem scanningLayer 2: Static Analysis — Full Quality + Security Config
The security plugins (eslint-plugin-security, eslint-plugin-sonarjs) catch maybe 20% of what a full quality-focused ESLint config catches. The insight: most security vulnerabilities start as code quality issues — overly complex functions that are hard to review, inconsistent types that cause runtime surprises, stale variable names from refactoring that slip through. Defense in depth through code quality.
Install the full plugin set
npm install -D eslint-plugin-security eslint-plugin-sonarjs \
typescript-eslint eslint-plugin-react eslint-plugin-react-hooks \
eslint-config-prettier eslint-plugin-prettierThe five rule categories that matter most, beyond the security plugins:
Naming Conventions
Why it matters for security: Catches refactoring mistakes where renamed classes or stale variable names slip through. A function named getUserData that now returns an admin object is a security issue waiting to happen.
camelCase for variables and functions
PascalCase for classes, interfaces, and React components
UPPER_CASE for enum members and module-level constants
Prefix interfaces with I is optional but must be consistent
// eslint.config.mjs — naming convention rules
{
'@typescript-eslint/naming-convention': ['error',
{ selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'] },
{ selector: 'function', format: ['camelCase', 'PascalCase'] },
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'enumMember', format: ['UPPER_CASE'] },
],
}Complexity Limits
Why it matters for security: Functions that are too complex are too hard to review for security issues. A 500-line function with 8 levels of nesting cannot be audited reliably — by humans or AI.
max-lines: 500 per file (warn) — large files hide problems
max-depth: 4 (warn) — deeply nested code is hard to reason about
complexity: 20 (warn) — cyclomatic complexity limit per function
sonarjs/cognitive-complexity: 15 (warn) — human-readable complexity measure
// eslint.config.mjs — complexity rules
{
'max-lines': ['warn', { max: 500, skipBlankLines: true, skipComments: true }],
'max-depth': ['warn', 4],
'complexity': ['warn', 20],
'sonarjs/cognitive-complexity': ['warn', 15],
}Consistent Type Imports
Why it matters for security: Catches runtime vs. type import confusion. Importing a class at runtime when you only need its type causes bundle bloat and can trigger side effects (database connections, API calls) at import time.
Enforce import type { Foo } for type-only imports
Prevents accidentally importing runtime code for type-only usage
Enables better tree-shaking and faster builds
// eslint.config.mjs — type import rules
{
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
}],
'@typescript-eslint/no-import-type-side-effects': 'error',
}React Hooks Rules
Why it matters for security: The most common source of React bugs. Stale closures in useEffect can cause auth state to be read from a previous render — a user who logged out might still see protected data.
rules-of-hooks: error — hooks must be called at the top level, not conditionally
exhaustive-deps: warn — all dependencies must be in the dependency array
Stale deps cause components to use outdated auth state, user data, or permissions
// eslint.config.mjs — React hooks rules
{
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
}Explicit Return Types
Why it matters for security: Forces developers to think about what functions return. Catches accidental data leaks where a function returns more fields than intended — a common source of over-exposure in API responses.
Require explicit return types on exported functions
Allow inference for simple arrow functions and callbacks
Catches when a refactor accidentally changes what an API endpoint returns
// eslint.config.mjs — return type rules
{
'@typescript-eslint/explicit-function-return-type': ['warn', {
allowExpressions: true,
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
}],
}Combined, these rules plus the security plugins give you a config that catches naming mistakes, complexity that hides bugs, type confusion, React state bugs, and direct security anti-patterns. Here's the full install:
eslint.config.mjs — Complete Recommended Config
import js from '@eslint/js';
import nextPlugin from '@next/eslint-plugin-next';
import tseslint from 'typescript-eslint';
import security from 'eslint-plugin-security';
import sonarjs from 'eslint-plugin-sonarjs';
import reactHooks from 'eslint-plugin-react-hooks';
import prettierConfig from 'eslint-config-prettier';
import prettier from 'eslint-plugin-prettier';
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
security.configs.recommended,
sonarjs.configs.recommended,
prettierConfig,
{
plugins: {
'@next/next': nextPlugin,
'react-hooks': reactHooks,
prettier,
},
rules: {
// TypeScript quality
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'@typescript-eslint/explicit-function-return-type': ['warn', {
allowExpressions: true,
allowTypedFunctionExpressions: true,
}],
// Naming conventions
'@typescript-eslint/naming-convention': ['error',
{ selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'] },
{ selector: 'function', format: ['camelCase', 'PascalCase'] },
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'enumMember', format: ['UPPER_CASE'] },
],
// Complexity limits
'max-lines': ['warn', { max: 500, skipBlankLines: true, skipComments: true }],
'max-depth': ['warn', 4],
'complexity': ['warn', 20],
// React hooks
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// Code quality
'no-console': ['warn', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
// Formatting
'prettier/prettier': 'error',
},
},
);Key patterns that eslint-plugin-security catches (still important, now part of a larger config):
- detect-eval-with-expression: Use of
eval()with dynamic input (code injection risk) - detect-non-literal-fs-filename: Dynamic file paths in
fsoperations (path traversal risk) - detect-non-literal-require: Dynamic
require()calls (code injection risk) - detect-possible-timing-attacks: String comparison vulnerable to timing attacks
- detect-child-process: Use of
child_process(command injection risk)
Layer 3: Code-Level Security Review
Manual (or AI-assisted) review of code areas that are most commonly vulnerable. Focus on these categories in order of criticality:
Authentication & Authorization
Password hashing uses bcrypt/argon2/scrypt (NOT MD5/SHA)
Session tokens are cryptographically random (crypto.randomBytes, not Math.random)
JWT tokens have expiration, audience, and issuer validation
Protected routes check auth before processing (middleware, not per-endpoint)
Role checks use allowlists ("is admin?"), not denylists ("is not banned?")
Failed login attempts are rate-limited
Password reset tokens are single-use and expire
Search for: bcrypt|argon2|jwt|session|auth|login|password|token
Input Validation
All API endpoints validate input with a schema (Zod, Joi, etc.)
No raw user input is passed to SQL queries (use parameterized queries / ORM)
No raw user input is inserted into HTML (use framework escaping / DOMPurify)
File uploads validate type, size, and content (not just extension)
URL/redirect parameters are validated against an allowlist
Search for: req.body|req.query|req.params|user input|innerHTML|dangerouslySetInnerHTML
Data Exposure
API responses do NOT include password hashes, internal IDs, or stack traces
Error messages are generic for users, detailed in server logs only
Debug/development endpoints are disabled in production
Sensitive data (PII, financial) is encrypted at rest
Logs do NOT contain passwords, tokens, or PII
Search for: console.log|console.error|password|secret|token|apiKey|API_KEY
Secrets Management
No hardcoded secrets in source code (API keys, passwords, tokens)
.env files are in .gitignore
No secrets in client-side code (check process.env usage in components)
Docker images don't bake in secrets (use runtime env vars)
CI/CD secrets use the platform's secret management (GitHub Secrets, etc.)
Search for: process.env|API_KEY|SECRET|PASSWORD|TOKEN|credentials
Layer 4: Configuration Review
Review application and infrastructure configuration for security misconfigurations:
Configuration Checklist
HEADERS & CORS
□ Content-Security-Policy header is set (blocks XSS, inline scripts)
□ X-Content-Type-Options: nosniff
□ X-Frame-Options: DENY (or SAMEORIGIN if iframes are needed)
□ Strict-Transport-Security header (HSTS)
□ CORS allows only specific origins, not wildcard (*)
COOKIES
□ Session cookies have HttpOnly flag (not accessible via JavaScript)
□ Session cookies have Secure flag (HTTPS only)
□ SameSite=Strict or SameSite=Lax on session cookies
□ Cookie domain is scoped correctly (not too broad)
ENVIRONMENT
□ Debug mode is OFF in production
□ Stack traces are hidden from users in production
□ Database connections use TLS/SSL
□ Admin/management endpoints are not publicly accessible
□ Rate limiting is configured on auth and API endpoints
DOCKER / INFRASTRUCTURE
□ Container runs as non-root user
□ Base image is recent and not EOL
□ Only necessary ports are exposed
□ Health check endpoint exists and is monitored
□ Secrets are injected at runtime, not baked into imageAI Security Review Playbook
When the user asks for a security review, follow these steps in order. Produce a summary report at the end with findings categorized by severity (Critical, High, Medium, Low, Info).
AI Security Review — Step by Step
STEP 1: DEPENDENCY AUDIT
Run: npm audit (or equivalent for the project's package manager)
Report: List any high/critical vulnerabilities with package name and CVE
STEP 2: STATIC ANALYSIS CHECK
Run the full ESLint config (not just security plugins):
npm run lint
Look for violations in these categories:
- Naming convention errors (renamed variables that don't match their type)
- Complexity warnings (functions over 20 cyclomatic complexity)
- Explicit return type warnings on exported functions (data leaks)
- React hooks exhaustive-deps warnings (stale auth state risk)
- Security plugin findings (eval, dynamic require, child_process)
If the project only has eslint-plugin-security and eslint-plugin-sonarjs,
recommend adding the full quality config (see Layer 2 on this page).
STEP 3: SECRETS SCAN
Search codebase for hardcoded secrets:
- grep -r "API_KEY|SECRET|PASSWORD|TOKEN" --include="*.ts" --include="*.js"
- Check .env files exist in .gitignore
- Check no .env files are committed (git log --all --diff-filter=A -- "*.env")
- Check client-side code doesn't reference server-only env vars
STEP 4: AUTH REVIEW
Find authentication code (search for: auth, login, session, jwt, bcrypt)
- Verify password hashing algorithm (must be bcrypt/argon2/scrypt)
- Verify session/JWT configuration (expiry, httpOnly, secure)
- Verify protected routes have auth middleware
- Check for role-based access control on admin endpoints
STEP 5: INPUT VALIDATION REVIEW
Find API route handlers (search for: route.ts, api/, POST, PUT, PATCH)
- Verify every endpoint validates input with a schema library
- Verify no raw SQL queries (parameterized queries or ORM only)
- Check for any use of innerHTML or dangerouslySetInnerHTML
- Verify file upload handling (if applicable)
STEP 6: CONFIGURATION REVIEW
Check these files:
- next.config.ts / vite.config.ts — security headers configured?
- middleware.ts — auth checks on protected routes?
- docker-compose.yml / Dockerfile — non-root user? minimal ports?
- .env.example — are required secrets documented?
STEP 7: PRODUCE REPORT
Format:
## Security Review Report — {Project Name}
Date: {date}
Reviewer: AI Security Review
### Critical Findings
(Issues that must be fixed immediately — active vulnerabilities)
### High Findings
(Issues that should be fixed soon — potential attack vectors)
### Medium Findings
(Issues to address in next sprint — defense-in-depth improvements)
### Low Findings
(Minor improvements — best practices not yet followed)
### Informational
(Observations and recommendations for future consideration)
### Summary
Total findings: X critical, X high, X medium, X low, X infoSetting Up Ongoing Security
A one-time review isn't enough. Set up these automated checks to catch security issues continuously:
- ESLint security plugin in the project's lint config — catches code-level issues on every save.
npm auditin CI — block merges if high/critical vulnerabilities are found in dependencies.- Dependabot / Renovate — automated PRs for dependency updates.
- Secret scanning — GitHub secret scanning or
gitleaksin CI to catch committed secrets. - Include security check in BobTheBuilder — add
npm audit --audit-level=highas a step in the pre-deployment check pipeline.