Source code review

Find the flaw in the code, not just in the response.

A manual source code review of your SaaS application and APIs. I read the authorization logic, the ownership checks, the authentication flows and the business rules line by line, and report what is actually exploitable rather than what a static analysis tool flagged.

Manual reviewSaaS and API codebases Authorization and tenancyBusiness logic
// invoice detail router.get('/invoices/:id', requireAuth, requireRole('member'), async (req, res) => { const id = req.params.id; const inv = await findById(id); return res.json(inv); } );
reading route handlersFinding
What it is

A source code review reads the decision, not the response

Testing a running application tells you what it does. Reading the code tells you why, and shows you the cases you never thought to test.

Coverage

Every path, not the reachable ones

A penetration test can only reach what is exposed through the interface and the API. A code review covers the branches behind feature flags, the administrative paths, the background jobs and the endpoints that exist but are not linked anywhere.

Root cause

The missing check, not the symptom

Black box testing finds one endpoint that leaks another tenant's data. A code review finds the query helper that omits the tenant filter, and therefore every one of the forty endpoints that calls it.

Timing

Before it ships, not after

Code can be reviewed before the feature is deployed, while the cost of changing the design is a conversation rather than a migration and an incident notice.

Review map

What I read, and what I am looking for in it

A secure code review is not a walk through every file. It is a targeted read of the code that makes security decisions, in the order an attacker would care about. These are the areas covered on a SaaS or API codebase.

01 Highest value

Authorization and ownership

Every place the code decides whether this caller may touch this object. I trace the ownership check from the route to the query, looking for handlers that trust an identifier from the request body, helpers that take a tenant argument the caller controls, and endpoints that check the role but never check who owns the record. This is where IDOR and broken object level authorization actually live, and it is the single most productive area in SaaS code.

02

Multi tenancy and data isolation

Whether tenant scoping is enforced in one shared place or repeated by hand in every query. Repeated scoping is the pattern that eventually gets forgotten in one method, and one forgotten filter is a cross tenant data leak. I look at the ORM layer, the raw queries, the caching keys and any background job that runs outside a request context and therefore outside the usual tenant guard.

03

Authentication and session handling

Token issuance and validation, signature verification and algorithm handling, expiry, refresh and revocation, password reset token generation and lifetime, multi factor enrolment and recovery, and what happens to an existing session when a password or an email address changes. Verification that accepts an unsigned token, or a reset token that never expires, is found by reading the code and almost never by guessing at the interface.

04

Business logic and workflow rules

The rules that decide what should happen: quantity and price handling, discounts and refunds, approvals, invitations and role assignment, quota and limit enforcement, and state transitions that assume an order of operations nobody enforces. Automated tools cannot evaluate these at all, because nothing about the request is malformed. Only the decision behind it is wrong.

05

Injection and unsafe input handling

SQL and NoSQL query construction, command execution, template rendering, deserialization, path handling in file operations, and server side request forgery in anything that fetches a URL the user supplied. I follow the data from where it enters to where it is used rather than pattern matching on function names, because the dangerous call is usually three functions away from the input.

06

Secrets, configuration and environment

Credentials, API keys and signing keys committed to the repository or its history, debug and verbose error settings that behave differently in production, permissive CORS configuration, default administrative accounts, and configuration that is safe in the file you read and overridden somewhere else at runtime.

07

Data exposure in responses

Serializers and response builders that return whole records where the interface shows three fields, internal identifiers and flags that leak how the system works, error messages that differ enough to enumerate users, and logging that writes tokens or personal data where it should not.

08

Dependencies and supply chain

Which third party packages carry known vulnerabilities and, more usefully, whether the vulnerable function is actually reachable from your code. A dependency report that lists forty advisories is not useful until somebody establishes which two of them matter here.

09

Cryptography and data at rest

Password storage and hashing choices, how encryption keys are derived and stored, use of random values where they need to be unpredictable, and anything rolling its own scheme where a standard one exists.

10

File handling and integrations

Upload validation, storage location and access control, signed URL generation and expiry, and the trust placed in webhook receivers and partner integrations, including whether inbound webhook signatures are verified at all.

Method

How the review actually runs

