Examples in this article are illustrative, not customer case studies. Product availability and coverage are described on the linked capability pages.
A developer opens a pull request for the document-export service, sees a wall of static analysis warnings about shell commands, and closes the tab because the last batch they checked was all false alarms. This guide explains sources, sinks and sanitisers, the three ideas behind SAST taint analysis, so you can tell which warnings deserve attention and why some tools produce far fewer of them.
SAST taint analysis is a static analysis technique that labels data from untrusted sources as tainted, follows it through assignments, calls and returns, and reports a finding only when it reaches a security-sensitive sink without passing a sanitiser. The output is a path a reviewer can check, not a pattern match on a risky-looking line.
The document-export service is the example throughout. It accepts a filename query parameter, reads a template from disk and calls a command-line converter to produce a PDF. That gives untrusted input two chances to do damage: the file path and the shell command.
Every injection bug is untrusted data reaching a sensitive operation
A source is any point where data enters from somewhere you do not control: a query parameter, a form field, a header, a message from a queue, a file a user uploads. In the export service, filename is a source. Nothing about that value is dangerous yet; what matters is where it goes next.
A sink is where data is used in a way that matters for security: concatenated into SQL, written into HTML, passed to a shell, used to build a file path or deserialised into an object. The export service has two sinks, the call that opens the template and the call that runs the converter. A constant argument can rule out some input-injection paths, but does not establish that the operation is safe in every respect.
The bug exists when untrusted data flows from a source to a sink without being neutralised on the way. A sanitiser is that neutralising step: a parameterised query, HTML encoding, or a check that a resolved path stays inside the templates directory. “Taint” is the label a value carries once it comes from a source. It follows the value through assignments, calls and returns, and it is cleared when the value passes through something the analysis recognises as a sanitiser.
Pattern-matching SAST cannot tell constants from attacker input
Many SAST tools do not track data at all. They match syntax: this line runs a shell command, this line writes to a response. A pattern fires whether or not the argument came from a request, and whether or not it was validated earlier in the function. To such a tool, a converter call built entirely from constants looks identical to one built from filename.
Suppose the export service sends every request through a validation layer that rejects anything except known template names. A pattern-matcher still flags every converter call on every pull request. Engineers check the first few, confirm each one is safe and stop reading, and from then on the tool provides no value, including on the one warning that was real. This is what people usually mean by “noisy” SAST: the rule was reasonable, but the tool never checked its precondition, that untrusted data actually reaches the sink.
SAST taint analysis reports a path, not a pattern
A taint-aware engine models how data moves through a function, and usually across functions and files, before it raises anything. It starts from a catalogue of sources for the language and framework, follows the data through assignments and calls, and checks each sink for two things: whether tainted data arrives, and whether a recognised sanitiser sits on every path in between. Only an unsanitised source-to-sink path becomes a finding.
That changes what a finding means. Instead of “a shell call exists in this file”, the result reads: filename enters at the export handler, passes through the name builder and reaches the converter call without validation on this branch. A reviewer can confirm or dismiss that quickly, because the engine has done the reasoning a careful human would do, within the engine’s modelling and resource limits. When a new code path skips the validation layer, the engine reports that path and leaves the protected ones alone.
Precision depends on per-language source and sink catalogues
Taint tracking is only as good as its knowledge of what counts as a source, a sink and a sanitiser in a given language and framework, and those catalogues differ widely. A request object that is raw input in one framework arrives pre-validated in another. An ORM query builder is a sanitiser when used as designed and an injection trap when handed an interpolated string.
A generic, language-agnostic ruleset is left with two poor options. It can generalise until it loses precision, or keep narrow rules that miss framework-specific sinks. Analysis built per language, with catalogues tuned to that language’s standard library and common frameworks, finds classes of bug that a one-size engine either misses or over-reports. When you evaluate a tool, ask to see the sources and sinks it models for your main framework.
Taint analysis models a possible flow; validate its assumptions
Taint analysis has limits, and knowing them makes its output more useful. It reasons about flows inside the code it can model, usually one service or a small group analysed together. A value that crosses a network call into another service, is stored and later reread by a different component, or passes through infrastructure the engine does not model can fall outside what it tracks.
It also reasons about what code could do, not what an attacker can reach today. A reported path in the export service matters a great deal if the service faces the internet, and far less if it runs only as an internal batch job with no external ingress. That question, reachable in code versus exposed in the running system, is a separate layer of analysis, covered in reducing SAST false positives without hiding real bugs. Hard-coded credentials are a different problem again, handled in secrets detection beyond HEAD.
Key takeaways
- Ask any SAST tool to show the source-to-sink path behind a finding, and treat findings without one as lower confidence.
- Put validation in a layer the analysis recognises as a sanitiser, so that safe calls stop producing warnings.
- Check that the tool’s source and sink catalogue covers the frameworks your services actually use.
- Rank reviewed taint paths by whether the service is deployed and exposed, not by the rule’s severity alone.
How SecurityVault approaches this
SecurityVault runs native per-language analysis alongside supported open-source SAST tools. Findings can include source context and modelled dataflow evidence where the engine supports it. Coverage varies by language and framework; a reviewer still validates the path and its deployment relevance. Human dispositions retain the reason for accepting or dismissing the reported weakness.
Evaluate the languages and frameworks your team actually maintains. Choose a small example with a known unsafe flow and a related example protected by a recognised control, then inspect what the engine reports for each. Include a framework-specific wrapper or dynamic call to expose modelling limits. Developers should be able to follow the evidence back to the relevant code and state what still needs manual confirmation. This gives the evaluation a more useful basis than comparing raw finding counts across tools with different rule sets.
Explore SAST coverage or talk to our team about an evaluation with your engineering, security and compliance stakeholders.