84%
of security vulnerabilities could be prevented by proper code review, per SANS Institute research
200 LoC
per hour is the effective rate for security-focused code review -- beyond that, defect detection drops sharply
Semgrep
automates detection of 70%+ of OWASP Top 10 vulnerability classes, freeing manual review for logic flaws

Security code review is the practice of examining source code specifically to find security vulnerabilities -- not style issues, performance problems, or functional bugs (though those may be noted). It is the highest-signal security activity in the development lifecycle because it finds vulnerabilities in the specific context of your application's logic, not just against known patterns. This guide covers the methodology for effective security code review: what to look for, how to automate the mechanical vulnerability classes, and how to structure a review program that scales beyond one AppSec engineer.

What Security Code Review Finds That SAST Misses

SAST tools are excellent at finding well-defined vulnerability patterns: SQL injection via string concatenation, hardcoded credentials, unsafe deserialization. They struggle with vulnerabilities that require understanding business logic and intent.

Vulnerability classes best found by manual review:

1. Business logic flaws

A user can modify their subscription tier by changing a hidden form field. The code passes all SAST checks because there is no injection, no unsafe function -- the flaw is that the server trusts client-provided state.

# SAST sees: safe parameter access
# Reviewer sees: the tier should come from the user's database record, not the request
@app.route('/checkout', methods=['POST'])
def checkout():
    tier = request.form.get('tier')  # attacker sets tier='enterprise'
    price = TIER_PRICES[tier]        # gets enterprise price
    charge_user(price)               # charges enterprise price without verifying entitlement

2. Authentication and authorization logic

IDOR (Insecure Direct Object Reference), broken object-level authorization, privilege escalation through role assumption -- these require understanding which users should have access to which resources.

// SAST sees: normal database query
// Reviewer sees: no verification that the requesting user owns document_id
app.get('/document/:id', authenticate, async (req, res) => {
  const doc = await db.documents.findById(req.params.id);
  // Missing: if (doc.owner !== req.user.id) return res.status(403).send('Forbidden');
  res.json(doc);
});

3. Cryptographic misuse

Using encryption correctly but with wrong parameters -- ECB mode, predictable IVs, key reuse, using encryption when you need authenticated encryption.

4. Race conditions and TOCTOU

Time-of-check to time-of-use vulnerabilities require understanding concurrency and timing -- not detectable by static analysis.

5. Chained vulnerabilities

A low-severity SSRF + an internal metadata API + a cloud credentials endpoint = account takeover. No single finding is critical; the combination is. SAST evaluates each line independently; a reviewer sees the chain.

Effective review rate:

A security reviewer can cover approximately 200 lines of code per hour at a level of detail sufficient to catch logic-level issues. For a 5,000-line PR, that is 25 hours of review -- clearly impractical. Scope security review to the highest-risk code: authentication flows, authorization checks, cryptographic operations, external input handling, and privilege-changing operations.

Security Review Checklist by Vulnerability Class

Use this checklist when reviewing code in each risk category. Focus attention where it matters most.

Authentication and session management:

  • Passwords stored with Argon2id, bcrypt, or scrypt (never MD5, SHA-1, or unsalted SHA-256)
  • Session tokens generated with CSPRNG (not Math.random() or timestamp)
  • Session invalidated on logout (not just client-side cookie deletion)
  • Session ID rotated after privilege change (login, role elevation)
  • Account lockout after N failed attempts with appropriate reset window
  • MFA enrollment enforced for privileged operations, not optional
  • Password reset tokens are single-use, time-limited, and sent to verified email only

Authorization:

  • Every endpoint checks authorization -- not just authentication
  • Object-level authorization: verify the requesting user owns/has access to the specific resource (not just any resource of that type)
  • Role checks on every privileged operation -- no client-side role enforcement only
  • Vertical privilege escalation: can a regular user perform admin operations by manipulating parameters?
  • Horizontal privilege escalation: can user A access user B's data by changing an ID?

Input validation:

  • All input validated against allowlist (not denylist)
  • File uploads: validate type (magic bytes, not just extension), size, filename (path traversal), content scan
  • Redirects validated against an allowlist of allowed destinations
  • JSON/XML parsers: XXE disabled, schema validation applied

Injection:

  • All database queries parameterized (no string concatenation into SQL)
  • ORM used correctly -- raw query methods (raw(), execute()) avoided
  • Shell commands avoided; if required, arguments passed as array (not string)
  • Template engines: auto-escaping enabled; | safe filter and |raw used only where documented

Cryptography:

  • AES-256-GCM (or ChaCha20-Poly1305) for symmetric encryption -- not AES-CBC without MAC, not ECB
  • Nonces/IVs are unique per operation (random or counter-based, never constant)
  • No hardcoded secrets, keys, tokens, or passwords in source
  • External APIs secrets loaded from environment/secrets manager, not config files
  • JWT signature validation enforced with explicit algorithm (not alg: none possible)

Error handling:

  • Error messages to users are generic (no stack traces, no DB errors, no internal paths)
  • All errors logged with correlation ID for server-side investigation
  • Exceptions caught at appropriate level -- no uncaught exceptions exposing internals
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Automating Review with Semgrep and CodeQL

