Est.

Approaches to Tracing Taint Through Third-Party Library Boundaries

Security tools go blind at library boundaries, letting vulnerabilities slip through undetected.

· 11 min read
Cover illustration for “Approaches to Tracing Taint Through Third-Party Library Boundaries”
AI-Native Security Testing · September 30, 2026 · 11 min read · 2,476 words

Taint analysis tracks untrusted data (things like HTTP params, file reads, environment variables) as it moves through a program, all the way to sinks like SQL execution, shell commands, or eval(), and it only raises an alert when there's a proven path with no sanitization in between. That's the whole idea in one sentence. The trouble starts the moment tainted data crosses into a third-party library. Most tools simply go blind at that point, reflecting a structural fact about how the analysis is built rather than a bug in any one product.

CodeQL is the clearest example. It severs taint flows at library boundaries unless someone has explicitly modeled that library, because third-party dependency code sits outside its analysis scope by default. Nothing unusual about CodeQL here, this is just what happens when an analyzer treats dependency code as a black box.

The scale of what's riding on this blind spot is hard to overstate. The npm registry alone holds more than 3 million JavaScript packages, pulling in tens of billions of downloads every week Multi-Agent Taint Specification Extraction for Vulnerability Detection. Between 2017 and January 2025, npm racked up 8,237 documented vulnerability reports, more than any other language ecosystem tracked Multi-Agent Taint Specification Extraction for Vulnerability Detection. Cross-site scripting and command injection attacks routinely travel straight through library code that scanners never actually inspect.

None of this is really a tooling failure in the sense of "someone wrote bad software." It's a coverage failure. The rest of this piece walks through the different ways researchers and vendors have tried to close that coverage gap, stub modeling, source inclusion, dynamic taint tracking, and now LLM-assisted specification extraction, each with its own tradeoffs to understand before picking one.

The conceptual skeleton every approach must satisfy: sources, sinks, sanitizers, and flow summaries

Every taint system, no matter how it's built, rests on four ideas. Sources are where untrusted data gets into the program: HTTP request parameters, file reads, environment variables, results pulled from a database. Sinks are where that data can do damage: SQL execution, shell commands, eval(), file writes, outbound HTTP calls. Sanitizers are the functions that legitimately break the taint chain (parameterized query builders and HTML-encoding functions, the stuff that turns dangerous input into safe input).

The fourth piece is the one that matters most for this discussion: flow summaries. A flow summary is a model describing how taint moves through a function the analyzer can't actually inspect, and it's the central mechanism any system needs to cross a library boundary. Reframed precisely, the library boundary issue is a flow summary completeness issue. The analyzer either has an accurate model of what a foreign function does to tainted data, or it doesn't, and everything downstream depends on that.

Every approach also has to navigate the same basic tension. Over-approximate, assume taint propagates through every unknown function, and recall goes up, but so does the flood of false positives, most of them unactionable. Under-approximate, assume it doesn't propagate unless proven, and the false-positive rate drops, but real vulnerabilities slip through silently. There's no dodging this tradeoff. Solving the library boundary issue, in the end, just means producing flow summaries that are accurate for code the analyzer can't read, and the approaches that follow differ mainly in how they generate those summaries.

Manual taint specifications and whole-program analysis: the baseline and its scaling wall

Manual taint specification is the oldest fix, and it's what it sounds like. Security engineers sit down and hand-write models for libraries they care about, documenting which parameters propagate taint to return values or to other parameters. It works fine for a small, stable set of widely-used libraries. It falls apart the moment dependency counts grow or the libraries themselves keep changing underneath the spec.

Writing these specs demands deep knowledge of each library's internals, and an ecosystem the size of npm, running to millions of packages, makes exhaustive hand-modeling a fantasy. JavaScript's dynamic behavior makes the job even harder: mapping what a library actually does at runtime back to a static specification is genuinely difficult even for libraries engineers already understand well.

Whole-program, or inter-procedural, analysis takes a different tack. Instead of hand-modeling libraries, it walks the entire call graph, doing alias and dataflow analysis across the full program. When it works, it can hit both high precision and high recall. It doesn't scale to large codebases: analyses that need to traverse millions of lines of code can take hours to finish, which rules them out for CI pipelines or quick local runs during development. YASA, the tool built at Ant Group, names this exact tension directly, pointing out that multi-language tools like CodeQL, Joern, and WALA run into limits on analysis precision and extensibility once they're pushed to industrial scale.

Both approaches, manual specs and whole-program analysis, hit the same wall from different directions. The number of third-party libraries in a modern application grows faster than any team of humans can write specs for them, and running exhaustive whole-program analysis across all of them is computationally out of reach. That wall is what every approach discussed from here on is trying to get around.

