Choosing a SAST tool without being fooled

Two tools scan your application. One returns eight alerts, the other sixteen. Which should you choose?
Without knowing what they analyzed, what they missed and how long their results take to handle, those counts cannot settle the decision. Here is how to build a trial that can.

This article builds on SAST in depth, but stands on its own.

Start with the work to be done

The right tool must cover your code and fit your team. Before a demo, write down three constraints:

  • Required code coverage. Languages, frameworks, generated code, repository size and whether you can build it. A “Java supported” checkbox does not tell you whether your web inputs and internal libraries are understood.
  • The decision point. A check before merging and a deep overnight analysis have different time budgets. Specify what should block a release and who can resolve a disputed alert.
  • Operating conditions. Whether code may leave your network, Internet access, required permissions, budget and who will maintain the rules.

An engine detects. A platform also provides result management, permissions and integrations. For each offer, distinguish what is included, what needs another product and what your team would have to build.

The perfect-score trap

One public benchmarkbenchmark: a shared set of tests used to compare tools, or reference test suite, is the OWASPOWASP: a foundation that publishes free application security guides and tools Benchmark Java 1.2, published by OWASP (Open Worldwide Application Security Project), a non-profit foundation.

2,740

small test programs

1,415

real vulnerabilities

1,325

cases without the tested flaw

The counts above come from the expected results file for version 1.2.

Its score is the share of flaws found minus the share of safe cases wrongly flagged: a tool that flags everything scores zero.
That is a good idea.

But this Java corpus is synthetic (there is also a Python one): an excellent score says little about how the tool behaves on your code. And beware of Goodhart's law: when a measure becomes a target, it ceases to be a good measure. Tuning for this corpus can improve the score without improving detection on your application.

Same limit for NIST's Juliet suite, tens of thousands of synthetic cases in C, C++ and Java: useful for testing rules, insufficient on its own for choosing a tool.

SAST engines, one minute each

With the vocabulary from the deep dive (syntax tree, taint, sensitivities), these engines are easier to read (profiles checked on 8 October 2026, sources linked from tool names). They are not interchangeable: first select those covering your languages and defect types. For each one: what it builds, what it looks for, and its limitation.

EngineWhat it buildsWhat it looks forBlind spot
Opengrep and SemgrepOpengrep: open-source fork of Semgrep, LGPL-2.1, since January 2025.A syntax tree, the equivalent of parsing a sentence's grammar, in a common representation for supported languages.Rules written like the target code, with wildcards, plus taint.Scope depends on engine, edition and language.
CodeQLOpen queries, CLI subject to GitHub's terms.A database of the code, produced by one extractor per language.Built-in security queries, extensible with the QL language.Database preparation; expertise needed for custom queries.
JoernOpen source, Apache 2.0.A code property graph: syntax, control flow and dependencies overlaid.Graph queries, run interactively or through scripts.Continuous integration requires suitable configuration.
PysaPublished by Meta, MIT license.Function summaries, helped by the type of each value (text, number, object).Taint flows computed in successive passes, until nothing changes.Python only.
InferPublished by Meta, MIT license.A mathematical description of what each function does with memorymemory: the space where the program keeps its data while it runs.Memory and concurrency defects, depending on the enabled analyses.Built for null pointersnull pointers: references to data that does not exist, which crash the program and leaks more than for web injections.
SpotBugs and Find Security BugsOpen source.Control-flow graphs and dataflow analyses of compiledcompiled: translated into bytecode, the intermediate format the Java virtual machine runs Java.Known dangerous calls in the most common Java web frameworksframeworks: ready-made toolkits for building an application.Requires a compiled project.

For Checkmarx, Snyk Code and SonarQube, verify the edition, languages and models included in the vendor's dated offer.

Judging a SAST tool on your code

On a copy of your repository, fix the scope, tool versions, rules, exclusions and available resources. Keep commands and reports so you can repeat the trial. Compare defaults first, then record the time spent tuning them.

