More Flows, More Bugs: Empowering SAST with LLMs and Customized DFA
Black Hat USA 2025 · Day 1 · Briefings
Overview
Researchers at Tencent Security Wind Ding Lab have built a pipeline that uses large language models to automatically identify missing source and sink functions in CodeQL, then extends the tool's data flow analysis engine to handle cross-thread execution, Java reflection, and pass-by-value semantics. The result: a 15% increase in detected data flows across more than 5,000 scanned projects, plus the discovery of previously undetectable CVEs in high-profile open-source software. ---

Key moments
- 2:00 Problem: CodeQL misses recent high-risk CVEs due to incomplete source/sink coverage
- 4:00 Insight: source and sink functions are framework API functions findable in open source
- 4:59 Three-agent LLM pipeline: discover, judge, and validation agents for automated source/sink ID
- 5:59 Finding: long-thinking LLM models outperform standard models at function-level filtering
- 8:01 DFA deep dive: forward/reverse flow and access path calculation in CodeQL explained
- 12:00 Fix: jump step extension bridges cross-thread data flow gaps in CodeQL DFA
- 12:59 Results: LLM+DFA approach finds bugs missed by default CodeQL rules at scale
More Flows, More Bugs: Empowering SAST with LLMs and Customized DFA
Speaker: Yuan, Security Researcher, Tencent Security Wind Ding Lab
Conference: Black Hat USA 2025 — August 6-7, 2025, Mandalay Bay, Las Vegas
YouTube: https://www.youtube.com/watch?v=Zp0x-cfClPY
Reading time: 8 minutes
Type: Briefing
TL;DR
Researchers at Tencent Security Wind Ding Lab have built a pipeline that uses large language models to automatically identify missing source and sink functions in CodeQL, then extends the tool's data flow analysis engine to handle cross-thread execution, Java reflection, and pass-by-value semantics. The result: a 15% increase in detected data flows across more than 5,000 scanned projects, plus the discovery of previously undetectable CVEs in high-profile open-source software.
Introduction
Static Application Security Testing (SAST) has become a cornerstone of modern DevSecOps, automating vulnerability detection before code ever reaches production. Tools like CodeQL and Fortify promise continuous security checks integrated directly into CI/CD pipelines. Yet practitioners who rely on these tools face an uncomfortable truth: even mature, widely adopted SAST tools miss real vulnerabilities because their built-in rules are incomplete.
Yuan from Tencent Security's Wind Ding Lab presented research at Black Hat USA 2025 that directly addresses this gap. The team analyzed recent high-severity bugs that CodeQL failed to detect and traced the root causes to two systematic problems: incomplete source and sink coverage in built-in propagation rules, and disruptions in data flow analysis caused by insufficient support for language features like multithreading and reflection. Their solution combines LLM-powered source/sink discovery with targeted extensions to CodeQL's data flow engine — a hybrid approach that significantly expands what automated static analysis can see.
Why CodeQL Misses Bugs: Two Root Causes
▶ Watch: Root cause analysis (02:00)
CodeQL's wall of fame lists 418 confirmed bugs — an impressive record, but one that obscures the false negative problem. When the team applied CodeQL to a set of recent high-risk vulnerabilities, they found a pattern of consistent misses. Diagnosing these failures revealed two primary causes.
The first is incomplete source and sink coverage. CodeQL's taint tracking rules require explicit definitions of where user-controlled data enters a program (sources) and where sensitive operations occur (sinks). These definitions are maintained either through manual expert review or community contributions. For third-party frameworks — particularly Go frameworks — the model is perpetually incomplete: new frameworks and new API patterns appear faster than community rule sets can be updated. A sink function like QueryRowX from the sqlx library, for example, was not included in CodeQL's default sink model, leaving entire categories of SQL injection bugs invisible.
The second root cause is data flow disruptions. CodeQL's inter-procedural data flow engine cannot track taint through certain Java language constructs without customization. The team identified three specific problem patterns: cross-thread taint propagation through Runnable instances, method invocations via Java reflection, and pass-by-value semantics where a tainted object is stored in a field through a constructor.
Automating Source and Sink Discovery with LLMs
▶ Watch: LLM-based source/sink identification (04:00)
To solve the first problem, the team built a three-agent pipeline that scans framework source code and automatically produces validated source and sink definitions.
The Discover Agent performs a coarse-grained pass at the file level, feeding framework source files and a prompt describing the characteristics of relevant functions — for example, for Server-Side Request Forgery (SSRF) bugs, sinks are functions that send outbound HTTP requests. The prompt instructs the model to respond with a confidence score for each candidate function, and results below a configurable threshold are discarded.
The Judge Agent applies expert rules derived from long-standing community practice. These rules include conditions such as: the function must be publicly accessible, return values must propagate tainted data, and return types must not be boolean. The agent uses the LLM to check each candidate function's name and body against these rules, filtering out false positives that the coarse-grained pass missed.
The Validation Agent confirms that the identified sources and sinks are actually used in real projects. It queries the framework's dependent repository list, downloads the highest-starred dependent projects, and runs CodeQL with the new rules applied. Any source or sink that produces no results is removed as unused.
Across 18 Go frameworks, this pipeline identified approximately 190 source and sink functions. Scanning over 5,000 projects with these expanded rules increased detected data flows by more than 15%.
Extending CodeQL's Data Flow Engine
▶ Watch: DFA internals and customization (08:00)
CodeQL's data flow analysis (DFA) works by computing forward flow from sources and reverse flow from sinks, combining them through a series of node and access-path computation stages. The engine exposes several extension interfaces, most notably additionalFlowStep for user-level customization and additionalValueStep for system-level customization. The team exploited these interfaces to patch three specific gaps.
Cross-Thread Analysis: When a Runnable is constructed and later executed via Thread.start(), standard CodeQL cannot follow taint through the boundary between the thread constructor call and the run() method. The team implemented a jump step using additionalValueStep to bridge this gap. Three scenarios required separate handling: taint passed through the constructor, taint assigned to the Runnable before start() is called, and taint assigned after start() begins — each requiring a different target node for the jump.
Java Reflection: The Method.invoke() pattern breaks data flow because the target method is determined at runtime, and the argument-to-parameter propagation is structurally different from normal call semantics. The team implemented two methods to resolve the reflective target: tracking the Method object through data flow, and matching based on parameter count and types. A key engineering challenge arose because CodeQL's additionalValueStep cannot itself invoke data flow analysis without creating a non-monotonic recursion. The solution was to create a copy of the data flow implementation that the reflection analysis depends on, then patch the original data flow to rely on that copy — effectively breaking the circular dependency.
▶ Watch: Reflection analysis implementation (16:01)
Pass-By-Value in Java: When a non-primitive parameter is stored in an object field through a constructor, CodeQL must update all copies of the reference simultaneously. The team located the relevant field, identified both post-update and non-post-update store operations, and used global data flow to establish the correct parameter-to-field mappings, adding jump steps between related nodes.
Real-World Results: Catching CVEs CodeQL Missed
▶ Watch: CVE case studies (22:04)
The team used a concrete CVE to demonstrate the impact of the expanded sink model. A SQL injection vulnerability in Apache Traffic Control's Traffic Ops component had evaded CodeQL detection despite the project having CodeQL CI integration enabled. The bug's data flow passed through user-supplied comments, through validation logic in info.go, and finally into a call to QueryRowX — a function from the sqlx framework that was absent from CodeQL's sink model. Once the team added sqlx as a scanned framework, the new sink was identified, and the vulnerability became detectable.
Beyond fixing false negatives for known CVEs, the cross-thread and reflection extensions enabled detection of historical vulnerabilities that were previously undetectable in principle. The team also discovered a number of new previously unknown bugs through the enhanced rule set, though specific details were not disclosed at the time of the presentation.
Notable Quotes
"We found there were a lot of false negatives. We analyzed the root causes and found two main reasons: incomplete source and sink coverage in built-in propagation rules, and disruptions in data flow due to insufficient support for certain language features."
— Yuan ▶ 02:00
"Semantic analysis of code in SAST is particularly suitable for LLM-assisted analysis, and their combination is a research direction worth pursuing."
— Yuan ▶ 24:04
"CodeQL's data flow analysis is not perfect and can be studied, modified, and improved."
— Yuan ▶ 24:04
Key Takeaways
- LLMs can automate source/sink discovery at scale. The three-agent pipeline (Discover, Judge, Validate) identified ~190 previously missing source/sink functions across 18 Go frameworks without manual review.
- SAST false negatives often stem from missing rules, not engine limitations. Adding a single missing sink (
QueryRowX) was enough to detect a SQL injection vulnerability in an Apache project that already had CodeQL CI enabled. - CodeQL's DFA extension interfaces are powerful but require care. The
additionalValueStepinterface enables custom propagation but prohibits recursive data flow calls; the copy-and-patch pattern circumvents this limitation. - Three Java language features routinely break SAST data flow: cross-thread propagation through
Runnable, reflection viaMethod.invoke(), and pass-by-value semantics for field assignments. All three have tractable solutions. - Scanning framework dependencies for sources/sinks is more effective than manual rule maintenance. The validation agent step ensures only rules that appear in real projects are kept, keeping the rule set high-signal.
Slides were not listed as available for this talk.
Reviews
Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT
Tencent's Wind Ding Lab does real engineering work here — LLM-assisted source/sink discovery plus targeted CodeQL DFA extensions that actually move the needle. Not glamorous, not novel in concept, but executed with genuine depth and quantified results. The reflection analysis workaround alone is worth the price of admission.
Heather Calloway (CISO) — WEAK
Extending CodeQL's data flow analysis to catch bugs it currently misses is legitimate and the results are real — a 15% increase in detected flows and a SQL injection CVE in Apache Traffic Control that CodeQL CI integration missed. But this is a research-to-practitioner gap talk without the bridge.