What Code Review Is and How It Catches Security Problems Early
This guide is for non-engineering customers who want to understand what code review actually does in a delivery process, especially for security. You will see where review sits in CI/CD, how it catches real issues before release, and when it is worth paying for deeper review versus relying on automated checks alone.
TL;DR — Code review is the step where another engineer reads a proposed code change before it is merged and deployed. Its security value is simple: automated scanners catch known patterns, but a human reviewer catches risky business logic, unsafe assumptions, and "this technically works but exposes data" mistakes that tools often miss. Reading time: ~7 min
What it is and where it sits
Code review is a structured check of a code change by someone other than the person who wrote it. In modern delivery, this usually happens on a pull request (a proposed change set in Git) before the change is merged into the main branch and sent through CI/CD (continuous integration and delivery).
For a customer, the important point is that code review is not just proofreading. It is a quality and risk-control gate between "a developer made a change" and "that change is now live in production." Security problems are often introduced in ordinary feature work: a new admin screen, a file upload, a search box, a billing export. Review is where another set of eyes asks: who can access this, what data leaves the system, what happens if the input is hostile, and does this bypass an existing safety control?
In a typical architecture, code review sits between source control and deployment:
View as text
[Developer] ---- 1. push branch ----> [Git repository]
[Git repository] ---- 2. open PR ----> [Code review]
[Code review] ---- 3. comments/approval ----> [CI pipeline]
[CI pipeline] ---- 4. tests + scans ----> [Main branch]
[Main branch] ---- 5. deploy ----> [Production app]
|
| 6. serves requests
v
[Users]What talks to it:
- Developers create the change and respond to comments.
- The Git hosting system shows the diff (the exact lines changed).
- CI tools run tests and security scans alongside review.
- Release automation usually waits for approval before deployment.
What it replaces:
- It replaces the risky model of "developer commits directly to production branch" or "we trust tests only."
- It does not replace automated security scanning, penetration testing, or access controls. It complements them.
Where it lives in request/data flow:
- Not in the live user request path. Your customers never hit "code review" at runtime.
- It lives in the software delivery path: change proposed → reviewed → tested → merged → deployed.
That matters because the cheapest security bug is the one removed before release. Fixing an access-control mistake during review is usually minutes. Fixing it after production may mean emergency patches, customer notifications, audit work, and reputational damage.
How it actually works
The mechanism is concrete: a developer submits a change, another engineer inspects the changed files in context, asks questions, requests edits, and approves only when the change is safe enough to merge.
One realistic example: an invoice download endpoint
Imagine your agency is adding a feature to let logged-in users download PDF invoices.
A developer adds a route like this in a web app:
- URL:
GET /invoices/:id/download - Expected behavior: a user can download their own invoice PDF.
The developer tests it with their own account. It works. Automated tests pass. A dependency scanner finds nothing, because no vulnerable library is involved.
Now the pull request is opened for review.
Step by step through the review
-
The reviewer reads the feature description They look at the ticket or PR summary: "Add invoice PDF download for customers."
-
They inspect the changed files, not just the final screen They review the controller/route, the database query, and any policy or permission code.
-
They look for authorization (permission checking) This is where many security issues appear. The reviewer asks: does the code check only that the user is logged in, or that the invoice belongs to that user?
-
They spot a subtle problem The code loads the invoice by ID only:
- "Find invoice where
id = 123" - Then return the PDF if the user is authenticated
That means any logged-in user might change the URL from
/invoices/123/downloadto/invoices/124/downloadand get someone else’s invoice. This is a classic insecure direct object reference, often shortened to IDOR (guessable record identifier without ownership check). - "Find invoice where
-
They leave a specific review comment Not "security issue?" but something actionable, such as:
- "This checks login but not ownership. Query should scope invoices to
current_user, or enforce a policy before download. Otherwise any authenticated user can fetch another customer’s invoice by changing the ID in the URL."
- "This checks login but not ownership. Query should scope invoices to
-
The developer updates the code They change the query from "invoice by ID" to "invoice by current user and ID," or they add a policy check.
-
The reviewer re-checks the flow They verify the fix covers both the HTML page and the actual file download endpoint, because teams often secure one and forget the other.
-
CI runs again Tests pass, and ideally a new test is added: user A cannot download user B’s invoice.
-
The PR is approved and merged The issue never reaches production.
This is why code review catches security problems: many real security bugs are not broken syntax or known vulnerable packages. They are logic mistakes in how a feature handles identity, data ownership, and trust boundaries (the lines between who is allowed to do what).
When to use it (and when not to)
If software changes are being deployed, code review should usually be the default. The real question is how strict and deep the review should be.
| Scenario | Recommendation |
|---|---|
| Customer-facing app handling logins, payments, files, or personal data | Require review before merge; include security checklist items in every PR |
| Internal business app with a small user base but real company data | Still require review; lighter process is fine, but do not skip ownership and data exposure checks |
| Infrastructure changes like firewall rules, IAM policies, or database permissions | Require review from someone familiar with operations/security; these changes can create broad exposure |
| One-line content or copy change with no code behavior change | Light review is enough |
| Solo prototype that will never hold real data and will not be exposed publicly | You probably do not need formal review, but add it before the prototype becomes a real product |
| Emergency production fix | Use expedited review if possible; if you must hotfix first, do a post-merge review immediately after |
You probably do not need a heavy, multi-approver process if:
- the change is purely cosmetic,
- the environment is disposable and contains no real user data,
- or the code will not be deployed beyond a short-lived experiment.
But if the software has accounts, APIs, admin actions, uploads, exports, billing, or personal data, review is one of the highest-value controls you can buy.
Trade-offs
Code review is valuable, but it is not free.
-
Benefit: catches logic-level security issues tools miss
Cost: engineer time. A meaningful review takes focus, and senior reviewers are expensive. -
Benefit: spreads knowledge across the team
Cost: slower throughput. Changes may wait for reviewer availability. -
Benefit: creates an audit trail of why a risky choice was accepted
Cost: process overhead. Teams need conventions, templates, and discipline. -
Benefit: improves consistency in auth, validation, and data handling
Cost: some developer frustration. Reviews can feel like friction if standards are unclear. -
Benefit: reduces production incidents and emergency fixes
Cost: not all bugs are caught. You still need tests, logging, backups, and runtime monitoring. -
Benefit: lowers security risk before release
Cost: possible bottlenecks and mild vendor lock-in if your workflow depends heavily on one Git hosting platform’s approval rules. The concept is portable; the exact buttons and automation may not be.
A practical rule: use humans for judgment, automation for repetition. The best systems combine both.
In practice
Below are two examples you can adapt today: one human process control in a pull request template, and one CI rule that blocks merging until checks pass. These do not replace review quality, but they make good review more likely.
Example 1: Pull request template with security prompts
In your Git provider’s repository settings, look for a path like Settings → General → Pull Requests or Settings → Repository → Templates. If your provider supports repository templates via files, add a file named .github/pull_request_template.md or the equivalent for your platform.
## Summary
- What does this change do?
- Who will use it?
## Risk check
- [ ] Does this change affect login, permissions, roles, or admin actions?
- [ ] Does this change read, export, upload, or delete customer data?
- [ ] Does this change add a new endpoint, webhook, or background job?
- [ ] Does this change trust user input, URL parameters, or uploaded files?
- [ ] If yes to any above, what security check was added?
## Test plan
- [ ] Happy path tested
- [ ] Unauthorized user tested
- [ ] Invalid input tested
- [ ] Logging/error handling checked
What it does: this forces the author and reviewer to look at common security-sensitive areas before approval. The gotcha: teams often paste this in and then ignore it, so reviewers should reject PRs with unchecked or vague answers.
Example 2: GitHub Actions workflow requiring tests before merge
In your repository, add .github/workflows/ci.yml, then in your Git provider dashboard enable branch protection on the main branch and require this status check before merging. In many dashboards this is under Settings → Branches → Branch protection rules.
name: ci
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
What it does: every pull request to main runs the test suite, and branch protection can block merging until it passes. The gotcha: passing tests do not prove security; they only enforce that basic checks ran, so keep human review as a separate required approval.
Optional CLI example: create a review-ready branch and commit clearly
If your team uses Git locally, a clean branch and focused commit history make review much better.
git checkout -b feature/invoice-download
git add .
git commit -m "Add invoice PDF download with ownership check"
git push -u origin feature/invoice-download
What it does: creates a dedicated branch for one feature and pushes it for review. The gotcha: avoid mixing unrelated refactors into the same branch, because large noisy diffs hide security mistakes.
Further reading
- OWASP Secure Code Review Guide
- OWASP Top 10
- Google Engineering Practices Documentation, "Code Review" section
- GitHub Docs, "About pull request reviews"
- The "Access Control" cheat sheets in the OWASP Cheat Sheet Series
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI