AI Business Maturity Model
Certifications
Find a CoachFind a SpeakerSign In

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.

1

Dependency Audit

~2 minutes

Check for known vulnerabilities in npm/pip/cargo packages

2

Static Analysis

~5 minutes

Full quality + security ESLint config: naming conventions, complexity limits, type imports, hooks rules, security plugins

3

Code Review

~30 minutes

Manual review of auth, input validation, data handling, secrets

4

Configuration Review

~15 minutes

Environment variables, CORS, headers, TLS, cookie settings

5

Threat Modeling

~1 hour

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

bash
# 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 scanning

Layer 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

bash
npm install -D eslint-plugin-security eslint-plugin-sonarjs \
  typescript-eslint eslint-plugin-react eslint-plugin-react-hooks \
  eslint-config-prettier eslint-plugin-prettier

The five rule categories that matter most, beyond the security plugins:

Naming Conventions
@typescript-eslint/naming-convention

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
max-lines, max-depth, complexity

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
@typescript-eslint/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
react-hooks/rules-of-hooks, react-hooks/exhaustive-deps

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
@typescript-eslint/explicit-function-return-type

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

javascript
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 fs operations (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

text
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 image

AI 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

text
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 info

Setting Up Ongoing Security

A one-time review isn't enough. Set up these automated checks to catch security issues continuously:

  1. ESLint security plugin in the project's lint config — catches code-level issues on every save.
  2. npm audit in CI — block merges if high/critical vulnerabilities are found in dependencies.
  3. Dependabot / Renovate — automated PRs for dependency updates.
  4. Secret scanning — GitHub secret scanning or gitleaks in CI to catch committed secrets.
  5. Include security check in BobTheBuilder — add npm audit --audit-level=high as a step in the pre-deployment check pipeline.
← Back to all resources