Automation handles the mechanical vulnerability classes, freeing manual reviewers to focus on logic and context.

Semgrep: rule-as-code for pattern matching

Semgrep matches code patterns with syntax awareness. Its rule language allows writing security rules that understand code structure, not just text patterns.

# Install Semgrep
pip install semgrep

# Run against a repository with OWASP Top 10 rules
semgrep --config p/owasp-top-ten --config p/secrets .

# Run with specific language security rules
semgrep --config p/python-security .    # Python
semgrep --config p/nodejs-security .   # Node.js
semgrep --config p/java-security .     # Java

# Write a custom rule (detect use of MD5 in Python)
# rules/no-md5.yaml:
# rules:
#   - id: no-md5-password-hash
#     patterns:
#       - pattern: hashlib.md5(...)
#     message: MD5 is not safe for password hashing. Use hashlib with sha256 or bcrypt.
#     severity: ERROR
#     languages: [python]

semgrep --config rules/no-md5.yaml .

CodeQL: dataflow analysis for complex vulnerabilities

CodeQL performs full program analysis, tracking data from sources (user input) to sinks (dangerous functions) through the entire codebase. It catches complex injection patterns that span multiple files and functions.

# Initialize CodeQL database for a Python project
codeql database create myapp-db --language=python --source-root=.

# Run the standard security query suite
codeql database analyze myapp-db   python-security-and-quality.qls   --format=sarif-latest   --output=results.sarif

# View results in VS Code with CodeQL extension, or upload to GitHub Code Scanning

Integrating findings into GitHub PRs:

# .github/workflows/security-review.yml
name: Security Code Review
on: [pull_request]
permissions:
  security-events: write
jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Semgrep Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/security-audit p/owasp-top-ten p/secrets
  codeql:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: python, javascript
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3
        with:
          upload: true  # uploads to GitHub Security tab

Findings appear as inline PR comments, blocking merge for configured severity levels.

Scoping and Prioritizing Manual Review

With limited AppSec capacity, manual review must be triaged to the highest-risk code.

Risk-based scoping criteria:

Review manually when the PR touches:

  • Authentication or session management logic
  • Authorization checks (permission gates, role verification)
  • Cryptographic operations (key generation, encryption, signing)
  • External input handling (new API endpoints, file upload handling)
  • Database query construction (especially raw/native queries)
  • Security-critical configuration (CORS, CSP, security headers)
  • Secrets or credential handling
  • New third-party library with significant capability (HTTP client, templating, ORM)
  • Privilege-changing operations (admin functions, payment operations)

Triage matrix:

Change TypeAppSec Review RequiredAutomated Check Sufficient
New authentication endpointYesNo
New API endpoint (any)Yes for Tier 1 appsYes for Tier 2-3
Dependency update (security fix)NoYes (SCA)
UI styling changeNoYes (SAST)
New admin featureYesNo
Bug fix (non-security)NoYes (SAST)
Configuration changeConsult securityYes (IaC scanner)

What to do when you cannot review everything:

  1. Security champions program: Train one developer per team as a security champion who conducts first-pass security review before AppSec engagement. Champions learn the review checklist and escalate ambiguous findings.

  2. Tiered review queues: Tier 1 app PRs touching security-sensitive code get same-day AppSec review; Tier 2 gets 2-day turnaround; Tier 3 relies on automated tools plus monthly spot-checks.

  3. Focused review sprints: Rather than reviewing every PR, schedule dedicated 2-hour security review sessions per team per sprint -- review the most significant changes from the sprint in depth.

Building a Security Champion Code Review Program

A security champions program trains senior developers to conduct first-pass security review, scaling AppSec capacity without proportionally scaling the AppSec headcount.

Security champion selection:

  • One champion per engineering team (6-10 developers typically)
  • Select from senior engineers with demonstrated quality mindset -- not junior developers
  • Champions volunteer or are nominated; forced participation rarely works
  • Time commitment: 2-4 hours per week for review and training

Training curriculum for security champions:

MonthFocusHands-On Activity
1OWASP Top 10 and application security fundamentalsComplete Portswigger Web Security Academy free labs
2Injection attacks (SQL, command, LDAP, template)Find injection in deliberately vulnerable app (DVWA, WebGoat)
3Authentication and authorization flawsPortswigger access control and authentication labs
4Cryptography for developersReview and fix intentionally broken crypto examples
5Threat modeling (STRIDE)Threat model a feature from your own codebase
6Using Semgrep and SAST toolsWrite a custom Semgrep rule for a pattern in your codebase

Champion review workflow:

  1. Champion receives PR notification for their team
  2. Champion checks the PR against the security review checklist
  3. Low-risk PRs: champion approves
  4. Uncertain findings: champion escalates to AppSec with specific question
  5. Confirmed security findings: champion opens security defect in tracker; PR blocked pending fix
  6. Monthly: AppSec reviews champion decisions to calibrate accuracy and provide feedback