The review is manual. Tooling is used to navigate the codebase, map routes and build dependency lists, not to produce findings. Every issue in the report was found and confirmed by reading the code.

  1. Scope and accessI agree with you which repositories and which parts of them are in scope, which branch or commit is being reviewed, and what the application is supposed to do, because a rule cannot be judged wrong without knowing what it should be. Access is read only repository access or an archive at a fixed commit, whichever suits your policy.
  2. Map the applicationBefore reading anything in depth I build a picture of the codebase: every route and endpoint, the middleware chain each one passes through, the data models and their relationships, where the authorization decisions are made, and which code paths run outside a normal request, such as jobs, queues and scheduled tasks. This map decides where the review time goes.
  3. Review the security decision pointsWorking through the review map above, in priority order, starting with authorization and tenancy because that is where the serious findings usually are. Each area is read against how your application is meant to behave, not against a generic checklist.
  4. Trace the dataFor anything suspicious, follow the value from its entry point to every place it is used, and follow the sensitive data back from where it is returned to where it was loaded. Most real findings are not visible in a single function. They come from the gap between two of them.
  5. Confirm before reportingA suspicious pattern is not a finding. Where the running application is available I verify the issue against it, and where it is not I show the exact path through the code that makes it reachable. Anything I cannot confirm is reported separately as an observation, clearly marked, rather than padded into the findings list.
  6. Report with file and lineEach finding names the file, the line and the function, explains why the code is wrong rather than only that it is, states the impact in terms of your application, and gives remediation guidance written for this codebase rather than a link to a generic reference page.
  7. Retest after the fixOnce the changes are made I read the new code and confirm the fix closes the issue and does not leave an adjacent path open. A finding is closed when the code says it is, not when the ticket does.
Worked example

One read, three findings, one cause

The endpoint in the panel at the top of this page checks the role and never checks ownership. Here is what following it produces.

Step 01
The endpoint

The role is checked, the owner is not. Anyone authenticated can request any invoice identifier and receive it.

Step 02
The helper

The query helper it calls has no tenant column in its predicate at all, so the omission is not local to this route.

Step 03
The blast radius

Eleven other call sites use the same helper. The fix belongs in one place, and testing would have found only the endpoint somebody happened to try.

This is the difference in one screen. A penetration test reports an endpoint. A code review reports a cause, the eleven places it reaches, and the single change that closes all of them.

Method

Why this is manual, and where static analysis stops

Static analysis tools have a real place. They are fast, cheap and good at patterns. They are also the reason many code review reports are unreadable.

QuestionStatic analysisManual review
Unsafe pattern in a known shapeFinds it reliably and fastFinds it, more slowly
Missing authorization checkCannot see it. Nothing in the code is malformed, a check is simply absentThis is the core of the review
Wrong business ruleNo concept of what the rule should beJudged against what the application is meant to do
Tenant isolationCannot tell a scoped query from an unscoped oneTraced through the data layer
Is this exploitable hereGeneric severity attached to a patternRated on what it allows in your application
False positivesMany, and they all have to be read by somebodyEvery reported finding is confirmed first

A SAST report with four hundred entries is not a secure code review. It is an input to one, and the work is the part that happens afterwards. That work is what this service is.

Coverage

Languages and frameworks

The security questions are the same everywhere. What changes is where the framework hides the answer, and which of its conveniences are unsafe by default.

JavaScript and TypeScript

Node, Express, NestJS, and React and Next.js on the frontend. Middleware ordering, route guards, ORM scoping in Prisma, Sequelize and TypeORM, mass assignment through object spread, and the server and client boundary in Next.js, where code you assumed was private is shipped to the browser.

Python

Django, Flask and FastAPI. Django permission classes and queryset scoping, Flask route decorators applied inconsistently, FastAPI dependency injection for authentication, serializer field exposure, and template autoescaping where it has been turned off.

PHP

Laravel, Symfony and WordPress plugin and theme code. Laravel policies and gates, Eloquent global scopes, mass assignment through fillable and guarded, and in WordPress the capability checks, nonce verification and the sanitisation and escaping boundary in plugin code.

Java and C#

Spring Boot and .NET. Method level security annotations, filter chains, attribute based authorization, model binding and over posting, and deserialization handling.

Working in something else? Send me the stack and I will tell you straight whether it is a good fit for review.

Access and confidentiality

How your code is handled

Access

Read only, at a fixed point

Either read only access to the repository, or an archive of it at an agreed commit. Write access is never needed and is not requested. The review is pinned to one commit so the report refers to code that still exists.

Handling

Kept to the engagement

The code is used for the review and nothing else. It is not shared, not used to train anything, and removed at the end of the engagement unless you ask for it to be kept for a retest.

Paperwork

NDA before access

An NDA can be signed before any code changes hands. Yours or mine, whichever is simpler for your legal team.

Deliverable

What you receive

Per finding

Something a developer can act on

The file, the line and the function. The code itself, quoted. Why it is wrong, not only that it is. A severity rating with the reasoning behind it, in terms of what it allows in your application. The security impact in business terms. Remediation written against this codebase, including the other call sites affected when the cause is shared.

