JavaScript & React Security Scanner: Source and Runtime (2026)
JavaScript and React apps leak secrets in bundles, trust client-side checks, and expose APIs without authz. A scanner that tests the running app, not only the source tree.
SCAN YOUR REACT APP NOW
Enter your deployed React/JS URL - we inspect the bundle for secrets and probe APIs for auth gaps.
Why JavaScript Apps Need Specialized Scanners
JavaScript and its ecosystem have unique security characteristics that generic SAST tools handle poorly. Prototype pollution, where attackers modify Object.prototype to inject properties across an application, barely exists outside JavaScript. Tools built for Java or C simply do not have rules for it.
React introduces its own surface area. The dangerouslySetInnerHTML prop is the most obvious vector for XSS, but subtler issues exist: unescaped URL parameters in href attributes can enable javascript: protocol attacks, and server-side rendering with unsanitized data creates hydration-based XSS that client-only scanners miss entirely.
The npm ecosystem is the largest package registry in the world, with over 2.5 million packages. That scale makes supply chain attacks routine: typosquatting, dependency confusion, and maintainer account takeovers. The event-stream incident, ua-parser-js compromise, and colors.js sabotage each showed a single compromised dependency reaching millions of downstream applications.
Node.js on the server adds path traversal via fs.readFile(userInput), command injection through child_process.exec(), and server-side request forgery when HTTP clients accept user-controlled URLs. A scanner that covers the browser, server, and build tooling catches issues that language-agnostic tools miss.
AI coding tools reproduce insecure tutorial patterns: completions in Cursor, Copilot, and Claude Code often wire dangerouslySetInnerHTML, open CORS, or concatenate SQL. A JS-aware pipeline catches these before they ship.
npm audit and GitHub Dependabot
Every Node.js project already has a scanner built in. Running npm audit checks your dependency tree against the GitHub Advisory Database and reports known vulnerabilities with severity ratings. It is free, needs no setup, and runs in under a second on most projects.
What npm audit catches: Known CVEs in direct and transitive dependencies, including prototype pollution in lodash, ReDoS in validator packages, and arbitrary code execution in build tools. The npm audit fix command can automatically update to patched versions when semver-compatible fixes exist.
GitHub Dependabot goes a step further by automatically opening pull requests when new advisories are published. It also handles version updates on a schedule, keeping dependencies current before vulnerabilities are discovered. Enable it by adding a dependabot.yml file to your repository’s .github directory.
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
Limitations: Both tools only scan dependencies, not your application code. They cannot detect XSS in your React components, prototype pollution in your own utility functions, or insecure configurations in your Express middleware. They also lag behind zero-day disclosures, because a vulnerability must reach the advisory database before these tools flag it. Treat npm audit as a baseline.
For AI-generated apps, also watch for hallucinated package names that never appear in advisories because they are brand-new attacker-owned packages. Pair SCA with the Package Hallucination Scanner.
ESLint Security Plugins
ESLint is already running in most JavaScript projects for code style. Adding security plugins turns it into a lightweight SAST scanner that catches vulnerabilities during development, before code reaches CI.
eslint-plugin-security is the most established option, with rules for detecting eval() usage, non-literal require() calls, non-literal regular expressions (ReDoS risk), and object injection via bracket notation with user input. Install with npm install eslint-plugin-security --save-dev and add "plugin:security/recommended" to your ESLint config.
eslint-plugin-no-unsanitized (maintained by Mozilla) detects direct DOM manipulation methods like innerHTML, outerHTML, and document.write() that accept unsanitized input. Use it in codebases that mix React with vanilla DOM manipulation.
React-specific rules: The built-in react/no-danger rule flags dangerouslySetInnerHTML, while react/jsx-no-target-blank catches missing rel="noreferrer" on external links. Combine these with eslint-plugin-security.
// eslint.config.js (flat config sketch)
import security from "eslint-plugin-security";
import react from "eslint-plugin-react";
export default [
{
plugins: { security, react },
rules: {
...security.configs.recommended.rules,
"react/no-danger": "error",
"react/jsx-no-target-blank": "error",
},
},
];
The main advantage of ESLint security plugins is that findings appear as squiggly lines in VS Code, not as CI failures twenty minutes later. The downside is that ESLint operates on single files without cross-file dataflow analysis, so it cannot trace tainted data from an API endpoint through middleware into a database query. For that, you need Semgrep or Snyk.
Snyk for JavaScript Projects
Snyk has the deepest npm integration of any commercial security platform. Running snyk test in a Node.js project scans your lockfile, identifies vulnerable packages, and shows the dependency path that introduced each vulnerability. Unlike npm audit, Snyk adds reachability analysis: it checks whether your code calls the vulnerable function, which cuts false positives.
Snyk Code for JavaScript: Beyond dependencies, Snyk Code provides SAST analysis specifically tuned for JavaScript and TypeScript. It detects XSS in React components, SQL injection in Node.js database queries, path traversal in Express route handlers, and hardcoded secrets in configuration files. Results appear as inline PR comments with fix suggestions.
.snyk policies: Create a .snyk file in your project root to ignore specific findings, set custom severity thresholds, or apply patches to vulnerabilities that do not have official fixes yet. This limits false-positive noise.
PR checks: Enable Snyk’s GitHub integration to automatically scan every pull request. New vulnerabilities block the merge, while existing (already-tracked) issues pass through. This stops new debt without blocking work on known issues.
The free tier includes 200 tests per month across dependency and code scanning, unlimited projects for open-source repositories, and basic reporting. Paid plans start at $25/developer/month and add priority support, advanced reporting, and higher test limits.
npx snyk auth
npx snyk test --severity-threshold=high
npx snyk code test
Semgrep Rules for React and Node
Semgrep’s pattern-matching approach works well for JavaScript because its dynamic patterns are hard for dataflow-based analyzers. You can write a Semgrep rule in minutes without building an AST plugin.
React-specific rules: The Semgrep Registry includes rules for detecting XSS via dangerouslySetInnerHTML with unsanitized variables, insecure use of useEffect with external data that bypasses sanitization, URL injection through window.location manipulation, and server-side rendering pitfalls where user data gets embedded in the initial HTML payload without encoding.
Node.js rules: Semgrep ships rules for path traversal via unsanitized file system operations, command injection through child_process with user input, SSRF in HTTP client libraries (axios, node-fetch, got), SQL injection in raw query builders, and insecure JWT verification that accepts algorithm: "none".
Custom rules: Semgrep lets you write rules specific to your codebase. If your team uses a custom ORM, write a rule that flags raw SQL queries outside the ORM. If you have an internal sanitization library, write a rule that detects when developers bypass it. Custom rules are YAML files that live in your repository and run alongside the community ruleset.
# .semgrep/no-raw-sql.yml
rules:
- id: no-string-concat-sql
patterns:
- pattern-either:
- pattern: |
$DB.query(`...${...}...`)
- pattern: |
$DB.query("..." + $X)
message: Use parameterized queries instead of string construction
languages: [javascript, typescript]
severity: ERROR
Run Semgrep locally with semgrep --config=p/javascript --config=p/react --config=p/nodejs . to scan your entire project against all JavaScript-related community rules. Scanning typically completes in under 30 seconds for projects with 50,000 lines of code.
Setting Up a JavaScript Security Pipeline
Combine the tools in a single GitHub Actions workflow. Each tool covers a different attack surface: npm audit for known dependency CVEs, Semgrep for code-level vulnerabilities, and Snyk for reachability-aware dependency analysis with automatic fix PRs.
Pipeline strategy: Run npm audit with continue-on-error: true so it reports but does not block merges on medium-severity dependency findings (which are often transitive and outside your control). Semgrep runs next and blocks on any finding above the configured severity. Snyk runs last with --severity-threshold=high to catch only critical dependency issues that npm audit may have missed due to advisory database timing.
# .github/workflows/js-security.yml
name: js-security
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npm ci
- run: npm audit --audit-level=high
continue-on-error: true
- uses: returntocorp/semgrep-action@v1
with:
config: >-
p/javascript
p/react
p/nodejs
- run: npx snyk test --severity-threshold=high
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
Local development: Add ESLint security plugins for instant feedback in the editor. Run Semgrep as a pre-commit hook with semgrep --config=p/javascript --error so developers catch issues before pushing. This catches issues without slowing CI.
Setup takes under 15 minutes. The npm audit step needs no configuration. Semgrep needs a free account for the app token (or skip the token for anonymous scanning with rate limits). Snyk requires a free account and token. Once configured, every pull request gets scanned automatically with results appearing as PR comments and check status updates.
What source scanners still miss
A SAST + SCA pipeline cannot see:
- Secrets inlined only after
NEXT_PUBLIC_/VITE_build - Supabase RLS or Firebase rules that exist only in the cloud console
- BOLA that needs two real sessions
- Middleware matchers that fail only on the deployed host
- Public storage buckets and open CORS on production
That is why a runtime layer matters for JS apps that ship to Vercel, Netlify, Railway, or similar. Point a live scanner at the URL:
- Vibe Code Scanner - AI-shaped failures on the deployed app
- Token Leak Checker - keys in the browser bundle
- Security Headers Checker - CSP/HSTS posture
Example runtime checks you should automate
# No secret-shaped strings in the production JS
curl -sL https://app.example.com | true
# use Token Leak Checker UI or download main chunks and grep:
# sk_live_, sk-, AKIA, service_role
# Unauthenticated API should not return private data
curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/api/me
React-specific threat checklist
| Sink / pattern | Risk | Scanner layer |
|---|---|---|
dangerouslySetInnerHTML |
XSS | ESLint, Semgrep, manual |
href={userInput} |
javascript: URL | ESLint, review |
| SSR + unsanitized props | Hydration XSS | Semgrep SSR rules |
| Client-only auth redirects | A01 bypass | Runtime DAST |
| Open redirects in routers | Phishing | Review + DAST |
| LLM markdown rendered raw | XSS | Review + CSP |
For LLM HTML specifically, see LLM-rendered HTML/Markdown.
Node.js-specific threat checklist
| Pattern | Risk | Notes |
|---|---|---|
child_process.exec(user) |
Command injection | Prefer spawn + allowlist |
fs.readFile(userPath) |
Path traversal | Resolve under a root |
axios.get(userUrl) |
SSRF | Allowlist hosts |
Raw SQL / Mongo $where |
Injection | Parameterize / validate |
JWT algorithms: ['none'] |
Auth bypass | Pin algorithms |
| Missing auth middleware | A01 | Router-level defaults |
Prototype pollution remains relevant in deep merge utilities and query parsers - Semgrep and Snyk both carry rules; still review custom merge helpers.
AI-generated JavaScript: extra detectors
When the code was written by Cursor, Claude Code, Copilot, Lovable, or Bolt:
- Assume missing ownership checks on CRUD.
- Assume secrets may be in the client bundle.
- Assume CORS was opened to silence a browser error.
- Assume tests cover only the happy path.
- Assume new deps were not verified on npm.
Add CI gates for removed auth calls (see Agentic Code Review Guide) and always scan the deploy URL after agent sessions.
Choosing tools by team stage
| Stage | Minimum viable stack |
|---|---|
| Solo weekend project | npm audit + ESLint security + Token Leak Checker on deploy |
| Early startup | + Semgrep CI + Dependabot + VibeEval on preview |
| Growth | + Snyk reachability / paid SCA + secret scanning (gitleaks) + scheduled DAST |
| Enterprise | + commercial SAST policy, SBOM, SSO-gated scanners, formal pentest cadence |
Do not buy enterprise SAST before you have env hygiene and authz tests. AI-app incidents are mostly A01/A02/A05 (access control, secrets, misconfiguration), not static analysis misses.
Comparing approaches
| Approach | Strengths | Weaknesses |
|---|---|---|
| npm audit / Dependabot | Free, fast SCA | No app code, lag on zero-days |
| ESLint security | Instant DX | No cross-file taint |
| Semgrep | Custom rules, speed | Needs rule ownership |
| Snyk | Reachability, PR UX | Cost at scale |
| Runtime DAST (VibeEval) | Real exploit evidence | Needs a URL / auth fixtures |
| Manual pentest | Business logic depth | Expensive, point-in-time |
No single product covers the whole JS threat model, so stack ESLint/Semgrep (SAST), SCA, and DAST.
Related Resources
SAST Tools for AI Code
Snyk vs Semgrep vs Checkmarx for AI-generated code
Between SAST and Pentest
Where automated gates end and probing begins
Vibe Code Scanner
Live scanning for AI-built web apps
OWASP Top 10 for AI Code
Priority map for what scanners should catch first
Package Hallucination Scanner
AI-specific dependency risk
Client-side vs server-side responsibilities in React apps
React can validate forms for UX; it cannot enforce authorization. Scanners that only parse JSX miss the real API. A JavaScript/React security scan should:
- Parse the production bundle for secrets and dangerous sinks (
dangerouslySetInnerHTML,eval, openpostMessagehandlers). - Exercise the API routes the SPA calls.
- Check auth cookies/
Authorizationhandling on those routes. - Flag dependency CVEs in the lockfile separately (SCA).
Common React sinks in AI output
// XSS sink - model "renders markdown quickly"
<div dangerouslySetInnerHTML={{ __html: userMarkdown }} />
// Better - sanitize or use a safe markdown component with hardened options
Also watch href={userUrl} for javascript: URLs, and target="_blank" without rel="noopener noreferrer".
SPA auth storage
Any XSS can steal tokens kept in localStorage and take over the account. Prefer httpOnly cookies with careful CSRF strategy, or short-lived memory tokens plus refresh rotation. AI templates default to localStorage because demos are easier.
Build pipeline checks
npm audit/ OSV- Secret scan on repo + built
dist - Bundle size review (accidental source maps)
- Runtime scan of the hosted SPA URL
Prototype pollution in the JS scanner stack
Cover deep merge helpers and query parsers; see prototype pollution and DOM attacks. Add Semgrep rules for __proto__ and runtime probes on JSON merge endpoints. ESLint alone will not catch exploitability.
postMessage and third-party widgets
React apps embed chat, billing, and analytics iframes. Handlers without event.origin checks are common in AI completions. Runtime DAST with a headless browser detects these reliably; SAST misses dynamically registered listeners.
Mapping tools to OWASP for JS apps
| OWASP | Primary JS tools |
|---|---|
| A01 Access control | Runtime dual-user DAST, manual IDOR |
| A02 Crypto / secrets | gitleaks, Token Leak Checker, bundle grep |
| A03 Injection | Semgrep, ESLint no-unsanitized, Snyk Code |
| A05 Misconfig | Headers checker, CORS review, source maps |
| A06 Components | npm audit, Dependabot, Snyk SCA |
| A07 Auth | Runtime auth flow tests |
| A10 SSRF | Semgrep HTTP client rules + review |
Full priority map: OWASP Top 10 for AI code.
Next.js App Router specifics
Next.js on Vercel blurs client/server. Scanners must understand:
- Server Actions are POST endpoints - auth inside the action, not only the page.
cookies()/headers()force dynamic rendering; missing them can cache private HTML.- Route Handlers under
app/api/**are public unless you check session. - Middleware matchers that omit
/apileave APIs open while pages redirect.
SAST that only flags dangerouslySetInnerHTML will miss Server Action BOLA. Prefer runtime tests against preview URLs after each agent PR.
Express / Fastify baseline rules
For Node APIs generated by Cursor/Claude Code:
// Router-level defaults
app.use("/api", requireAuth);
app.use(helmet());
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
// Per-resource
app.get("/api/items/:id", async (req, res) => {
const item = await db.item.findById(req.params.id);
if (!item || item.ownerId !== req.user.id) return res.status(404).end();
res.json(item);
});
A Semgrep custom rule for findById without an owner comparison catches this class of AI CRUD bug.
Source maps in production
AI deploys often leave *.js.map public. That makes secret hunting and code theft easier. Fail CI if source maps upload to the CDN for production:
find dist -name '*.map' | grep . && exit 1 || true
Use Source Map Checker on the live host.
Scan Your JavaScript App in Seconds
VibeEval probes the live app for JavaScript, React, and Node.js projects: XSS sinks, prototype-pollution patterns, secret leaks in the bundle, and authorization gaps.
Scan the running react app
Source review misses what ships in the bundle and what APIs return without auth. Get both on one URL.
14-day free trial · No credit card · Cancel anytime