Skip to: Results Table | eslint-plugin-security | SonarJS | Microsoft SDL | Interlace | Methodology
I built a comprehensive benchmark with 40 vulnerable code patterns across 14 security categories and 38 safe patterns that should NOT trigger warnings. Then I ran six ESLint security plugins against them.
| Plugin | Rules | TP (Detections) | FP (False Alarms) | Precision | Recall | F1 Score | ESLint 9 |
|---|---|---|---|---|---|---|---|
| Interlace Ecosystem | 201 | 40/40 | 0 | 100.0% | 100.0% | 100.0% | ✅ Works |
| eslint-plugin-sonarjs | 269 | 14/40 | 5 | 73.7% | 35.0% | 47.5% | ✅ Works |
| eslint-plugin-security | 13 | 11/40 | 11 | 50.0% | 27.5% | 34.4% | ❌ Broken |
| eslint-plugin-security-node | 22 | 7/40 | 4 | 63.6% | 17.5% | 27.4% | ✅ Works |
| @microsoft/eslint-plugin-sdl | 17 | 4/40 | 1 | 80.0% | 10.0% | 17.8% | ✅ Works |
| eslint-plugin-no-unsanitized | 2 | 2/40 | 1 | 66.7% | 5.0% | 9.3% | ⚠️ Limited |
Note:
eslint-plugin-securitycrashes on ESLint 9. Its results are from ESLint 8.57.0. All other plugins were tested on ESLint 9.39.2.
Key Findings:
eslint-plugin-securityhas a 1:1 true positive to false positive ratio — for every real issue it catches, it incorrectly flags a safe patterneslint-plugin-sonarjshas 269 rules but only detects 35% of vulnerabilities — most rules target code quality, not securityeslint-plugin-security-node(the "successor" to eslint-plugin-security) still misses 82.5% of vulnerabilities- The Interlace ecosystem achieved a perfect score: 40/40 detections with zero false positives
Security linters exist to catch vulnerabilities before they reach production. But two failure modes undermine this mission:
False Negatives (missed vulnerabilities) create a dangerous illusion of security. Your CI pipeline passes, your code looks "clean," but invisible vulnerabilities ship to production.
False Positives (incorrectly flagged safe code) create alert fatigue. Developers start ignoring warnings, disabling rules, or worse—bypassing security checks entirely.
The ideal security linter has high recall (catches most vulnerabilities) and high precision (doesn't cry wolf).
| Category | Test Cases | CWEs |
|---|---|---|
| SQL Injection | 4 | CWE-89 |
| Command Injection | 4 | CWE-78 |
| Path Traversal | 4 | CWE-22 |
| Hardcoded Credentials | 4 | CWE-798 |
| JWT Vulnerabilities | 3 | CWE-757, CWE-347 |
| XSS / Code Execution | 4 | CWE-79, CWE-94 |
| Prototype Pollution | 3 | CWE-1321 |
| Insecure Randomness | 2 | CWE-330 |
| Weak Cryptography | 3 | CWE-328, CWE-327 |
| Timing Attacks | 2 | CWE-208 |
| NoSQL Injection | 2 | CWE-943 |
| SSRF | 2 | CWE-918 |
| Open Redirect | 1 | CWE-601 |
| ReDoS | 2 | CWE-1333 |
Secure implementations that should NOT trigger any warnings:
- Parameterized SQL queries (Prisma, TypeORM, pg)
- execFile with validated arguments
- path.resolve with startsWith validation
- Environment variables for credentials
- JWT with explicit algorithm restriction
- DOMPurify sanitization
- Allowlist validation before object access
- crypto.randomBytes for tokens
- crypto.timingSafeEqual for comparisons
- URL allowlists for SSRF prevention
Vulnerable Code Detections (out of 40 patterns):
Interlace Ecosystem: ████████████████████████████████████████ 40 (100%)
eslint-plugin-sonarjs: ██████████████░░░░░░░░░░░░░░░░░░░░░░░░░░ 14 (35%)
eslint-plugin-security: ███████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 11 (27.5%)
eslint-plugin-security-node: ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 7 (17.5%)
@microsoft/eslint-plugin-sdl:████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 4 (10%)
eslint-plugin-no-unsanitized:██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2 (5%)
| Rank | Plugin | Version | Rules | TP | FP | FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|---|---|---|
| 🥇 | Interlace Ecosystem | 3.0.2 | 201 | 40 | 0 | 0 | 100.0% | 100.0% | 100.0% |
| 🥈 | eslint-plugin-sonarjs | 3.0.6 | 269 | 14 | 5 | 26 | 73.7% | 35.0% | 47.5% |
| 🥉 | eslint-plugin-security† | 2.1.1 | 13 | 11 | 11 | 29 | 50.0% | 27.5% | 34.4% |
| 4 | eslint-plugin-security-node | 1.1.4 | 22 | 7 | 4 | 33 | 63.6% | 17.5% | 27.4% |
| 5 | @microsoft/eslint-plugin-sdl | 1.1.0 | 17 | 4 | 1 | 36 | 80.0% | 10.0% | 17.8% |
| 6 | eslint-plugin-no-unsanitized | 4.1.4 | 2 | 2 | 1 | 38 | 66.7% | 5.0% | 9.3% |
† Tested on ESLint 8.57.0 — crashes on ESLint 9 with
TypeError: context.getScope is not a function
Weekly Downloads: 1.5M+ | Rules: 13 | Last Updated: 2024 | ESLint 9: ❌ Broken
| Rule | Count | Lines |
|---|---|---|
detect-non-literal-fs-filename | 4 | 106, 115, 124, 134 |
detect-child-process | 2 | 64, 73 |
detect-object-injection | 2 | 264, 276 |
detect-eval-with-expression | 1 | 243 |
detect-unsafe-regex | 1 | 432 |
detect-non-literal-regexp | 1 | 441 |
| Category | Detected | Missed |
|---|---|---|
| SQL Injection | 0/4 | ❌ All |
| Hardcoded Credentials | 0/4 | ❌ All |
| JWT Vulnerabilities | 0/3 | ❌ All |
| Weak Cryptography | 0/3 | ❌ All |
| NoSQL Injection | 0/2 | ❌ All |
| SSRF | 0/2 | ❌ All |
| Open Redirect | 0/1 | ❌ All |
| Timing Attacks | 0/2 | ❌ All |
| Command Injection | 2/4 | ❌ Template literals |
| Path Traversal | 4/4 | ✅ Good |
| XSS / eval | 1/4 | ❌ innerHTML, document.write |
| Prototype Pollution | 2/3 | ⚠️ Partial |
| ReDoS | 2/2 | ✅ Good |
The plugin has ZERO coverage for: SQL injection, hardcoded credentials, JWT attacks, weak crypto, NoSQL injection, SSRF, open redirects, and timing attacks.
For every vulnerability eslint-plugin-security catches, it also incorrectly flags a safe pattern:
FP #1-8: detect-object-injection (8 false positives)
// ✅ SAFE: Key validated against allowlist
const VALID_KEYS = ["name", "email", "age"];
if (VALID_KEYS.includes(key)) {
return obj[key]; // ⚠️ Flagged as "Generic Object Injection Sink"
}
The rule flags any bracket notation with a variable, regardless of validation. It cannot recognize allowlist checks, hasOwnProperty guards, or Object.hasOwn() checks.
FP #9-11: detect-non-literal-fs-filename (3 false positives)
// ✅ SAFE: Path validated with startsWith
const safePath = path.resolve(baseDir, path.basename(filename));
if (!safePath.startsWith(baseDir + path.sep)) {
throw new Error("Path traversal detected");
}
fs.readFileSync(safePath); // ⚠️ Flagged anyway
The rule cannot recognize path validation patterns.
TypeError: context.getScope is not a function
Rule: "security/detect-child-process"
This is a breaking API change in ESLint 9. The plugin hasn't been updated, making it unusable with modern ESLint flat config.
Weekly Downloads: 3M+ | Rules: 269 | Last Updated: 2025 (active) | ESLint 9: ✅ Works
Despite having the most rules of any plugin tested, SonarJS missed 65% of vulnerabilities. The majority of its 269 rules target code quality (complexity, duplication, cognitive load), not security.
| Category | SonarJS | What It Missed |
|---|---|---|
| SQL Injection | 2/4 | Template literal patterns |
| Command Injection | 2/4 | execSync, spawn with shell |
| XSS | 2/4 | document.write, new Function |
| Hardcoded Credentials | 2/4 | AWS keys, JWT secrets |
| Prototype Pollution | 2/3 | Nested merge patterns |
| Weak Crypto | 2/3 | MD4, custom hash selection |
| ReDoS | 1/2 | Complex catastrophic patterns |
| Path Traversal | 0/4 | ❌ All |
| JWT | 0/3 | ❌ All |
| Timing Attacks | 0/2 | ❌ All |
| NoSQL Injection | 0/2 | ❌ All |
| SSRF | 0/2 | ❌ All |
| Open Redirect | 0/1 | ❌ All |
| Insecure Random | 1/2 | Token generation patterns |
SonarJS had a 73.7% precision rate — better than eslint-plugin-security, but still means roughly 1 in 4 security warnings is noise.
📖 Deep dive: SonarJS vs Interlace: 269 Rules, 65% Missed
Weekly Downloads: ~30K | Rules: 22 | Last Updated: 2023 | ESLint 9: ✅ Works
Created as a modern alternative to eslint-plugin-security, this plugin adds SQL injection and NoSQL injection detection rules that the original lacks. However, it still misses the majority of our test suite.
| Category | security-node | What It Caught |
|---|---|---|
| SQL Injection | 2/4 | Basic concatenation patterns |
| Command Injection | 2/4 | exec with string interpolation |
| XSS / eval | 1/4 | eval with expression |
| NoSQL Injection | 1/2 | Direct $where usage |
| Timing Attacks | 1/2 | Basic === comparison on secrets |
| Path Traversal | 0/4 | ❌ All |
| Hardcoded Credentials | 0/4 | ❌ All |
| JWT | 0/3 | ❌ All |
| Weak Crypto | 0/3 | ❌ All |
| SSRF | 0/2 | ❌ All |
A 63.6% precision rate — better than eslint-plugin-security's 50%, but still noisy.
Weekly Downloads: ~100K | Rules: 17 | Last Updated: 2024 (active) | ESLint 9: ✅ Works
Microsoft's Security Development Lifecycle plugin has the highest precision of any non-Interlace plugin (80%), but its scope is extremely narrow — focused almost entirely on browser-side injection patterns.
| Category | Microsoft SDL | What It Caught |
|---|---|---|
| XSS | 2/4 | innerHTML, document.write |
| Code Execution | 2/4 | eval, setTimeout with exprs |
| Everything else | 0/32 | ❌ All |
High precision, but extremely limited coverage. Its 17 rules focus narrowly on XSS patterns — it has zero rules for SQL injection, command injection, path traversal, JWT attacks, or any server-side vulnerability.
📖 Deep dive: Microsoft SDL vs Interlace: Enterprise Security Benchmark
Weekly Downloads: ~500K | Rules: 2 | Focus: XSS via DOM manipulation | ESLint 9: ⚠️ Limited
| Rule | Count | What It Caught |
|---|---|---|
no-unsanitized/property | 1 | innerHTML = userContent |
no-unsanitized/method | 1 | insertAdjacentHTML |
// ✅ SAFE: Content sanitized with DOMPurify
const sanitized = DOMPurify.sanitize(userContent);
element.innerHTML = sanitized; // ⚠️ Flagged anyway
Very narrow scope. Useful as a supplement for XSS, but covers only 2 of 14 categories.
Weekly Downloads: ~5K | Rules: 201 (11 specialized plugins) | ESLint 9: ✅ Works
The Interlace ecosystem achieved a perfect score — detecting every vulnerability with zero false positives across all 14 categories.
Sample detections:
🔒 CWE-798 OWASP:A04-Cryptographic CVSS:9.8 | Hard-coded API key detected | CRITICAL
Fix: Use environment variable: process.env.API_KEY
🔒 CWE-347 | Including "none" in algorithms array allows unsigned tokens | CRITICAL
Fix: Remove "none" from the algorithms array
🔒 CWE-95 OWASP:A05-Injection CVSS:9.8 | eval() can be refactored to safer alternative | HIGH
Fix: Remove eval entirely
The reason for 100% coverage is specialization. Instead of one monolithic plugin, the ecosystem uses purpose-built plugins for each domain: SQL (eslint-plugin-pg), JWT (eslint-plugin-jwt), crypto (eslint-plugin-crypto), browser XSS (eslint-plugin-browser-security), and more.
| Category | security† | security-node | sonarjs | MS SDL | no-unsanitized | Interlace |
|---|---|---|---|---|---|---|
| SQL Injection (4) | ❌ 0/4 | ⚠️ 2/4 | ⚠️ 2/4 | ❌ 0/4 | ❌ 0/4 | ✅ 4/4 |
| Command Injection (4) | ⚠️ 2/4 | ⚠️ 2/4 | ⚠️ 2/4 | ❌ 0/4 | ❌ 0/4 | ✅ 4/4 |
| Path Traversal (4) | ✅ 4/4 | ❌ 0/4 | ❌ 0/4 | ❌ 0/4 | ❌ 0/4 | ✅ 4/4 |
| Hardcoded Creds (4) | ❌ 0/4 | ❌ 0/4 | ⚠️ 2/4 | ❌ 0/4 | ❌ 0/4 | ✅ 4/4 |
| JWT (3) | ❌ 0/3 | ❌ 0/3 | ❌ 0/3 | ❌ 0/3 | ❌ 0/3 | ✅ 3/3 |
| XSS / eval (4) | ⚠️ 1/4 | ⚠️ 1/4 | ⚠️ 2/4 | ⚠️ 2/4 | ⚠️ 2/4 | ✅ 4/4 |
| Prototype Poll. (3) | ⚠️ 2/3 | ❌ 0/3 | ⚠️ 2/3 | ❌ 0/3 | ❌ 0/3 | ✅ 3/3 |
| Insecure Random (2) | ❌ 0/2 | ❌ 0/2 | ⚠️ 1/2 | ❌ 0/2 | ❌ 0/2 | ✅ 2/2 |
| Weak Crypto (3) | ❌ 0/3 | ❌ 0/3 | ⚠️ 2/3 | ❌ 0/3 | ❌ 0/3 | ✅ 3/3 |
| Timing Attacks (2) | ❌ 0/2 | ⚠️ 1/2 | ❌ 0/2 | ❌ 0/2 | ❌ 0/2 | ✅ 2/2 |
| NoSQL Injection (2) | ❌ 0/2 | ⚠️ 1/2 | ❌ 0/2 | ❌ 0/2 | ❌ 0/2 | ✅ 2/2 |
| SSRF (2) | ❌ 0/2 | ❌ 0/2 | ❌ 0/2 | ❌ 0/2 | ❌ 0/2 | ✅ 2/2 |
| Open Redirect (1) | ❌ 0/1 | ❌ 0/1 | ❌ 0/1 | ❌ 0/1 | ❌ 0/1 | ✅ 1/1 |
| ReDoS (2) | ✅ 2/2 | ❌ 0/2 | ⚠️ 1/2 | ❌ 0/2 | ❌ 0/2 | ✅ 2/2 |
| TOTAL | 11/40 | 7/40 | 14/40 | 4/40 | 2/40 | 40/40 |
† ESLint 8 results (crashes on ESLint 9)
If your codebase has 100 potentially vulnerable patterns:
| Plugin | Detected | Missed | In Production |
|---|---|---|---|
| eslint-plugin-security | 28 | 72 | 72 vulnerabilities |
| eslint-plugin-sonarjs | 35 | 65 | 65 vulnerabilities |
| eslint-plugin-security-node | 18 | 82 | 82 vulnerabilities |
| @microsoft/eslint-plugin-sdl | 10 | 90 | 90 vulnerabilities |
| Interlace Ecosystem | 100 | 0 | 0 vulnerabilities |
When false positive rates are too high:
- Developer sees
detect-object-injectiononconfig[key]where key is validated - Developer adds
// eslint-disable-next-line - Repeat 50 times across codebase
- Developer starts ignoring all security warnings
- Real vulnerability slips through disabled rule
- Breach
| Plugin | FP Rate | Developer Impact |
|---|---|---|
| eslint-plugin-security | 50.0% | Every other warning is wrong |
| eslint-plugin-sonarjs | 26.3% | 1 in 4 is noise |
| eslint-plugin-security-node | 36.4% | 1 in 3 is noise |
| @microsoft/eslint-plugin-sdl | 20.0% | Tolerable, but very limited |
| Interlace | 0.0% | Every warning is actionable |
| Component | Version |
|---|---|
| Node.js | v20.19.5 |
| ESLint | 9.39.2 (8.57.0†) |
| Platform | macOS (darwin/arm64) |
| Date | February 8, 2026 |
† ESLint 8.57.0 used for eslint-plugin-security only (crashes on ESLint 9)
All fixtures are:
- Realistic: Patterns from actual codebases, not contrived examples
- Reproducible: Published to GitHub with exact versions
- Comprehensive: All OWASP Top 10 with detectable patterns
git clone https://github.com/ofri-peretz/eslint-benchmark-suite
cd eslint-benchmark-suite
npm install
npm run benchmark:fn-fp
# ESLint 8 benchmark (eslint-plugin-security)
cd benchmarks/fn-fp-comparison/eslint8-compat
npm install
npm run benchmark
Every claim in this article can be independently verified.
-
eslint-plugin-security is not viable for production. A 72.5% false negative rate and a 1:1 TP:FP ratio. It crashes on ESLint 9. There's no reason to use it in 2026.
-
eslint-plugin-sonarjs is a quality tool, not a security tool. Despite 269 rules and 3M+ downloads, it misses 65% of security vulnerabilities. Its strength is code quality enforcement.
-
eslint-plugin-security-node is better but still inadequate. It covers more categories than its predecessor, but still misses 82.5% of vulnerabilities.
-
@microsoft/eslint-plugin-sdl is high precision, low coverage. Excellent for browser XSS, but provides zero server-side security coverage.
-
The Interlace ecosystem delivers comprehensive coverage. 100% detection rate with zero false positives. Domain-specific plugins ensure deep coverage across all vulnerability categories.
-
Security tooling requires active maintenance. The OWASP landscape evolves. Plugins from 2020 don't cover JWT algorithm confusion, AI prompt injection, or modern SSRF patterns.
npm uninstall eslint-plugin-security
npm install -D eslint-plugin-secure-coding eslint-plugin-node-security \
eslint-plugin-browser-security eslint-plugin-crypto \
eslint-plugin-pg eslint-plugin-jwt eslint-plugin-mongodb-security
// eslint.config.js
import secureCoding from "eslint-plugin-secure-coding";
import nodeSecurity from "eslint-plugin-node-security";
import browserSecurity from "eslint-plugin-browser-security";
import crypto from "eslint-plugin-crypto";
export default [
secureCoding.configs.recommended,
nodeSecurity.configs.recommended,
browserSecurity.configs.recommended,
crypto.configs.recommended,
];
Run ESLint. See what you've been missing.
201 security rules. 11 specialized plugins. 100% OWASP Top 10 coverage.
The Interlace ESLint Ecosystem provides comprehensive security static analysis for modern Node.js applications.
Related Articles:
- I Benchmarked 17 ESLint Security Plugins. Only One Found Every Vulnerability.
- eslint-plugin-security Is Unmaintained
- I Let Claude Write 80 Functions. 65-75% Had Security Vulnerabilities.
Build Securely.
I'm Ofri Peretz, a Security Engineering Leader and the architect of the Interlace Ecosystem. I build static analysis standards that automate security and performance for Node.js fleets at scale.