Prepare a trial you can repeat
  1. Choose the scope. A representative repository, its dependencies and build commands. Keep the same copy for every candidate.
  2. Establish reference cases. Known vulnerabilities, their fixed versions and safe code that resembles a flaw. Include your actual framework. Record expected results before running the tools.
  3. Run two passes. First without corpus-specific tuning, then with the same configuration time budget. Hold a few cases back to check that models work beyond the examples already shown.
  4. Have your team triage. A developer and an AppSec analyst review the same alerts without the vendor's conclusion in front of them. Record disagreements, time to understand and missing evidence.
  5. Keep the evidence. Versions, configuration, skipped files, errors, results and decisions. An alert disappearing after a failure or exclusion is not a fix.
  6. Detail of the five tests in the table. Flows: check each alert and its disappearance after the fix, with identical configuration. Sample: 50 alerts, or all if fewer, measuring known flaws found and missed separately. Sanitizer: the unprotected case and the one with unsuitable protection must still trigger. Unreadable file: check the diagnostic, the partial or absent analysis, and the exclusion reason. Pull request: separate first and cached analyses, and record total time, memory and any timeouts.

Five questions, the evidence to demand, and the test to run yourself:

QuestionEvidence to ask forTest on your repository
How far does data tracking go?Per function, per file, across files, for your languages and frameworks.A vulnerable flow within one function, then across two files, before and after the fix.
Show me the path.Flow paths, or context and reasoning for other alerts.Review a random sample of alerts and count the confirmed and unresolved ones.
How do we declare our own sanitizers and inputs?The model format, with an example on your framework.Model a verified protection: the protected case should be dismissed, the others must still trigger.
What happens when the tool cannot read a file?The real coverage: files analyzed, skipped, and why.Add a file with a syntax error and an excluded file: neither should be presented as safe.
How long on a pull request?The analysis time for one change, on a repository your size.Repeat representative changes with fixed resources, with and without cache.

Read two reports without misreading the results

Fictional example, unrelated to the tools listed. The reference set contains 12 vulnerable cases and 12 cases without the flaw being tested. Both engines analyze every file. Count one alert per case after grouping duplicates.

A shorter report can hide more missed flaws.
MeasureEngine AEngine B
Alerts to review816
Real flaws found8 of 1211 of 12
False alerts05
Missed flaws41
Precision8 ÷ 8 = 100%11 ÷ 16 ≈ 69%
Recall8 ÷ 12 ≈ 67%11 ÷ 12 ≈ 92%

A provides fewer alerts to review but misses four flaws. In this example, B finds all eight flaws found by A and three others. It needs eight additional reviews: three concern extra real flaws, five concern false alerts. The decision also depends on which flaws were missed and your capacity to handle the results.

These percentages do not carry over to your repository, where unknown flaws remain unknown: recall is measured on your reference cases. Keep unresolved alerts separate.

The trap to avoid:zero alerts with zero files analyzed is not a clean result. And 100% precision on eight alerts does not mean every flaw was found.

Test the work after the alert

Take a real alert from the pilot and give it to whoever would fix it. Can they locate the input, the dangerous operation and the steps between them, explain the fix and verify the corrected behavior beyond the signal simply disappearing? Then follow the same defect through changed line numbers, a new scan and a fix: its history must stay attached to the right issue. Also try a justified exception: who approves it, when does it expire and what happens if the code changes?

If the vendor offers an AI-generated fix

Evaluate the fix as a code change: it must remove the cause, preserve expected behavior and pass tests. An ignore comment or a disabled rule can make an alert disappear without fixing the problem.

Ask what code and context are sent to the model, where they are retained and whether this feature can be disabled. Measure the time spent reviewing the fix, not just generating it.

Finally, simulate an interrupted or incomplete analysis in the pilot. The release check must follow your chosen policy and explain the failure. A green check without usable results distorts the entire comparison.

Before you sign

Set your criteria before the trial: required languages, known flaws to detect, triage budget and acceptable time per change. A good overall score cannot compensate for a required language left unanalyzed. Test a SARIF export in the tool that will receive your results: locations, traces and identifiers must remain usable. Importers do not all support the same fields, as illustrated by the GitHub documentation. Separately check the recovery of decisions, comments and assignees. Finally, ask for the total cost (license, execution, tuning, triage), the data sent outside your environment and its retention period. Record gaps against your criteria before deciding.

Wrapping up

An alert count does not settle a choice between two SAST tools. What matters: what the tool actually read, the known flaws it finds in your code, the time it takes to decide each alert, and what it does when it fails. Set these criteria before the demo, replay the trial on your repository, and keep the proof.

Words in this article · 4 words
Benchmark
A shared set of tests used to compare tools.
Precision
The share of a tool's alerts that are real flaws.
Recall
The share of real flaws a tool finds.
SARIF
A standard format for exchanging code analysis results between tools.

To revisit the basics: SAST explained simply. And for imported blocks: Choosing an SCA tool without being fooled.

Share this article

LinkedIn Bluesky X Email RSS

Start here: SAST in depth: engines, taint and limits