JavaScript Static Analysis: Tools, Techniques, and Workflow
Table of Contents

Most advice about JavaScript static analysis starts with the wrong prescription: add more scanners. That sounds sensible until developers receive overlapping findings from ESLint, SonarQube, Semgrep, CodeQL, dependency tools, and framework-specific checks, then stop trusting the entire queue. Security improves when analysis produces findings people can validate and fix, not when a pipeline produces the largest possible alert count.
The harder problem is the gap between soundness and usefulness. A sound analyzer aims to account for every possible execution path, while a useful analyzer must finish on real applications, understand enough of the surrounding ecosystem, and avoid turning uncertain guesses into mandatory manual review. JavaScript's dynamic features make that balance unusually difficult. Static analysis remains valuable, but only when teams define what it can reliably answer, where it needs runtime evidence, and how findings will enter an existing engineering workflow.
Why More Scanners Do Not Mean Better Security
Adding scanners doesn't automatically add coverage. It often adds duplicate detection, inconsistent severity ratings, and competing remediation advice. One tool may flag a user-controlled value flowing toward a browser sink, while another reports the sink without enough context to determine whether the value is attacker-controlled. Developers then spend time reconciling tools instead of fixing the underlying issue.
The problem is especially visible in JavaScript because analysis depends heavily on assumptions about runtime behavior. A property may be selected dynamically, a function may arrive through a callback, or a module may be loaded only after a runtime condition. If one scanner models those paths conservatively and another ignores them, their findings won't line up. More output doesn't resolve that uncertainty.
ESLint illustrates the difference between a focused tool and a general security scanner. Created in June 2013 by Nicholas C. Zakas, ESLint was designed as an open-source linter with configurable and extensible rules, addressing limitations in earlier tools such as JSLint and JSHint. Its history, including movement to the OpenJS Foundation in March 2019, reflects the maturation of JavaScript analysis, but its core strength remains configurable source-level feedback rather than complete security proof (ESLint's project history and design).
Practical rule: Choose the smallest set of tools that answers distinct questions, then tune those tools until developers trust the results.
A large empirical study examined 168,214 open-source projects and found that static analysis use was widespread but not universal, while projects commonly lacked strict enforcement policies (the large-scale evaluation of static analysis in open-source software). That matters operationally. Adoption proves that analysis belongs in engineering workflows, not that every rule deserves a blocking gate.
Before adding another scanner, ask three questions:
- What distinct signal does it provide? If it repeats an existing pattern, it may increase noise without improving decisions.
- Who owns the finding? A security alert without a developer-facing remediation path will age in the backlog.
- What happens after failure? A check that blocks routine work for uncertain findings teaches teams to bypass or disable it.
Threat modeling should decide which rules matter before scanning begins. A design review can identify trust boundaries, authorization assumptions, and dangerous data flows that pattern matching won't infer reliably. Teams that want to make that shift can use threat modeling instead of treating scanning as the starting point.
How JavaScript Static Analysis Actually Works
Most JavaScript static analysis begins by turning source text into an abstract syntax tree, or AST. The tree represents declarations, calls, operators, imports, property access, control flow, and other language constructs without executing the application. A rule can then inspect the tree for patterns such as eval, suspicious DOM assignments, hardcoded credentials, or unsafe use of object properties.
Modern syntax makes parsing a real engineering concern. A tool must understand modules, JSX, optional chaining, decorators, TypeScript syntax, generated framework code, and the project's parser configuration. Babel-based parsers and the TypeScript compiler can expose different representations and type information, so a rule that works against one tree may miss or misclassify an equivalent construct in another.

From syntax to data flow
AST matching is the shallowest layer. Deeper tools build control-flow graphs and track how values move through assignments, function calls, returns, object properties, and module boundaries. A taint rule might mark request input as untrusted, follow it through helper functions, and report a path to an HTML rendering sink or a database query.
That path becomes uncertain quickly. The analyzer may need to infer which function a variable references, whether a callback runs, which object property is selected, or which package implementation sits behind an import. In untyped JavaScript, type inference can narrow possibilities but rarely eliminates them. TypeScript improves available information, yet runtime values can still violate declared expectations.
What tools detect well
Static analysis is effective when the source pattern is explicit and the surrounding assumptions are stable. Common examples include:
- Direct dangerous calls, such as
evalor obvious child-process execution. - Known sinks, including DOM APIs or query builders used with visibly untrusted values.
- Hardcoded secrets, when credentials appear in recognizable formats and aren't generated or transformed.
- Prototype pollution patterns, especially assignments to attacker-controlled keys or unsafe object merges.
- Insecure configuration, such as permissive options represented directly in source.
It

