Guides15 min read

Secure Code Review Checklist: A Practical Reviewer Guide

A secure code review is what catches the bugs that scanners cannot see: missing authorisation, weak validation, broken business logic, and crypto choices that look fine but are not. This checklist is written for the reviewer sitting in front of the code, not for the manager tracking the engagement. It covers what to do before reading the first file, what order to read the code in, what to look for in each layer, common language pitfalls, and how to write up findings that developers can actually act on. It pairs naturally with SAST and SCA scanning run before the manual pass, and with the code review engagement workflow for tracking the deliverable end to end.

Why Manual Review Still Matters

Static analysis is excellent at known patterns: a tainted variable reaching a sink, a hardcoded API key, a vulnerable dependency. Manual review is where you catch the bugs that depend on context: a permission check that exists but on the wrong identity, a function that returns more data than the caller is allowed to see, an authentication path that works correctly until you replay the request without the cookie, a feature flag that bypasses authorisation in a non-production environment that is reachable from production. None of those are easy for a tool to flag with low false-positive rates.

The reviewer brings two things the tools do not: an attacker mindset and an understanding of business intent. The checklist that follows is a structured way to apply both consistently across a codebase.

For internal AppSec organisations that cannot review every pull request from a central function, distribute first-pass review into product teams via a security champions programme. Our security champions program guide covers selection, training, and the finding-handoff workflow that lets champions catch most of the noise at the source while the central reviewer keeps the high-risk sign-off.

Step 1: Prepare Before You Read Any Code

Most weak reviews are weak because the reviewer started reading code immediately. A useful manual review is preceded by a short prep pass that gives every later finding a place to land.

  • Confirm scope in writing. Which repositories, which branches, which commit, which subset of services. Out of scope items must be named explicitly. This is the same discipline as the pentest scope of work.
  • Read the architecture notes. Five minutes with a system diagram beats two hours of guessing. If there is no diagram, sketch one from the README and the deployment manifests. Identify trust boundaries, external entry points, and data stores.
  • Run the tools first. Run SAST and SCA against the target commit, triage the output, and bring legitimate findings into the review register before manual reading starts. SecPortal supports Semgrep SAST and SCA on connected GitHub, GitLab, and Bitbucket repositories through the code scanning feature.
  • Read the threat model if one exists. If a threat model has been produced, use it to weight your time toward the highest-risk components. If not, build a lightweight one in your head.
  • Set up a finding register. Decide where each finding will live before you find one. PR comments and Slack threads disappear; a proper register in findings management with CVSS 3.1 vectors, file and line references, and a status field is the difference between a deliverable and a recap.

Step 2: Read in the Right Order

Reading top to bottom by file path is the slowest way to find security bugs. A risk-first reading order surfaces the highest-impact issues sooner.

  1. Configuration and bootstrap. Read the dependency manifest, the build config, the runtime config, environment loading, framework configuration, and security middleware registration. Misconfigurations here often undo everything below.
  2. Authentication. Login, signup, password reset, session creation, MFA, federation. Read every flow start to finish.
  3. Authorisation. The middleware, the role and permission model, where checks are applied, and crucially where they are missing.
  4. Public entry points. Every controller, route handler, GraphQL resolver, RPC method, and webhook handler that is reachable from outside the trust boundary.
  5. Data layer. Queries, ORM usage, raw SQL, file access, external API clients, and message queue consumers.
  6. Cryptography and secrets. Where keys live, how tokens are signed, what algorithms are chosen, and how secrets are loaded.
  7. Business logic that involves money, identity, or data sensitivity. Pricing, invoicing, account merging, role escalation, exports, and admin actions.

Each layer has its own checklist below. Skim them once before you start so you know what you are looking for.

