A scanner false positive is a specific claim that fails when checked against the relevant code, controls, and deployment context. The presence of a suspicious line is enough to start a review; it is not enough to conclude that an attacker can reach it or cause the stated harm. Investigate the path from actor and input to the harmful effect, actively look for controls that could defeat it, and record what remains unknown. If the evidence defeats the claim, reject that claim with a reason. If a missing fact could change the answer, keep it under investigation. If the path survives independent review, move it to the team's approval and remediation process.
This distinction matters because static tools can report false positives, while manual review can miss real issues too. OWASP identifies false positives as a limitation of source analysis tools and recommends human review of application logic and data flow where context matters in its Secure Code Review Cheat Sheet.
What a false positive actually means
The label should apply to a bounded assertion, such as “an ordinary account can read another account's invoice through this route at revision A.” It should not mean “this scanner is noisy,” “the developer says we already check permissions,” or “we could not reproduce it.” A code citation may correctly show an unscoped data read while missing a preceding account check. Conversely, a route may contain a check that is bypassed by another caller. The review has to establish which claim the evidence supports.
Keep four outcomes separate:
| Outcome | What the evidence says | Next action |
|---|---|---|
| Supported | The actor, entry, controls, and harmful effect form a source-supported path in the declared scope. | Assign the supported repair; use independent challenge for consequential or disputed conclusions. |
| Rejected | Counterevidence defeats this exact path or premise. | Record the control and revision; avoid a blanket suppression of related alerts. |
| Unresolved | A deploy flag, external authorization decision, generated route, or other material fact is missing. | Name the owner of the missing fact and a date or event for review. |
| Out of scope | The component or environment was not part of this assessment. | State the excluded scope and assess it separately if risk warrants. |
A risk acceptance decision is different again: the problem may be real, while an authorized stakeholder chooses to defer the fix. A patched line is also not proof of closure until the new behavior is checked. GitHub's code scanning alert workflow distinguishes dismissal reasons, which is a useful reminder that “not used,” “false positive,” and “won't fix” tell different stories.
Why good tools still raise wrong claims
They see a pattern but not the complete path. A query may find user input near a database call or an object read without seeing a wrapper that enforces ownership. Even data-flow alerts need inspection of the path and assumptions. CodeQL's path-query documentation explains that path explanations show the flow associated with a result; that explanation is evidence to examine, not a live exploit.
They miss a control outside the highlighted function. Middleware, route mounting, policy engines, database row filters, and gateway checks may sit elsewhere. The control must be proved to govern the exact route and object. A generic login check does not prove object ownership. OWASP calls for permission checks on every request and on the specific object or function.
They apply the wrong runtime or deployment model. A method may be present in source but excluded from the deployed build, reachable only in an isolated maintenance job, or behind a flag whose production value is unknown. “Not seen in traffic” is weak counterevidence: a rare path may still be exposed. Use the release artifact, route table, configuration, and deployment ownership rather than a folder name.
They use an incomplete model of a framework or library. Generated routes, reflection, dependency injection, or custom wrappers can hide either the entry or the defense. The mirror problem is a false negative: a scanner that misses a path has not proved safety. Record what was scanned and what was not.
They treat package presence as exploitability. A manifest or lock file can identify a vulnerable package version without showing whether the affected component is shipped or the vulnerable feature runs. OpenVEX separates “component not present” from “vulnerable code not in the execution path” as distinct not-affected justifications. Neither justification should be asserted without supporting artifact or path evidence.
Illustrative case: an invoice read that looks unguarded
Illustrative repository: Meridian Billing. A scanner flags an invoice lookup in a handler at revision 7a1. It proposes CWE-862: Missing Authorization and says any signed-in user can supply another customer's invoice ID. The highlighted handler has no inline ownership check.
An AppSec reviewer asks for the route and follows the call graph. The GET route calls a repository function with both the session's account ID and the requested invoice ID. The database query constrains both columns. The service owner supplies the deployed bundle and route map; a second reviewer confirms that this GET route uses the same scoped lookup in that bundle. A separate POST export route, however, calls a legacy lookup by invoice ID alone. The original GET-route claim is defeated for the assessed deployment; the export route needs its own investigation. Rejecting the scanner's exact alert does not establish that invoice access is safe across the service.
A compact review record makes the distinction visible:
| Field | Recorded observation |
|---|---|
| Candidate claim | GET /api/invoices/:id lets account B read account A's invoice. |
| Actor and input | Signed-in account B; invoice ID supplied in the path. |
| Entry and effect | GET route returns invoice metadata and downloadable PDF. |
| Counterevidence | At revision 7a1 the GET route passes a server-derived account ID to a query constrained by account ID and invoice ID; the deployed bundle and route map use that lookup. |
| Original claim decision | Rejected for this GET route and assessed deployment after independent review. |
| New question | POST /api/invoices/export calls a legacy lookup without the same visible constraint. Track as a separate candidate. |
| Missing fact | Effective registration and gateway behavior for the export path. |
| Owner and next check | Service owner supplies the export route registration and gateway policy; AppSec reviews the export call chain. |
The evidence above is a source-and-deployment review narrative, not a claim that an exploit was executed. Do not paste customer invoice data or raw source into an executive note. Give engineers authorized references to exact revision and code ranges.
Diagnose the cause before tuning the rule
Meridian's scoped wrapper is one kind of scanner error. Compare it with two different causes in this synthetic review batch. All cases and outcomes are constructed; no assessments or controlled requests were performed for this article.
| Claim | Decisive supplied artifact | Disposition | Appropriate improvement |
|---|---|---|---|
| GET invoice lacks account scope | Mandatory wrapper binds trusted account and invoice ID in one query. | Reject this GET claim; export remains separate. | Propose a model for this wrapper's actual constraints, including caller binding. |
| Staff maintenance handler is public | Effective registration puts the handler behind the staff-only router; no alternate mount exists in the selected build. | Reject the public-actor premise; separately assess staff permissions if relevant. | Correct the entry/role model, rather than declare every operation in the file safe. |
| Vulnerable package is in the customer API | Lock file identifies the package, but artifact inventory is missing. | Investigation: package identification is correct; production applicability is unknown. | Improve build inventory intake. Suppression cannot supply the missing artifact. |
A mixed batch of these three reasons does not justify “turn off authorization checks.” Only the first provides a candidate wrapper-model change. The second concerns framework wiring. The third is an evidence collection gap, not demonstrated scanner error.
For Meridian, the tool-improvement request should say:
Recognize
find_invoice_for_account(trusted_account, invoice_id)only when the implementation enforces both predicates and the caller supplies a server-derived account. Preserve alerts forfind_invoice(invoice_id)and for calls whose account comes from the request. Reevaluate the model when the wrapper or framework binding changes.
That request contains a positive case, two counterexamples and an invalidation condition. A tool maintainer can assess it. “Mark InvoiceRepository safe” would hide the legacy export path the review just discovered.
A model change needs evaluation against known safe and unsafe variations in the authorized tool-development environment. This article does not claim that any scanner supports this exact model or that the proposal improves detection. Until its behavior is checked, keep the narrow human decision and the model proposal separate.
Use 017's review workflow to operate the queue, 018's suppression guide to inspect what an ignore action conceals, and 020's measurement guide to compare outcomes without losing the denominator.
What would change the verdict?
The Meridian GET route is not safely dismissible if the account ID comes from a client header rather than a trusted session, the query wrapper silently drops the account constraint, or a second route calls the same handler before the scoped lookup. If the production bundle differs from revision 7a1, the recorded counterevidence does not apply to that bundle. If the export route is confirmed to return invoices across accounts, it becomes a distinct supported claim even though the original GET alert was rejected.
The reverse is also important. An inability to reproduce a cross-account read in one test account may reflect missing privileges, data, or timing. That is useful negative evidence, but it does not prove the path impossible. Where safe controlled validation is authorized, use it to challenge source reasoning; record the environment and limits. Do not describe a source-only review as a penetration test.
Choose evidence that can actually defeat the premise
Different objections answer different parts of the claim. A route registration can establish an entry but cannot show that an ordinary user may enter it. A policy document states intended access but cannot prove that the deployed handler enforces it. A source check on an object can defeat an ownership claim only when the reviewer confirms the actor and the returned object are the same ones checked. A deployment manifest can defeat a production-applicability claim for one artifact while saying nothing about an older release or another service. A controlled request can expose a bypass, but one denied request cannot establish the absence of every bypass.
Ask the reviewer to write one sentence in the form “This evidence defeats [specific premise] for [revision and environment].” If that sentence cannot be completed, the objection is a lead for further investigation rather than a rejection reason. This small discipline prevents “there is middleware” and “it did not reproduce” from becoming unreviewable closure notes.
How to reduce noise without hiding risk
Tune rules only after collecting disposition reasons across examples. If most rejections share one provable wrapper, model that wrapper or add a narrowly scoped exception tied to the control and version. Review exceptions when routes or frameworks change. Avoid suppressing all instances of a weakness class because one path was protected. Distinguish unresolved claims from confirmed defects so engineers understand the basis for each task. A justified precautionary repair can proceed while AppSec gathers evidence, with its provisional purpose stated.
Track at least two numbers: rejected alerts with reasons, and supported findings that later needed correction. The first can reveal expensive noise; the second can reveal weak verification. Neither is a product-quality score without knowing the assessed scope, rule set, and sampled cases. A low alert count might mean strong code, poor coverage, or a disabled scanner.
For the next concrete task, take one noisy alert and fill the seven-field case record above at a pinned revision. If reachability is unclear, use the code-path reachability walkthrough. For a claimed shared control, follow the middleware authorization guide; for input transformation, use the sanitizer review; for package alerts, use the production dependency checklist.