
Secure code review is reading source code specifically to find security defects: injection, broken auth, unsafe deserialization, secrets, and logic flaws that let an attacker do something they shouldn't. Unlike a dynamic test that pokes a running app from outside, code review gives you the logic directly, so you can trace exactly how untrusted input flows to a dangerous operation.
This guide covers how to run a review that actually finds bugs: where automated SAST helps and where it lies, the source-to-sink model that organizes the whole exercise, the language-specific sinks worth grepping for, and how to triage results so your team trusts the output instead of muting it.
Secure code review is the practice of examining application source code to find and fix security vulnerabilities before they reach production. It sits in the build/test phase of the SDLC and complements dynamic testing rather than replacing it.
The advantage is coverage of code paths a black-box tester would never reach: an injection sink behind a feature flag, an authorization check that's missing on one of forty endpoints, a deserializer that only fires for admin uploads. Because you read the logic directly, you catch the bug without having to guess the input that triggers it. The OWASP Code Review Guide formalizes this into a repeatable process built around risk, not line-by-line reading of every file. For where it fits among other assessment types, see our overview of the OWASP Top 10 application vulnerabilities.
You need both. SAST (static application security testing) gives you breadth and consistency across a large codebase fast; manual review gives you the judgment that tools lack, especially for business logic, authorization, and chained issues. Treat the scanner as a first pass that narrows where you spend human attention.
SAST tools each have a personality. Semgrep uses lightweight pattern rules that read almost like the code itself, so it's fast to write custom rules and great in CI. CodeQL models code as a queryable database and does real interprocedural data-flow analysis, which makes it strong at tracing taint across functions and files. SonarQube blends security rules with code-quality and maintainability checks, which suits teams that want one gate for both.
Taint analysis tracks untrusted data from where it enters the application (a source) to where it's used in a dangerous operation (a sink), checking whether anything sanitizes it along the way. If tainted data reaches a sink unsanitized, you likely have a vulnerability.
Sources are anything an attacker controls: request parameters, headers, cookies, JSON bodies, file uploads, message-queue payloads, and data read back from a database that was itself attacker-influenced. Sinks are operations where that data does damage: a SQL query, an OS command, an HTML response, a file path, a deserializer. The space in between is the sanitizer: parameterized queries, output encoding, allow-list validation, safe APIs. CodeQL and Semgrep both model this directly, so you can write a rule that says "flag any flow from an HTTP parameter to Runtime.exec with no escaping." When you review manually, you're running the same algorithm in your head: find the sink, walk backward to the source, and look for the missing sanitizer.
Most exploitable bugs cluster around a small set of dangerous sinks, and they differ by language and framework. Learn the sinks for your stack and you can grep your way to the highest-value findings quickly.
"SELECT ... " + userId instead of parameterized queries or an ORM with bound params.Runtime.exec, os.system, subprocess with shell=True, child_process.exec, backticks in Ruby.eval, exec, Function(), pickle.loads, and template engines run in unsafe mode.ObjectInputStream / readObject, Python pickle and yaml.load without SafeLoader, PHP unserialize, .NET BinaryFormatter.These map cleanly to real-world classes you'll also probe dynamically in a web application pentest, so a finding in review usually predicts a finding in testing.
Run the security review on the diff, in the pull request, before merge. Reviewing only the changed lines plus their immediate context is faster, more accurate, and lands the feedback while the author still has the change in their head.
A practical setup: a SAST scan (Semgrep or CodeQL) runs in CI on every PR and posts findings as inline comments, blocking merge only on high-confidence rules so the gate stays credible. A human reviewer then looks at anything touching auth, crypto, deserialization, file handling, or query building, regardless of whether the tool flagged it. Keep PRs small so the security-relevant change isn't buried in a thousand-line refactor. This is also where AI-assisted review is changing the workflow: the same continuous, per-change model underpins agentic pentesting, which keeps assessment running against every build instead of once a quarter.
Triage every finding into true positive, false positive, or won't-fix, and tune the ruleset so the false-positive rate stays low enough that developers keep reading the output. A scanner nobody trusts is worse than no scanner, because alerts get blanket-muted.
The most common false positives come from data that looks tainted but is actually constrained: a value already validated against an allow-list, a sink that's safe in context (a parameterized query the tool didn't recognize), or an unreachable path. Resolve them by confirming the source is genuinely attacker-controlled and the sanitizer is genuinely absent. Then make the fix stick: suppress with an inline annotation tied to a reason, write a custom Semgrep rule to encode your framework's safe wrappers, and feed confirmed bugs back as regression rules. Prioritize what's left with severity and exploitability, the same lens you'd apply when reading a penetration testing report.