Per engagement

Context around the list

A summary an engineering lead can read quickly and act on. Coverage notes stating what was reviewed and, just as importantly, what was not. Systemic observations where the same mistake appears in several places, because that is a pattern to fix rather than six tickets. Observations that could not be fully confirmed, listed separately and marked as such.

Findings are ordered so your team knows what to fix first. If everything is high then nothing is, so severity is decided by what an attacker actually gains here, what access they need before they can use it, and whether it can be chained with anything else found in the same review.

Timing

When a source code review is the right call

Before a release

A significant new feature

Anything that adds a role, changes the permission model, introduces a new way to share data between users, or opens a new integration. These are the changes that break tenant isolation, and reviewing them before deployment costs a conversation rather than an incident.

Compliance

Before a SOC 2 or ISO audit

Secure development practices are part of what auditors look at, and a documented independent code review is straightforward evidence. It is also the point where teams discover that the control they described is not what the code does.

Depth

Alongside a penetration test

Run together, the two cover what neither does alone. The test proves impact on the running system, the review explains the cause and finds the other places it exists. This is what a white box application assessment actually means.

Diligence

Inheriting a codebase

An acquisition, a handover from an agency, or a rewrite of something nobody left documentation for. You are taking on whatever is in there, and a review tells you what before it becomes yours to explain.

After an incident

Finding the rest of it

One issue was found and fixed. The question worth answering is whether the same mistake exists elsewhere, and that is a code question rather than a testing one.

Customer pressure

An enterprise security review

A large customer's questionnaire asks whether your code is independently reviewed. Answering yes, with a report behind it, moves a deal along.

Comparison

Source code review or penetration test

They answer different questions. Most teams eventually want both, and which comes first depends on what you already know.

Source code reviewPenetration test
The questionWhy is this wrong, and where else is it wrongHow far can an attacker actually get
PerspectiveInside the code, every pathOutside the application, reachable paths
Finds firstMissing checks, unsafe patterns, systemic causesWorking exploit chains and demonstrated impact
Blind toRuntime and configuration issues the code does not describeCode that is not reachable from outside yet
Best timingBefore release, or after a finding, to catch the patternOnce the feature is live and reachable
NeedsRepository accessA working environment and test accounts

If you already know something is wrong and want to know why, start with the review. If you need to demonstrate real impact to a board or a customer, start with the penetration test. If the requirement is broad coverage across the whole surface, that is a vulnerability assessment.

Questions

Source code review questions

What is a source code review?

A source code review is a manual examination of an application's code to find security flaws that the code itself makes possible, such as missing authorization checks, unsafe data handling, broken tenant isolation and business rules that can be abused. It differs from a penetration test in that it covers code paths that are not reachable from outside yet, and it explains the cause of a flaw rather than only its symptom.

How is a source code review different from SAST?

Static analysis matches patterns and produces candidates. It cannot see a missing authorization check, because nothing in that code is malformed, a check is simply absent. It also cannot judge whether a business rule is correct, since it has no idea what the rule should be. A source code review uses tooling to navigate the codebase and then evaluates the security decisions by hand, and every reported finding is confirmed before it appears in the report.

Do you need write access to the repository?

No. Read only access is enough, or an archive of the repository at an agreed commit if your policy prefers that. Write access is never requested.

Which languages and frameworks do you cover?

JavaScript and TypeScript including Node, Express, NestJS, React and Next.js, Python including Django, Flask and FastAPI, PHP including Laravel, Symfony and WordPress plugin code, and Java and C# on Spring Boot and .NET. If your stack is not on that list, ask rather than assume, because the review is only offered where the code can be read properly.

Do you review the whole codebase?

Not line by line, and any provider claiming to do that on a large codebase is describing a tool rather than a person. The review maps the application first, then reads the code that makes security decisions in priority order, starting with authorization and tenant isolation. The report states plainly what was covered and what was not.

Can a code review replace a penetration test?

No, and the reverse is also true. A review finds causes and covers paths that are not yet reachable. A test proves impact on the running system, including configuration and runtime issues the code does not describe. Run together they are a white box assessment, and that is the most complete option.

What happens after the findings are fixed?

The changed code is read again to confirm each fix closes the issue and has not left an adjacent path open. A finding is not closed because a ticket was closed.

Will you sign an NDA before seeing the code?

Yes. An NDA can be signed before any access is granted, using your template or mine.

Have the code read before someone else does

Send me the stack, the rough size of the codebase and what you are worried about. I will tell you whether a code review is the right thing to buy, and say so if it is not.