Write six tiny programs where untrusted input reaches a dangerous call by increasingly indirect routes, and see which ones each tool reports. The pattern of hits and misses tells you how deep its data flow analysis goes, faster than any feature list. Add a safe twin for each case to catch false alarms.
Why depth matters
Simple pattern matching can find a banned function call. Real injection flaws usually involve a value that travels: read from a request, stored in a variable, passed through helpers, perhaps across files, and finally used in a query or command. Tools differ a lot in how far they follow that path.
You do not need a vendor to tell you. Six samples in your main language will show it.
The six cases
Each one has a vulnerable version and a safe twin, where the input is cleaned or constant.
1. Direct. Request parameter goes straight into a database query string. Every serious tool should catch this. If one fails, stop there.
2. Through a variable. The value is copied to another variable, concatenated with text, then used. Checks basic propagation.
3. Across a function. The tainted value is passed into a helper that builds the query and returns it. Checks whether the tool follows calls.
4. Across files. The helper lives in a different module or package. Checks cross file analysis, which many tools do with limits.
5. Through a sanitiser. One version cleans input with the framework's standard escaping call. A second uses your own internal cleaner. The first should not be flagged. The second depends on whether the tool can learn your cleaner, which links to custom rules.
6. Through a framework. The value arrives via your framework's binding, such as an annotated controller parameter or a template context, rather than a raw request object. Checks whether the tool understands the frameworks you really use. OWASP lists the ability to understand libraries and frameworks as a selection criterion for this reason.
How to score
Make a grid: six vulnerable cases and six safe twins, per tool. Mark found, missed, or flagged wrongly.
A tool that catches cases 1 to 3 and misses 4 may still be fine for you, if your code is mostly self contained services. A tool that catches all six vulnerable versions but also flags every safe twin will generate noise, so look at both halves.
What to do with the result
Write the grid into your evaluation notes alongside what the vendor said. Where a tool misses a case, ask the vendor why. Good answers explain a design limit, such as a depth cutoff for performance, and tell you whether a setting changes it. Weak answers deny the miss or change the subject.
Remember what this does not show. Static analysis misses things that happen at runtime. Reflection, dynamic dispatch and configuration driven wiring can hide flows. Treat a clean result as the absence of a pattern, not proof of safety.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how a finding shows the path from input to sink.
Quick check
How many test cases are enough?
Six paired cases are enough to see the shape of a tool's depth. They do not produce an accuracy rate.
Should I test every language I use?
Yes for your top two or three. Depth can differ sharply between languages in the same product.
Safeguard's reachability analysis, which traces whether vulnerable code is actually called, is generally available, and you can try the free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.