Metrics for champion program:

  • Security defects found by champions vs. post-production findings (champions should catch before production)
  • Champion review coverage (% of security-sensitive PRs reviewed by champion)
  • Time from champion review to AppSec escalation (lower = champion is confident)
  • Champion retention rate (measure program health)

The bottom line

Effective security code review is a force multiplier: catching one authentication bypass during PR review prevents the breach that would cost millions to remediate. The practical approach is layered -- automate the mechanical vulnerability classes with Semgrep and CodeQL in CI, focus manual AppSec review on authentication, authorization, and cryptographic code, and build security champions to extend review capacity to every engineering team. Measure effectiveness by tracking the ratio of vulnerabilities found in review versus production -- the goal is to drive production security incidents toward zero.

Frequently asked questions

How is security code review different from a normal code review?

A standard code review focuses on correctness (does it do what it should?), readability, and maintainability. A security code review specifically asks: can this code be abused by an attacker? This requires a different mindset -- thinking adversarially about every input, every trust assumption, every data flow. A security reviewer asks 'what happens if the user sends -1 here? What if they send 10MB? What if the external API returns malicious data?' This adversarial thinking is not natural to developers who are focused on making things work, which is why dedicated security review -- either by AppSec specialists or trained security champions -- is necessary.

How many lines of code can be effectively reviewed per hour?

Research consistently shows that effective defect detection requires reviewing no more than 200-400 lines of code per hour. Beyond that rate, the probability of missing significant defects increases sharply. This means a 2,000-line PR requires 5-10 hours of dedicated security review -- which is why scoping is critical. Focus security review time on the highest-risk code paths (authentication, authorization, input handling, cryptography) rather than attempting comprehensive review of every line. The mechanical vulnerability classes (injection, hardcoded secrets) can be handled by automated tools at scale, reserving manual review capacity for logic-level issues.

What is a security champion and how do they differ from an AppSec engineer?

A security champion is a developer (not a security specialist) who has received additional security training and serves as the security advocate for their team. Champions conduct first-pass security review, help teammates understand security requirements, participate in threat modeling sessions, and escalate complex security questions to the AppSec team. They are not security engineers -- they do not perform penetration tests, configure security tools, or make final security decisions. The AppSec team trains and supports champions, reviews their decisions for calibration, and handles cases that require specialist expertise. One AppSec engineer can effectively support 8-12 security champions, allowing security review coverage for 80-120 developers.

Should we use Semgrep or CodeQL, or both?

Both, for different purposes. Semgrep is fast, developer-friendly, runs in seconds, and is ideal for enforcing coding standards and catching common vulnerability patterns (hardcoded secrets, unsafe functions, basic injection patterns). Its rules are easy to write and customize for your codebase-specific patterns. CodeQL performs deep dataflow analysis -- tracking user input from source to dangerous sink across multiple function calls and files -- catching complex injection vulnerabilities that span the codebase. CodeQL takes longer to run (minutes) and is harder to write custom rules for. The practical setup: Semgrep runs on every PR for fast feedback; CodeQL runs on PR merge or nightly for deep analysis.

How do we handle developer pushback on security review findings?

Pushback typically falls into three categories: (1) 'This is a false positive' -- engage with the specific finding, understand the context, and either confirm it is a false positive (document and suppress) or explain why the concern is valid with a concrete attack scenario; (2) 'It is not exploitable in our environment' -- document this as a risk acceptance decision with the developer's and their manager's acknowledgment, tracked in your finding management system; (3) 'We do not have time to fix it' -- use SLA data to show the cost of deferral, and offer to help scope a minimal fix. Never argue in the abstract about security -- always connect findings to concrete attack scenarios and business impact. Developers respond to 'an attacker can do X and cause Y impact' far better than 'this violates CWE-89.'

What is the best way to get started with security code review if we have no AppSec team?

Start with automated tools that require minimal expertise: enable Dependabot or Snyk for SCA (catches vulnerable dependencies in minutes), add Semgrep with the p/security-audit ruleset to your CI pipeline (catches common vulnerability patterns on every PR), and enable GitHub's built-in secret scanning (catches hardcoded credentials). These three steps take under a day to configure and provide continuous coverage with no ongoing AppSec time investment. For manual review, identify 2-3 senior developers willing to do security champion training -- the PortSwigger Web Security Academy is free and provides excellent hands-on training. Engage an external AppSec firm for annual review of your highest-risk application to calibrate your internal program.

Sources & references

  1. OWASP Code Review Guide
  2. Semgrep Security Rules Registry
  3. GitHub CodeQL Documentation
  4. PortSwigger Web Security Academy (Free)

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, weekly, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals weekly.

Free Brief

The Mythos Brief is free.

AI that finds 27-year-old zero-days. What it means for your security program.

Joins Decryption Digest. Unsubscribe anytime.

Daily Briefing

Get briefings like this every morning

Actionable threat intelligence for working practitioners. Free. No spam. Trusted by 50,000+ SOC analysts, CISOs, and security engineers.

Unsubscribe anytime.

Mythos Brief

Anthropic's AI finds zero-days your scanners miss.