Safeguard
Security

JavaScript Static Analysis: Catching Bugs Before They Ship

JavaScript static analysis reads your code without running it to find bugs, security flaws, and risky patterns early. Here is what it can and cannot catch, and how to set it up well.

Priya Mehta
DevSecOps Engineer
6 min read

JavaScript static analysis examines your source code without executing it to surface bugs, security vulnerabilities, and risky patterns before they reach production. Done well, JavaScript static analysis turns a whole class of defects into fast, automated feedback in the editor and in CI, which is far cheaper than catching them during an incident.

The appeal is straightforward. Running code to find bugs requires you to hit the exact path with the exact input that triggers them. Reading code, in contrast, can reason about paths you have not tested yet. That is why static analysis complements testing rather than replacing it: tests prove that specific behavior works, while static analysis flags patterns that are wrong regardless of whether a test happened to exercise them.

What static analysis actually looks at

A static analyzer parses your code into an abstract syntax tree and then reasons over that structure. The most valuable tools go further with data flow analysis, tracing how a value moves through your program. This is what lets a tool follow a value from a "source" (say, an HTTP request parameter) to a "sink" (say, a database query or innerHTML assignment) and warn when tainted input reaches somewhere dangerous.

For JavaScript specifically, the language's dynamism makes this harder than in statically typed languages. Values change type, objects gain properties at runtime, and eval and dynamic imports blur what code even runs. Modern analyzers handle the common cases well but will always have blind spots around highly dynamic code.

The tiers of JavaScript static analysis

It helps to think of three overlapping layers, each catching different problems.

Linting is the entry point. ESLint is the de facto standard. Out of the box it catches undeclared variables, unreachable code, and style inconsistencies, but its real power is the plugin ecosystem. Security-focused plugins flag patterns like eval usage, unsafe regular expressions prone to catastrophic backtracking, and dangerous DOM sinks.

// eslint.config.js
import security from "eslint-plugin-security";

export default [
  {
    plugins: { security },
    rules: {
      "security/detect-eval-with-expression": "error",
      "security/detect-non-literal-fs-filename": "warn",
      "no-eval": "error"
    }
  }
];

Type checking is a second layer that catches a large share of real bugs. Even in a plain JavaScript codebase, running TypeScript's compiler in checked mode against JSDoc annotations catches null dereferences, wrong argument counts, and typos in property names that would otherwise surface as runtime errors.

// jsconfig.json
{
  "compilerOptions": {
    "checkJs": true,
    "strict": true,
    "noImplicitAny": true
  }
}

Dedicated SAST is the third layer, focused specifically on security. Tools in this category use taint tracking and curated rule sets to find injection flaws, hardcoded secrets, insecure randomness, and authentication mistakes. They are tuned for security signal rather than general code quality, and they typically map findings to CWE identifiers so you can prioritize.

Where static analysis stops

Static analysis is powerful but not omniscient, and pretending otherwise leads to misplaced trust. It struggles with anything determined at runtime: values that come from a network call, behavior that depends on environment configuration, or logic routed through reflection and dynamic dispatch. It also cannot see into your dependencies' runtime behavior; a vulnerable package deep in your tree is a job for software composition analysis, not source-level SAST. And it says nothing about how your running application behaves under real requests, which is the domain of dynamic testing.

The honest framing is that these techniques stack. Static analysis on your first-party code, composition analysis on your dependencies, and dynamic testing on the running app together cover ground that no single tool covers alone.

The false positive problem

The fastest way to kill a static analysis rollout is to drown developers in noise. A tool that flags two hundred issues, most of them irrelevant, trains people to ignore all of it, including the five findings that matter. Manage this deliberately:

  • Start with a small, high-confidence rule set and expand as trust builds.
  • Triage findings and suppress false positives inline with a documented reason, not a blanket disable.
  • Fail the build only on high-severity, high-confidence rules at first; treat the rest as warnings.
  • Track your true-positive rate. If a rule is mostly noise, tune or remove it.

Wiring it into the workflow

Static analysis pays off most when feedback is instant. Run the linter and type checker in developers' editors so problems surface as they type. Run the full suite in a pre-commit hook or in CI so nothing merges without passing. A minimal CI step looks like this:

npx eslint . --max-warnings=0
npx tsc --noEmit

Add your SAST scan as a separate CI job so its longer runtime does not block fast lint feedback. Gate merges on the results, and route findings to the same place your team already looks so they do not get lost. For a structured walkthrough of building this pipeline, our academy has a track on it.

FAQ

Is ESLint enough for JavaScript static analysis?

ESLint with security plugins is a strong foundation and catches many issues, but it is primarily a linter. For deeper security coverage, add type checking and a dedicated SAST tool that does taint tracking. The three layers catch different classes of problems.

What is the difference between static and dynamic analysis?

Static analysis reads your code without running it, so it can reason about untested paths but cannot see runtime behavior. Dynamic analysis exercises the running application with real inputs, catching issues that only appear at execution time. They are complementary, not competing.

Can static analysis find security vulnerabilities?

Yes, particularly injection flaws, hardcoded secrets, unsafe DOM manipulation, and insecure API usage. Taint-tracking tools follow untrusted input to dangerous sinks. It cannot find vulnerabilities in third-party dependencies, though, which is a job for composition analysis.

How do I deal with false positives?

Start with a small, high-confidence rule set and expand gradually. Suppress genuine false positives inline with a documented reason rather than disabling rules wholesale, and only fail builds on high-severity findings at first. Track which rules produce noise and tune them.

Never miss an update

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