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.
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
- Every install: run
npm ciandnpm audit. - Every pull request: run a SAST check such as CodeQL or Semgrep and ESLint security rules.
- On dependency changes: compare npm audit with Snyk, Dependency-Check, or Retire.js when your risk or compliance process requires a second data source.
- Before release: run authenticated DAST against staging and manually exercise authorization boundaries.
- 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.