Trust Boundaries and Untrusted Input

  • Every external entry point is identified and listed: HTTP, GraphQL, gRPC, webhooks, message queues, file uploads, scheduled jobs that read external state.
  • Input is validated at the trust boundary, not deep inside the call stack.
  • Validation is positive (allowlist) where possible, not negative (blocklist).
  • Validation includes type, length, range, character set, and structural rules. Strings are not assumed to be safe just because they parsed as JSON.
  • Validation rejects, it does not silently coerce. A user submitting role=admin is denied, not stripped to role=user.
  • Server-side validation is present even when client-side validation already exists.
  • Decoded or transformed input (base64, URL decoding, JSON parsing, deserialisation) is treated as untrusted again after each transform.

Authentication

  • Passwords are hashed with a memory-hard algorithm (Argon2id, scrypt, or bcrypt) with parameters tuned for current hardware.
  • Login responses do not leak whether the username exists. Failed login messages are uniform.
  • Rate limiting is applied to login, password reset, MFA verification, and signup. See missing rate limiting.
  • Password reset tokens are single-use, short-lived, and bound to the user.
  • MFA enrolment is gated behind a recent password verification.
  • Session identifiers are rotated on login and on privilege change.
  • Cookies use Secure, HttpOnly, and a sensible SameSite value.
  • JWTs, if used, validate the signature with a fixed algorithm. alg: none is rejected. See JWT vulnerabilities.
  • OAuth and SSO flows verify the state parameter and the redirect URI against an allowlist. See OAuth misconfiguration.

Authorisation

Authorisation is the most common source of high-severity findings in real reviews. Treat it as a separate pass, not a side-check inside the authentication review.

  • Every endpoint and every GraphQL resolver has an explicit authorisation check, not an implicit one inherited from a router default.
  • Authorisation is enforced server-side. UI hiding does not count.
  • Object-level authorisation is checked: a user with id A cannot read or modify objects belonging to user B by changing the id in the request. See IDOR and BOLA.
  • Tenant isolation is enforced at the data access layer, not only in the controller. Query helpers automatically scope by tenant where possible.
  • Role checks compare the requested action against the user's actual permissions, not against a role label that happens to match.
  • Privilege boundaries are tested: a member cannot perform an admin action by setting a flag in the request body. See mass assignment.
  • Internal endpoints used by background workers, cron jobs, or admin tools are not silently exposed externally.
  • File and storage access checks the requesting identity against the resource owner, not just whether a path is well-formed.

Injection and Output Handling

  • SQL is built with parameterised queries or a query builder, not by string concatenation. Look for raw query helpers and any execute on a templated string. See SQL injection.
  • Shell commands are constructed with explicit argument arrays, never by concatenating user input into a single string. See command injection.
  • HTML output is encoded by the templating engine. Inspect any function called dangerouslySetInnerHTML, v-html, |safe, or Markup. See XSS.
  • NoSQL queries are not built from raw user-controlled objects. See NoSQL injection.
  • Path operations validate the resolved path stays inside the allowed root. See path traversal.
  • Server-side requests built from user input have an allowlist of destinations. See SSRF.
  • XML and YAML parsers disable external entity resolution and untrusted constructors. See XXE.
  • Deserialisation of untrusted data uses safe formats (JSON, protobuf), not native object serialisation. See insecure deserialisation.
  • Templating engines that allow code execution (Jinja, Twig, Handlebars helpers) do not render user input as a template. See server-side template injection.

Cryptography and Secrets

  • No secrets are hardcoded. Search for likely patterns: long base64 strings, anything ending in _KEY, _SECRET, or _TOKEN, AWS access key prefixes, private key headers. See hardcoded secrets.
  • Symmetric encryption uses AEAD modes (AES-GCM, ChaCha20-Poly1305). ECB and unauthenticated CBC are flagged.
  • Random values used for security (tokens, IVs, nonces, salts) come from a cryptographically secure RNG, not Math.random or non-secure language equivalents.
  • Hash functions for security purposes are SHA-256 or stronger; MD5 and SHA-1 are flagged for any non-legacy use.
  • TLS verification is not disabled in production code paths. Search for verify=False, rejectUnauthorized: false, and equivalents.
  • Secrets at rest are encrypted with a documented key management story, not stored in plain database columns.
  • JWT signing uses asymmetric keys for public verification scenarios; symmetric keys are not shared between unrelated services.

