What a secure code review is
A secure code review examines the source code of an application to find security flaws before attackers do. Because the reviewer can see every line, they find problems that a tester working from outside may never reach: an administrator function with no permission check, a rarely used endpoint, or a debug route left in by mistake.
It differs from a penetration test, which attacks the running application and sees the deployed configuration but only the paths it manages to reach. It also differs from an everyday code review for quality, although both often happen in the same pull request process: the mechanics are the same, the questions are about security. See also the glossary entry.
Two layers: automated tools and a human reviewer
Neither layer is enough alone.
| Layer | What it finds | Its limits |
|---|---|---|
| Static analysis (SAST) | Dangerous patterns in your own code, such as SQL built from strings, weak hash functions and unsafe function calls. | Noisy, with false positives. It does not know your business rules, so it misses most access control flaws. |
| Dependency scanning | Third-party libraries with published vulnerabilities, including those pulled in indirectly. | Knows only published issues. Many alerts do not affect your code and have to be triaged. |
| Secret scanning | Keys, tokens and passwords committed to the repository, including its history. | Can miss secrets in unusual formats. |
| Manual review | Access control, business logic, design flaws, cryptography misuse and anything the tools flag that needs judgement. | Takes time, so it concentrates on the riskiest code first. |
Run the tools on every change so the easy problems never reach a person. Keep the reviewer's time for the flaws only a person can see.
What reviewers look for
These categories do not depend on a programming language. They overlap with the OWASP Top 10, a good awareness baseline, though a review goes further.
- Authentication and access control. Is every endpoint and action checked on the server, for the right user and role? Can a user change an ID in a request and read someone else's record? Are session tokens issued, stored and expired properly? Is multi-factor authentication enforced where it is promised?
- Injection. Do database queries use parameters, or are they built from strings? The same question applies to operating system commands, templates and search query languages. See SQL injection and cross-site scripting.
- Unsafe deserialisation. Does the code rebuild objects from untrusted data using formats that can run code? Some native serialisation formats, such as Python's pickle, can execute code while loading data, so they must never read input from an untrusted source.
- Secrets in code. Keys, passwords and tokens in source files, or in configuration committed to the repository, including old commits. Rotate any secret that was committed, even if it was deleted later, because the history keeps it.
- Cryptography misuse. Home-made schemes, hard-coded keys, predictable random values used for tokens, fast hashes for passwords instead of a purpose-built function such as Argon2, scrypt or bcrypt, and certificate checks switched off.
- Error handling and logging. Internal stack traces shown to users, checks that fail open when an error occurs, and logs that contain passwords, tokens or personal data.
- Third-party code. Vulnerable or abandoned libraries, packages added for a tiny feature, scripts that run on install, and versions that are not pinned.
Language and framework specifics
The categories stay the same across languages, but the traps differ.
| Stack | Typical places to look |
|---|---|
| JavaScript and TypeScript (Node.js, React) | Inserting raw HTML into pages, such as dangerouslySetInnerHTML, eval, shell commands built from user input, prototype pollution when merging objects, and checks done only in the browser. |
| Python | Loading untrusted data with pickle or an unsafe YAML loader, SQL built with string formatting, and shell calls that include user input. |
| Java and Kotlin | Native object serialisation, XML parsers that allow external entities, SQL built by concatenation and, on Android, components exported to other apps. |
| Swift and mobile apps | Secrets inside the app package, sensitive data kept in plain local storage instead of the system's secure storage, and certificate checks turned off. |
| Frameworks and ORMs | Raw query escape hatches, mass assignment (request fields bound straight onto models) and routes added without the authentication layer. |
Framework defaults, such as templates that escape output and queries that take parameters, remove whole classes of bugs. A reviewer therefore first checks where the code steps around them.
How a review runs, and what you receive
- Scope and context. Which repositories and branches, what the application does, and which data and roles matter. A threat model, if one exists, shows the reviewer where to spend time.
- Automated pass. Static analysis, dependency scanning and secret scanning, with the results triaged to remove false positives.
- Manual pass. The reviewer follows the highest-risk flows through the code: login, permissions, payments, file handling and admin features.
- Report. The findings are written up and prioritised.
- Walkthrough. A call with your developers to explain the findings and agree the fixes.
- Re-check. The reviewer confirms the fixes, ideally on the same pull requests that make them.
A useful report contains:
- A summary for decision makers.
- Each finding with its file and line, an explanation of why it is a problem, a severity and a suggested fix, often as a short code example.
- A list of vulnerable dependencies and which to upgrade first.
- A statement of what was and was not reviewed.
- Suggestions for checks to add to your pipeline, so the same issue is caught automatically next time.
When in the lifecycle to review
Earlier is cheaper. Design flaws are best caught with a threat model before code exists. While building, review security-sensitive pull requests as they arrive. Before launch, run a focused review of the riskiest areas. Afterwards, review again when sensitive code changes, after an incident, and when you inherit a codebase from another team or supplier, because you cannot assume that anyone checked it.
Continuous pull request review or a one-off audit
| Approach | Strengths | Weaknesses | Fits |
|---|---|---|---|
| Pull request review | Catches problems while the author still remembers the context. Cheap to fix. Builds team habits. | The reviewer sees a change, not the whole system, and it depends on discipline. | Teams that ship often. |
| One-off audit | Looks at the whole codebase with fresh eyes. Useful as evidence for other parties. | A snapshot: the code changes the next day, and findings can pile up. | Before launch, inherited code and major milestones. |
Most teams need both: automated checks and a short security checklist on every pull request, plus a deeper audit at milestones. That is how we think about secure app development: checks on every change and a deeper look at the moments that matter.
A code review finds what is in the code, not how the application is deployed. Pair it with penetration testing to check the running system, including server and cloud configuration.