A source code security audit is easy to buy badly. A proposal may promise “complete SAST and manual review,” yet never identify the commit, build profile, files accepted by the analyzer, code excluded from human review, or evidence required to close a finding. The resulting report can contain useful bugs while remaining impossible to relate to a release.
The buyer’s real question is narrower: what code and security paths were examined, by which methods, with what limits, and how does that work relate to the artifact we intend to ship? The answer should survive handover from the security team to engineering, release management, a customer assessor, or a future retest.
This guide focuses on that evidence contract. A broader application security audit may also cover architecture, running infrastructure, APIs, and operational controls. A source review looks inside an agreed code baseline. It can inform those activities, but it does not by itself prove the behavior of a deployed service or establish that a binary contains only the reviewed code.
Start with the decision, then choose the review type
Name the decision the audit must support. Common examples are accepting a release candidate, investigating a critical legacy component, obtaining independent evidence for a customer, or checking a high-risk change before merge. That decision determines whether the work should be a baseline review, a diff review, or a hybrid.
A baseline review maps an agreed codebase and concentrates depth on its high-risk parts. It suits a first independent assessment or a major release, but “baseline” must not be misread as a promise that every line receives equal human attention. A diff review examines changes between two immutable revisions and asks how they affect existing controls and trust boundaries. It is efficient for a release delta only when the earlier baseline remains relevant. A hybrid reviews critical paths across the baseline and then follows the change set around them.
Ask the provider to translate effort into observable units: repositories and revisions, languages and build targets, automated target files, manually traced paths, findings validated, and excluded areas. Lines of code can help estimate effort, but they do not measure security coverage. One authorization function may matter more than thousands of generated model classes.
Freeze the object being reviewed
A branch name or version label can move. The scope record should identify each repository by its canonical location and immutable commit identifier, plus submodule revisions where used. Record whether the auditor received a clean archive, a read-only repository clone, or time-limited platform access. Preserve the manifest used at kickoff and the final scope ledger with the report.
Then describe how that source becomes the intended release. The useful chain is not merely “tag 3.4 was reviewed.” It is: commit, dependency lock state, build target, build mode, security-relevant build flags, generated inputs, produced artifact, and artifact digest or comparable immutable identifier. Build provenance or an attestation can strengthen the last link when the delivery pipeline already produces it. It still does not prove that the build was trustworthy or the software secure; it identifies which artifact a particular build statement concerns.
- BaselineRecord repositories, immutable commits, submodules, lockfiles, build targets, configuration assumptions, and exclusions
- ExtractionPreserve tool versions, rules, build mode, target and skipped files, warnings, errors, and generated-code treatment
- Manual pathsTrace selected entry points through identity, authorization, transformations, security decisions, and sensitive sinks
- Release and retestLink findings and fixes to revisions, rebuild the intended artifact, and verify the original path plus nearby variants
This distinction matters for compiled and generated systems. Some analyzers can create a model without a build for supported languages. Other configurations analyze a real build whose commands are selected automatically or supplied explicitly; those commands may deliberately cover only selected targets. “Scan succeeded” therefore does not answer which source became part of the analysis. Require the build mode, command or target, extraction summary, warnings, skipped files, and a reconciliation against the expected repository tree.
Define included code, excluded code, and configuration
The statement of work should classify the code, not just list repositories. Separate first-party application code, shared internal libraries, build and deployment scripts, infrastructure definitions, migrations, templates, test utilities, generated code, vendored source, and third-party packages. For each class, say whether it receives automated analysis, dependency analysis, manual review, configuration review, or no review.
Generated code deserves an explicit decision. Reviewing a generated file may identify a dangerous output, while the durable fix belongs in its generator, schema, template, or options. Excluding generated code may also hide code that actually executes. The ledger should name the generator and input, state whether output is shipped, and explain which layer is reviewed. The same discipline applies to custom frameworks: if an analyzer does not understand an organization’s routing, authorization wrapper, templating engine, or sanitizer, a generic ruleset may miss the path. The provider should disclose any custom models, stubs, or rules and the residual gap.
Tool configuration is evidence. Capture analyzer and rule-pack versions, enabled and disabled rules, severity filters, path inclusions and ignores, language failures, timeouts, parse errors, and whether analysis crosses files or functions. Different editions or modes of the same tool may have different semantic reach. A dashboard count without this context is not reproducible coverage.
Scroll sideways to compare
| Review layer | What it can establish | Evidence to request | Boundary to record |
|---|---|---|---|
| SAST and semantic analysis | Rule matches and modeled flows in files the engine actually analyzed | Tool and rules version, build mode, targets, skipped files, warnings, raw and triaged results | Unsupported language, missing framework model, per-file analysis, timeout or parse failure |
| Dependency review | Declared or resolved components and applicable vulnerability or policy signals | Lockfiles, resolver output, package identity, reachability rationale where assessed | Undeclared, vendored, runtime-loaded, or service-side components |
| Configuration review | Security-relevant defaults and settings available in the supplied material | Named profiles, manifests, environment assumptions, effective settings where obtainable | Secrets and production overrides not supplied; infrastructure outside scope |
| Manual review | Contextual authorization, data-flow, state, and business-rule reasoning on selected paths | Path ledger, entry points, decisions, sinks, reviewer notes, validated findings | Paths not selected, inaccessible services, assumptions that could not be tested |
| Code-assisted runtime validation | Whether an agreed behavior can be reproduced in the authorized build or environment | Build/revision identity, accounts, requests, responses, logs, test conditions | Deployed drift, external services, untested roles, and production-only behavior |
Manually trace critical security paths
Manual review should follow questions derived from the system, not wander through files. Begin with assets, roles, trust boundaries, entry points, and misuse cases. Then select paths where a mistake changes access, money, sensitive data, tenant separation, execution, or an irreversible state.
For authorization, trace from the externally reachable handler through identity parsing, tenant selection, object loading, policy evaluation, caching, and the final sensitive operation. Record where the decision is made, which attributes feed it, what defaults apply when context is missing, and whether another entry point reaches the same sink without the check. For data flow, identify the source, transformations and validation, framework behavior, sanitizer assumptions, and sink. For a business workflow, write the invariant first and follow every transition that can violate it. The purchasing details for that last form of work are covered in our guide to business logic security testing.
Consider a hypothetical multi-tenant invoicing service. The main API resolves the tenant from a signed token and calls a shared loadInvoice helper. A background export handler accepts a tenant identifier from a queued message, then calls the same helper. Tools can detect some authorization patterns, but generic rules without this application-specific relationship may raise no alert even though every value is correctly typed. A manual trace asks who can create the message, whether its tenant is bound to the job owner, and whether the helper enforces that relation. The coverage record should name both entry points, the shared decision point, the export sink, and the assumptions that require runtime confirmation.
Manual coverage should be expressed as reviewed paths and decisions, not “70% of the code read.” Keep a ledger of authentication, authorization, cryptography, deserialization, file handling, command and query construction, secrets, outbound requests, logging, administrative actions, and critical workflow transitions. Mark each as traced, tool-only, sampled, blocked, or out of scope, with a reason.
Validate findings before accepting the report
An automated alert is a candidate. The provider should inspect the actual call path, configuration, preconditions, existing controls, and likely impact before presenting it as a vulnerability. When safe and authorized, a focused test against the agreed build can distinguish a reachable weakness from dead code or an incorrect model. When runtime confirmation is unavailable, the report should say so and preserve the uncertainty instead of implying exploitation.
Every accepted finding should identify the affected revision, file and symbol or stable code location; the security rule that fails; the relevant source-to-sink or authorization path; required attacker position and conditions; impact; confidence; severity rationale; and remediation direction. A line number alone decays as soon as code changes. Function, path, revision, and a concise evidence excerpt make the record durable.
Triage also needs an audit trail. Preserve raw findings and their disposition: confirmed, duplicate, false positive, accepted risk, mitigated elsewhere, unverified, or out of scope. A finding disappearing from a later scanner view does not itself prove a fix. A rule may have changed, a file may have been ignored or removed, or a different branch may have been scanned.
The report must also explain negative results. “No critical findings” means only that none were confirmed by the stated methods in the stated scope and time. It is not a certification or proof that the code has no vulnerabilities. A useful sample pentest report shows the level of evidence a technical finding needs, but source-review acceptance also requires the revision, build, and coverage ledger described here.
Accept remediation with revision-linked evidence
Define retest terms before work begins: which severities or findings are included, the submission window, how many cycles, what evidence developers provide, and whether variant analysis is expected. Each fix should arrive as an immutable revision or narrow diff linked to the original finding. The reviewer checks the changed control, the original vulnerable path, foreseeable sibling paths, and any new behavior introduced by the patch.
Agree closure meanings in the contract and keep them distinct. For example, fixed can mean the reviewed change addresses the cause and the original weakness cannot be demonstrated with the agreed method under comparable conditions. A source-only retest should still preserve uncertainty about runtime behavior. Partially fixed can mean risk fell but a path or variant remains. Mitigated can mean another control limits impact while the cause remains. Accepted risk is the owner’s decision, not a technical fix. Cannot verify records missing build, access, or environmental evidence.
For the hypothetical export flaw, a convincing retest includes the fix commit, the changed job-creation and worker checks, tests showing a job cannot cross tenants, a review of other callers of loadInvoice, and—if runtime validation is in scope—evidence from the build created from that revision. A closed ticket or absent scanner alert is weaker. Our separate guide explains how to structure remediation validation across findings and variants.
Protect the source and buy a usable evidence package
Before access is granted, agree who can see the source, where copies may exist, how access is logged, whether subcontractors participate, how long working copies and logs are retained, and how deletion is evidenced. Use controlled test credentials where practical and agree controlled handling for secret evidence that the review truly needs. Document every redaction or replacement because it changes the supplied baseline and may hide a relevant path. Prefer read-only, time-limited access when it meets the method.
AI-assisted or hosted analysis needs its own boundary. The provider should name which code, findings, prompts, metadata, and logs leave the buyer-controlled environment; the service and region receiving them; retention and training terms; subprocessors; operator access; and the local or offline alternative. Also agree whether repository text is treated as untrusted input to an agent and which actions the agent may take. “We use AI” and “we do not train on your data” are not enough to define the data flow.
Price and schedule should follow the evidence burden: languages and framework novelty, number of build targets, dependency and generated-code complexity, critical paths, environment readiness, validation depth, and retest cycles. Compare proposals against the same coverage matrix. A low fixed fee can be sensible for a narrow diff; it cannot be compared honestly with a baseline review that includes custom modeling and code-assisted validation.
The final package should contain the signed scope and authorization, baseline and exclusion ledger, source-access record, tool and build configuration, automated coverage and triage evidence, manual path ledger, validated findings, limitations, remediation decisions, and revision-linked retest results. It should state which release or artifact relationship was evidenced and which was merely assumed.
If runtime impact matters, commission that step explicitly. An agreed white-box web application pentest can use source context to target the running application, while the code review explains implementation paths. Pentest.red can scope a formally authorized white-box test or code-assisted validation with source-review activities explicitly listed in the statement of work. Start with the scope planner to record systems, revisions, roles, build evidence, exclusions, handling constraints, and acceptance deliverables. The result is a bounded technical assessment and evidence package, not certification and not a guarantee that every weakness has been found.
PENTEST.RED
Discuss pentest scope and pricing
We start by agreeing on systems, access, constraints and the expected evidence.
Based on in-depth research, edited for clarity and practical use.