Logging, Errors, and Information Disclosure

  • Errors returned to users are sanitised. Stack traces and internal paths are not leaked. See information disclosure.
  • Logs do not contain credentials, full tokens, full payment data, or identity documents.
  • Authentication, authorisation, role changes, secret rotations, and admin actions are logged with enough context to reconstruct an incident. See insufficient logging.
  • Log injection is mitigated: user input that lands in logs is encoded so that an attacker cannot forge log lines.
  • Verbose debug endpoints are disabled in production builds.

Dependencies and Supply Chain

  • Dependency versions are pinned and committed. Lockfiles match the manifest.
  • SCA results from the prep pass have been reviewed; vulnerable transitive dependencies are tracked, not silently dismissed. See vulnerable dependencies.
  • No abandoned, single-maintainer dependencies sit in the critical path of security functions (auth, crypto, parsing).
  • Build steps that pull from external registries verify integrity (lockfile hashes, signed packages, vendored mirrors).
  • Container images use pinned digests, not floating tags, for production deployments.

Business Logic

Business logic flaws are the bugs scanners cannot help with at all. They require the reviewer to read the code with the product workflow in mind and ask, can a user trigger this state in a way the developer did not intend.

  • Multi-step flows (checkout, signup, password reset, KYC) cannot be entered halfway through by skipping a step.
  • State transitions enforce direction. A user cannot move an order from refunded back to paid by replaying an earlier request.
  • Race conditions on financial or identity-relevant operations are protected by database locks or idempotency keys. See race condition.
  • Discount codes, referral bonuses, and credits cannot be stacked or replayed in ways the product team did not intend.
  • Account merging, ownership transfer, and team invitations require confirmation by both sides where appropriate.
  • Bulk operations enforce per-item authorisation, not just a single check on the request.
  • Background jobs operate with the originating user's permissions where security-relevant, not with a god-tier service identity.

Language-Specific Pitfalls

The structural review applies to every codebase. The pitfalls below are the small set of language-specific patterns worth grepping for in addition.

JavaScript and TypeScript

Look for eval,new Function,dangerouslySetInnerHTML, prototype pollution sinks (__proto__, constructor.prototype), weak Express defaults, missing SameSite on cookies, and reliance onMath.random for tokens. See prototype pollution.

Python

Look for pickle.loads, yaml.load without SafeLoader,subprocess with shell=True, raw os.system, Jinja templates rendered from user input, Django |safe filter, and DEBUG=Truein production settings.

Java and Kotlin

Look for ObjectInputStream on untrusted data, XML parsers without external-entity protections, Statement instead of PreparedStatement, Spring controllers without method-level security annotations, and SnakeYAML default constructors.

Go

Look for html/template versus text/template mix-ups,exec.Command with concatenated strings, database/sql calls building queries with fmt.Sprintf, missing context propagation on auth-relevant calls, and InsecureSkipVerify: true on TLS clients.

C and C++

Look for strcpy, sprintf, gets, manual length arithmetic on size_t, missing bounds checks on parsed lengths, integer overflow in size calculations, and use-after-free patterns. See buffer overflow.

PHP

Look for unserialize on user input, eval, include with user-controlled paths, weak comparison operators (== versus ===) on auth tokens, and old PHP framework versions still in production. See local file inclusion.

Frontend Code

  • Authentication tokens are not stored in localStorage for high-value applications; HttpOnly cookies are preferred.
  • Secrets are not embedded in the client bundle. Public keys and product config are fine; signing keys, API secrets, and admin tokens are not.
  • Forms that change state validate the origin and use a CSRF token where session cookies are involved. See CSRF.
  • Open-redirect sinks built from document.location or query parameters are validated against an allowlist. See open redirect.
  • Content Security Policy is configured to disallow inline scripts where the framework allows it.
  • Postmessage handlers validate the origin.