Synthetic test harness generation and multi-language unified ASTs: static approaches that push past the scaling wall

One patented approach, USPTO #10069855, generates a synthetic test application designed to call every entry point inside a third-party component. The harness isn't meant to run successfully, it's meant to be compiled and fed to a static analyzer so every possible data path through the library gets exposed. Methods inside the harness force the SAST tool to taint data passed into the library, so every possible path through the component gets analyzed without the analyzer needing the library's actual source code. The results can even surface vulnerabilities inside the third-party component itself, in how the calling application uses it. The harness has to be generated correctly; gaps in harness generation become gaps in coverage, full stop.

YASA takes a different route to the same destination. Modern enterprises run diverse technology stacks, and existing taint tools are mostly single-language. Engineering effort scales with however many languages a company happens to use. YASA's answer is the Unified Abstract Syntax Tree, or UAST, a single abstraction that represents code across different programming languages so one analysis engine can handle all of them. It was built for deployment at industrial scale inside Ant Group, so the constraint driving its design is real-world throughput, not academic benchmark scores. It directly targets the gap left by CodeQL, Joern, and WALA around intermediate representation design, precision, and extensibility at scale.

On the commercial side, SonarQube's Advanced SAST offers a production implementation of the same basic idea. It extends taint tracing deep into third-party libraries, specifically targeting how first-party code interacts with external dependencies, and it traces user-controlled data across the full execution flow, spanning multiple files, to pinpoint SQL injection and XSS. It does cross-function and cross-file analysis specifically to cut down on false positives, the goal being actionable results rather than a big pile of alerts nobody has time to triage.

What ties these three together is straightforward: each one pushes static reasoning further into library code than a traditional analyzer would go on its own. But they're all still bounded by the analyzer's ability to model code it can't fully read. That's the ceiling every purely static approach eventually hits.

Dynamic taint analysis: observing actual execution to bypass the modeling problem

Dynamic taint analysis (DTA) sidesteps the modeling problem in a fairly elegant way. Instead of trying to predict what a library function does to tainted data, it just watches what actually happens when the program runs. If tainted data flows through a function, the shadow state records it. No model required, because the execution itself is the model.

That elegance comes at a price. There's real runtime overhead, and coverage is only as good as the paths actually exercised during testing, any code path that isn't run during the test session stays invisible to the analyzer.

Interpreted languages like JavaScript and Python make the engineering especially tricky. Existing systems tend to bolt these three capabilities together inside a single instrumentation layer, either rewriting source code or hooking into the engine natively, and that coupling creates a persistent tension between how much overhead you accept and how accurate the results end up being.

Function summaries are one optimization. If static analysis can already prove that a given function never propagates taint from a particular input to any output, the runtime can skip tracking through it entirely, which speeds things up considerably compared to tracking every single instruction. Cross-language boundaries, Java calling into JNI is the classic example, remain a genuinely open problem. Hybrid dynamic-static approaches are starting to appear here, but nobody's solved it cleanly yet.

The overhead and instrumentation-coupling issues described above motivated a newer architecture built specifically to separate those concerns.

Mystra and Shar: a Shadow VM architecture that achieves library-boundary crossing at low overhead

The stated design goal behind this work is blunt: build a DTA engine that's extensible, fast, and accurate, all three at once. That's an ambitious ask given everything covered in the last section, and the answer is an architecture called the Shadow Virtual Machine.

The Shadow VM runs as a parallel abstract machine alongside the host runtime. It consumes well-bracketed entry and exit events from the running program and updates four separate shadow states as it goes, a shadow stack, a shadow heap, a provenance DAG, and a specification context. That gives it register-level and heap-level taint tracking, plus provenance and cross-invocation context, across both the Ignition interpreter and the Maglev JIT, without ever forcing the engine to deoptimize back down to the interpreter.

Rules get compiled ahead of time into a binary form that dispatches in constant time. The genuinely novel part is a pair of actions called inject and extract, which bridge taint declaratively across higher-order function boundaries, think Array.map, and this is specifically the mechanism that handles crossing library boundaries. Specification context extends taint tracking across invocation boundaries too, covering things like file I/O. Because the rules are declarative, adding support for a brand-new vulnerability class just means writing new Mystra rules, no changes to the underlying engine required.

The implementation is called Shar. It's a shared core engine instantiated across three different runtimes: V8 inside both Node.js and Chromium, SpiderMonkey, and CPython. Tested against SecBench.js, a benchmark covering 493 in-scope CVEs across four CWE categories, the V8 instantiation hit 95.5% recall with zero false positives when tested against patched versions arxiv.org Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine.

The 95.5% figure deserves a second look, though arxiv.org Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine. Zero false positives on patched-version testing is a strong result, but it's a specific kind of test: it says the system doesn't cry wolf on code that's already been fixed arxiv.org Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine. It says nothing about paths the test suite never exercised, and path coverage remains the fundamental limit of any dynamic approach, Shar included. The system did find real cross-library vulnerabilities in live software, including FUXA v1.2.9, PsiTransfer, n8n v1.123, and Flowise v3.0.7. Across Node.js, Chromium/SpiderMonkey, and CPython, it confirmed detection of Code Injection (CWE-94), Path Traversal (CWE-22), OS Command Injection (CWE-78), Cross-Site Scripting (CWE-79), SQL Injection (CWE-89), and Prototype Pollution (CWE-1321) How to Choose the Right Penetration Testing Provider in 2026: A Decis…. The Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine work reports a runtime overhead of 1.85× over vanilla Node.js, presented as acceptable for security analysis contexts arxiv.org AI Penetration Testing: Methodology, Tools & Best Practices.

LLM-assisted taint specification extraction: SemTaint's multi-agent approach to the npm ecosystem

Diagram: How SemTaint's Three Agents Close the Library Boundary Gap. Visualizes: Visualize the sequential pipeline of SemTaint's three cooperating LLM agents, showing how each narrows the problem before handing off to the next.

A separate line of work, SemTaint, tackles the problem from the static side but brings in language models to do the parts static analysis can't manage on its own.

SemTaint's answer is three cooperating LLM agents working in sequence. A Source/Sink Agent identifies candidate sources and sinks for a given vulnerability category, guided by the relevant MITRE CWE descriptions. A CallGraph Agent iteratively resolves call edges that CodeQL can't figure out statically on its own, producing both first-party edges inside a package and conservative candidate flow summaries for the third-party dependencies it touches. A Flow Summary Agent then refines those candidate summaries along actual vulnerability paths, deciding whether each API sanitizes the data or just passes taint through unchanged.

Querying an LLM for every single unresolved call edge in a codebase would be absurdly expensive, in time, compute, and dollars. That makes the whole approach economically workable at ecosystem scale rather than just a research demo.

The results back it up. SemTaint, run alongside CodeQL, detected 106 out of 162 vulnerabilities that CodeQL alone couldn't find, and it turned up 4 previously unknown vulnerabilities across 4 popular npm packages arxiv.org Multi-Agent Taint Specification Extraction for Vulnerability Detection. What this really shows is that LLMs aren't replacing static analysis here, they're resolving the specific edges, unresolved call targets, ambiguous type flows, that static analysis structurally can't handle in a dynamic language, then handing the results back to the static engine to finish the job.

This approach's accuracy hinges on an LLM's probabilistic judgment about whether a given function sanitizes taint or just passes it along. That's a real difference from a flow summary someone can mathematically verify, and it's the tradeoff anyone adopting this method is signing up for. SemTaint's multi-agent approach to the npm ecosystem employs Taint-Informed Callee Resolution (TICR) as its cost-control mechanism. TICR restricts LLM queries to unresolved calls that lie on potential vulnerability paths for the CWE category of interest.

Mapping vulnerability classes that slip through library boundaries to real application risk

Put all of this together: the pattern is consistent, the vulnerability classes that most often slip through at library boundaries are exactly the ones that cause the most damage once they land in production. SQL injection (CWE-89) is a textbook case, tainted input routed through an ORM or a query-builder library, arriving eventually at raw SQL execution the original analyzer never traced that far. Command injection, path traversal, prototype pollution, and cross-site scripting, the categories confirmed detectable by systems like Shar, appear repeatedly in real breach reports because they hide in the handoff between application code and dependency code.

This is exactly the structural gap that makes source-level, whitebox penetration testing valuable rather than optional. Automated scanners that stop at the library boundary will miss vulnerabilities that only become visible once someone traces data through the actual dependency code and the cloud configuration surrounding it.

Each closes part of the gap. Understanding which part each one closes, and which part it leaves open, is what separates a security program that actually catches inter-library vulnerabilities from one that just assumes its scanner already did.

Sources

  1. YASA: Scalable Multi-Language Taint Analysis on the Unified AST at Ant Group
  2. Multi-Agent Taint Specification Extraction for Vulnerability Detection
  3. Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine
  4. Automated security analysis of software libraries
  5. Static Taint Flow Analysis Tool Checker | Sonar
  6. [2607.12308] Mystra: Declarative Dynamic Taint Analysis via Shadow Virtual Machine

More in AI-Native Security Testing