ScreenshotNeo

BlogEngineering

8 Tools for Analyzing Node.js Application Security Vulnerabilities

Compare eight Node.js security tools, learn what each detects, and build a practical dependency, SAST, and dynamic-testing workflow.

By the ScreenshotNeo team1 October 20268 min read

Short answer: use several tools because they inspect different surfaces. Start with npm audit for known vulnerabilities in package manifests and lockfiles. Add a source-code analyzer such as CodeQL or Semgrep for first-party JavaScript and TypeScript. Use dynamic testing against a running application when you need runtime evidence. Snyk, OWASP Dependency-Check, and Retire.js add other dependency and workflow options. No clean report proves that a Node.js application is secure.

What these tools actually analyze

Security analysis has three distinct targets:

Target Typical findings Best-fit tools
Dependencies Known CVEs and advisories in direct or transitive packages npm audit, Snyk, OWASP Dependency-Check, Retire.js
First-party source Injection paths, unsafe process execution, path traversal, dangerous APIs, data-flow mistakes CodeQL, Semgrep, dedicated SAST rules
Running application Exposed endpoints, authentication and authorization behavior, headers, runtime responses Dynamic scanners and manual testing

Dependency scanners match package metadata to advisory databases. They do not understand every business rule in your code. OWASP describes SAST as source analysis that can use code-flow tracking to find complex vulnerabilities that ordinary lint rules miss; its Node.js guidance lists risks including SQL injection, cross-site scripting, command injection, local or remote file inclusion, denial of service, directory traversal, LDAP injection, unsafe eval(), shell invocation through child_process.exec, and ReDoS.

1. npm audit

npm audit is the practical baseline for npm projects. npm says the command submits a description of configured dependencies to the default registry and asks for a report of known vulnerabilities. OWASP classifies its Node.js and JavaScript support as full.

Run it locally

# Install exactly what the lockfile specifies
npm ci

# Human-readable report
npm audit

# Machine-readable output for CI
npm audit --json

# Ask npm to apply non-breaking fixes
npm audit fix

The report includes the package, severity, advisory description, dependency path, and suggested commands. It checks dependencies, devDependencies, bundled dependencies, and optional dependencies, but not peer dependencies. A suggested update can cross a semver-major boundary, so review the proposed change and run your test suite before merging.

CI example

name: dependency-audit
on: [push, pull_request]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm audit --audit-level=high

Set the severity threshold to match your release policy. A zero-exit result means no advisory matched the scanned dependency data at that moment; it does not mean your application logic was reviewed.

2. Snyk

Snyk describes JavaScript and npm-library vulnerability scanning through its IDE, CLI, and Git-repository workflows, with continuous monitoring and suggested fixes. Treat those as vendor-described capabilities rather than an independent accuracy benchmark.

# Install the CLI, then authenticate according to Snyk's documentation
snyk test --file=package.json
snyk monitor --file=package.json

# Scan source code where your plan and configuration support SAST
snyk code test

Use Snyk when developers need findings in pull requests or an ongoing inventory after a commit. Confirm which manifest and lockfile formats your project uses, and review whether a suggested upgrade changes runtime behavior.

3. OWASP Dependency-Check

OWASP Dependency-Check identifies publicly known vulnerabilities by matching dependencies against vulnerability data. OWASP’s dependency-management guidance labels Node.js support experimental, while npm audit is listed as full support. That distinction matters for JavaScript repositories.

# Example CLI shape; install and configure Dependency-Check using OWASP's current instructions
dependency-check.sh --project my-node-app --scan . --format HTML --out reports/dependency-check

Use it when your organization already standardizes on Dependency-Check across multiple ecosystems. For a Node-only project, compare its results with npm audit and investigate differences before making it your sole gate.

4. Retire.js

OWASP’s Node.js Security Cheat Sheet names Retire.js for checking JavaScript libraries with known vulnerabilities. It is useful when you need a focused check for vulnerable JavaScript libraries, including code that may be present outside the normal npm dependency path.

# Install and run the current Retire.js CLI according to its project documentation
retire --path . --outputformat json --outputpath reports/retire.json

Keep the tool’s version and vulnerability data current. Treat a finding as a lead: verify the actual library version, whether the vulnerable code is shipped, and whether the affected functionality is reachable.

5. Semgrep

Semgrep performs pattern and semantic static analysis and supports JavaScript and TypeScript. Its open-source repository documents use in IDEs, pre-commit checks, and CI/CD. Semgrep also cautions that its Community Edition can miss true positives that cross function or file boundaries; use the platform and rules appropriate to your required coverage.

# Install the CLI in a virtual environment or with your package manager
semgrep scan --config p/javascript --error

# Run a project configuration checked into the repository
semgrep scan --config .semgrep.yml --json > semgrep.json

Use Semgrep for fast feedback on dangerous patterns such as shell execution, unsanitized URL construction, or insecure framework calls. Tune rules to your framework and review suppressions so that developers do not normalize ignored findings.

6. CodeQL

CodeQL treats code as data and provides JavaScript and TypeScript query suites. The JavaScript query help documents default, security-extended, and security-and-quality suites. CodeQL is appropriate for repository-scale data-flow analysis and custom queries.

# Typical GitHub Actions setup using the maintained CodeQL action
name: codeql
on:
  push:
  pull_request:
jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      actions: read
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: javascript
          queries: security-extended
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3

Choose CodeQL when you need findings attached to pull requests and the ability to write organization-specific queries. Database extraction and analysis consume more CI resources than a simple manifest check, so schedule full scans appropriately.

7. ESLint security rules

ESLint security plugins are rule-based checks that run alongside normal linting. They can flag risky constructs early, but they are not a replacement for SAST. OWASP explicitly says dedicated SAST tools can detect complex vulnerabilities that linters miss.

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

export default [
  {
    plugins: { security },
    rules: {
      'security/detect-eval-with-expression': 'error',
      'security/detect-child-process': 'warn',
      'security/detect-non-literal-fs-filename': 'warn'
    }
  }
];
npx eslint .

Use these rules as a fast developer feedback layer. Expect false positives around deliberately constructed paths or trusted internal commands; document a narrow suppression with its reasoning.

8. Dynamic testing with OWASP ZAP

Dynamic application security testing (DAST) exercises a running service. OWASP ZAP is a commonly used open-source proxy and scanner. It can reveal runtime behavior that dependency and source scanners cannot, but it needs a safe test environment and representative authentication and routes.

# Baseline scan of a local test server
./zap-baseline.py -t http://localhost:3000 -r zap-report.html

# API-driven automation can be added after starting ZAP in daemon mode
zap.sh -daemon -port 8090 -config api.key=change-me

Never point an active scan at a system you do not own or have permission to test. Seed test accounts, exclude destructive endpoints, and separate staging data from production data.

How to combine the tools

  1. Every install: run npm ci and npm audit.
  2. Every pull request: run a SAST check such as CodeQL or Semgrep and ESLint security rules.
  3. On dependency changes: compare npm audit with Snyk, Dependency-Check, or Retire.js when your risk or compliance process requires a second data source.
  4. Before release: run authenticated DAST against staging and manually exercise authorization boundaries.
  5. After a finding: confirm affected version, dependency path, reachability, exploitability, and the smallest safe remediation.

Common Node.js findings and review questions

Finding Review
Command injection Is user input passed to exec, a shell, or a process argument? Prefer fixed commands and validated allowlists.
Path traversal Can a request select ../ or an absolute path? Resolve against an allowed directory and verify the final path.
ReDoS Can attacker-controlled text trigger pathological regular-expression backtracking? Bound input and simplify the expression.
Prototype pollution Does untrusted object data merge into application configuration? Use safe parsers and reject dangerous keys.
Dependency advisory Is the vulnerable package actually installed, bundled, reachable, and used in the affected mode?

Troubleshooting

“npm audit fix” wants a major upgrade

Read the dependency path and changelog, create a branch, apply the update deliberately, and run unit, integration, and end-to-end tests. Do not accept a breaking change solely because the command suggested it.

The scanner reports a package you do not recognize

Trace the path in the lockfile. It is often transitive. Upgrade the direct parent where possible; otherwise use the package manager’s documented override mechanism and verify the resulting tree.

Different tools disagree

They may use different advisory feeds, package resolution, severity mappings, or reachability assumptions. Compare package versions, timestamps, and paths before deciding which result is actionable.

SAST reports a dangerous call that is safe in context

Inspect the full data flow. If inputs are constrained by an allowlist or the call is unreachable, record that evidence and add the narrowest suppression supported by the tool.

DAST finds nothing

Confirm that the scanner reached authenticated routes, the test data exercised meaningful workflows, and the application produced source maps or responses that expose the relevant behavior. A shallow crawl is not evidence of safety.

Performance, reliability, and cost considerations

  • Manifest checks are fast enough for every commit; cache npm data and avoid reinstalling dependencies unnecessarily.
  • Repository-wide SAST is slower and may need separate pull-request and scheduled configurations.
  • DAST runtime depends on crawl depth, authentication, rate limits, and application size.
  • Advisory databases change. Record tool versions and scan timestamps so results are reproducible.
  • Prioritize reachable, high-severity findings, but do not silently discard lower-severity issues that combine into a larger exploit path.
  • Pricing and plan limits for commercial scanners change; consult each vendor before procurement.

What the evidence says about scanner limits

A 2023 study by Brito and colleagues curated 957 vulnerabilities from npm advisory reports. It reported “57.6% maximum combined detection by the three best-performing tools, with 0.11% precision — Brito et al., arXiv, 2023.” That result belongs to the study’s dataset and method; it is not a universal current score for every product. Use scanners with secure coding practices, threat modeling, tests, and human review.

Or skip the browser setup

When you need visual evidence of a public security report, documentation page, or staging result, ScreenshotNeo can capture it with one request. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There are 1,000 screenshots each month on the free plan with no card required; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Is npm audit enough?

No. It covers known dependency advisories, not all first-party code, configuration, authentication, or runtime behavior.

Should I run every scanner?

Choose at least one dependency scanner, one SAST layer, and DAST where a running environment is available. Add overlapping tools when their data sources or workflows solve a specific gap.

Can a low-precision tool still be useful?

Yes, if findings are triaged consistently and suppressions are evidence-based. A noisy tool becomes harmful when teams ignore all results.

How often should scans run?

Run fast checks on changes, deeper scans on a schedule, and an authenticated dynamic scan before significant releases.