Configuration and Deployment

  • Default credentials are not present in seed scripts or sample environment files. See default credentials.
  • Security headers are configured: HSTS, X-Content-Type-Options, X-Frame-Options or framing CSP, Referrer-Policy. See missing security headers.
  • CORS allowlists are explicit and do not include wildcard plus credentials. See CORS misconfiguration.
  • Cloud storage buckets and object stores require authentication and are not world-readable. See cloud bucket misconfiguration.
  • Feature flags that disable security checks in non-production cannot be toggled on production by accident.
  • Container images run as a non-root user where possible.

Writing Up Findings

A finding is a deliverable, not a hint. The reviewer's job is not finished when the bug is spotted; it is finished when the developer can read the writeup and ship a fix.

  • Reference the file, line, and commit hash so the writeup survives a refactor.
  • Describe the security impact in one sentence the engineering manager will understand without reading the code.
  • Score with a CVSS 3.1 vector. Pair this checklist with the CVSS calculator so scores stay consistent across the review.
  • Prioritise within a calibrated scale rather than scoring everything high. See severity calibration research.
  • Recommend a concrete fix in code-level terms, not only a CWE name.
  • Flag obvious deduplications across the review so the same root cause is not logged five times. See findings deduplication.
  • Track every finding in a register that supports retesting after the fix lands. The retesting workflow closes the loop and produces auditable evidence that each item was fixed.

For the deliverable itself, follow the structure in the penetration testing report template and use AI report generation to draft the executive summary and remediation roadmap from the findings register.

Where Secure Code Review Fits in Frameworks

  • OWASP ASVS: many ASVS verification requirements are designed to be checked against source code rather than runtime behaviour. The checklist above maps directly to ASVS sections on input validation, authentication, session management, access control, and cryptography. See the OWASP ASVS framework.
  • OWASP SAMM: the Implementation business function includes Secure Build and Secure Deployment practices that secure code review evidences directly. See the OWASP SAMM framework.
  • PCI DSS: Requirement 6 mandates secure development including code review for security defects on payment-handling applications. See the PCI DSS framework.
  • ISO 27001: Annex A control 8.28 (Secure coding) is met in part by performing structured code reviews and tracking findings to closure.
  • DORA Article 25: source code reviews are listed alongside vulnerability assessments and penetration testing as part of the digital operational resilience testing programme. See the DORA framework.

Operating Secure Code Review at a Consultancy

A repeatable secure code review service has the same operational shape as a pentest: scope, kickoff, delivery, debrief, retest. The checklist above is the technical core; the workflow around it determines whether the service is profitable.

  • Run scoping with the client's engineering lead, not only with security stakeholders. The reviewer needs to know which components are in scope, what is under active refactor, and where the client has already invested in security tests.
  • Pair every code review engagement with a kickoff meeting that confirms repository access, branch, commit, in-scope languages, and how findings will be reported.
  • Track delivery on the engagement record alongside any pentest or vulnerability assessment. The engagement management feature keeps scope, findings, and reports together.
  • Deliver findings through the client portal so the client's engineers see the live finding register, not a static PDF that is out of date the moment a fix lands.
  • Charge for the work in line with effort. See how to price security services and the pricing models research for guidance that applies cleanly to code review engagements.

Frequently Asked Questions About Secure Code Review

Run SAST and SCA, log the manual review findings, and deliver one clean report

SecPortal connects to GitHub, GitLab, and Bitbucket for Semgrep SAST and SCA, then tracks every manual finding with CVSS 3.1 in a single register. Generate the executive summary and remediation roadmap with AI report generation and deliver through a branded client portal. See pricing or start free.

Get Started Free