Safeguard
Application Security

How Snyk Code detects cross-site scripting (XSS) through ...

How Snyk Code's taint analysis traces untrusted input from source to sink to flag reflected, DOM-based, and stored XSS with fewer false positives.

Aman Khan
AppSec Engineer
7 min read

Cross-site scripting bugs rarely look dangerous in a code review. A developer takes a value from req.query.name, drops it into a template string, and calls res.send(). Nothing crashes, the tests pass, and the pull request merges. The vulnerability only becomes visible when an attacker crafts a URL that turns that same query parameter into a <script> tag rendered in another user's browser. Pattern-matching scanners that just grep for innerHTML or eval either miss this kind of bug entirely or bury real findings under hundreds of false positives. Snyk Code, the static application security testing (SAST) engine Snyk built from its 2020 acquisition of DeepCode, takes a different approach: it models how data actually moves through a program, and flags XSS only when untrusted input can reach a dangerous rendering point without being sanitized along the way. Here is how that mechanism works.

What Is Data-Flow Modeling, and Why Does XSS Detection Need It?

Data-flow modeling means tracing a specific value's journey through a program's execution paths, from where it enters to where it gets used, rather than scanning code for suspicious keywords in isolation. XSS is fundamentally a data-flow problem: the vulnerability doesn't live in a single line, it lives in the relationship between two lines that might be dozens of function calls and several files apart. A snippet with element.innerHTML = data is only exploitable if data originated from something an attacker can influence, like a URL fragment, a form field, or a database record that was itself populated by user input. Keyword-based scanners can tell you innerHTML appears in the file; they can't tell you whether the value assigned to it is attacker-controlled. Snyk Code builds a model of the program (an abstract syntax tree combined with control-flow and data-flow graphs) so it can answer that second question directly, which is the question that actually determines exploitability.

How Does Snyk Code Trace Tainted Data from Source to Sink?

It uses taint analysis, a static-analysis technique that labels data as "tainted" at defined entry points and tracks whether that taint reaches a defined exit point through any executable path. A "source" is anywhere untrusted data enters an application, such as req.query, req.params, req.body, location.hash, document.referrer, or a WebSocket message payload. A "sink" is an operation that can turn that data into executable content in a browser, such as innerHTML, outerHTML, document.write, eval, insertAdjacentHTML, or framework-specific escape hatches like React's dangerouslySetInnerHTML and Angular's bypassSecurityTrustHtml. Snyk Code performs this tracing interprocedurally, meaning the taint can be followed as a value is passed as a function argument, returned from a call, stored on an object property, or reassigned across multiple statements, rather than only catching same-line or same-function cases. If the engine can construct a path from a source to a sink, and no recognized sanitizer (such as DOMPurify.sanitize() or a framework's built-in output encoding) interrupts that path, it reports the finding with the full trace, source line, and sink line shown in the IDE or scan output.

Which XSS Variants Can This Approach Actually Catch?

Data-flow modeling is well suited to reflected, DOM-based, and stored XSS, because all three reduce to the same source-to-sink pattern even though the data travels through different layers. Reflected XSS shows up when a request parameter flows straight into a server-rendered response within the same request lifecycle, for example an Express route that interpolates req.query.q into an HTML string passed to res.send(). DOM-based XSS happens entirely client-side, when a value like window.location.hash is read by JavaScript and written into the DOM via innerHTML without ever touching the server; Snyk Code's client-side taint model covers this because it doesn't require the sink and source to be in the same request/response cycle, just reachable in the same execution flow. Stored XSS is harder because the taint has to survive a round trip through a database or cache between the source (an earlier write, like a comment submission) and the sink (a later read, like rendering that comment). Snyk Code's public documentation is explicit that stored, multi-request taint tracking is the hardest of the three to model precisely, which is why sanitization at the point of output, not just at the point of input, remains the safer engineering practice regardless of what the scanner catches.

How Does Snyk Code Keep the False-Positive Rate Down?

It combines the deterministic taint-analysis engine with a machine-learning model trained on real-world fix commits, so the ML layer filters and ranks findings the symbolic engine produces instead of generating them from scratch. This hybrid design traces back to DeepCode, the Ph.D.-founded static analysis startup Snyk acquired in October 2020, whose core idea was to learn what security-relevant code patterns look like by ingesting millions of open-source commits where developers actually fixed vulnerabilities, including the surrounding context of what "before" and "after" looked like. When the taint engine flags a candidate source-to-sink path, the learned model scores it against patterns it has seen resolved (or dismissed) in real codebases, which is how the tool distinguishes something like a value that passed through a recognized sanitizer function from one that only looks sanitized because it went through an unrelated string transform. Snyk has publicly stated this combination is central to why Snyk Code's false-positive rate runs meaningfully lower than legacy SAST tools that rely purely on rule matching, though exact percentages vary by codebase and language and shouldn't be taken as a universal guarantee.

Where Does This Analysis Run in the Development Workflow?

It runs as a static scan with no application execution required, which is what lets it plug into IDEs, pull requests, and CI pipelines at the point where fixing a bug is cheapest. Snyk Code ships as a real-time IDE plugin (VS Code, JetBrains, Visual Studio, Eclipse) that flags a tainted path as the developer types, a snyk code test CLI command for local or CI scans, and a PR-check integration for GitHub, GitLab, Bitbucket, and Azure DevOps that comments directly on the diff. Results export in SARIF format, the standard schema for static analysis output, so findings can flow into a central dashboard alongside other scanners. Because it's pure static analysis, it has none of the runtime, authentication, or crawl-coverage limitations that DAST tools have, but it also can't observe how a WAF, a CDN rule, or a runtime CSP header would have blocked the same payload in production, which is a gap runtime and dependency-level tooling is built to cover.

How Safeguard Helps

Understanding how a SAST engine like Snyk Code reasons about taint is directly useful for triage, because it tells you what a finding actually claims: not "this file contains a dangerous function" but "this specific path from this input to this sink was reachable in this build." Safeguard's software supply chain security platform is built to give teams that same level of specificity across the parts of the pipeline that source-code scanners don't cover: the provenance of the dependencies that sit between the source and the sink, the build artifacts that get shipped, and the CI/CD configuration that determines what actually reaches production. A tainted-path finding in application code and a compromised or typosquatted package upstream are both supply chain risks, but they require different evidence and different remediation, and treating them with the same generic severity score is how real findings get lost in alert fatigue. Safeguard correlates SAST, SCA, and provenance signals against a single asset and dependency graph, so an XSS finding tied to a specific input handler can be prioritized alongside whether the package that handler depends on has known integrity issues, giving security teams one place to see which findings represent genuine, exploitable risk in their actual build rather than working through disconnected tool outputs one dashboard at a time.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.