Your ESLint Security Plugin is Missing 80% of Vulnerabilities (I Have Proof)
Source: Dev.to
Benchmark Methodology
The Test Files
vulnerable.js (218 lines) – Contains 12 categories of real vulnerabilities
// 1. Command Injection
exec(`ls -la ${userInput}`);
execSync('echo ' + userInput);
spawn('bash', ['-c', userInput]);
// 2. Path Traversal
fs.readFile(filename, 'utf8', callback);
fs.readFileSync(filename);
// 3. Object Injection
obj[key] = value;
data[key][value] = 'test';
// 4. SQL Injection
db.query('SELECT * FROM users WHERE id = ' + userId);
// 5. Code Execution
eval(code);
new Function(code);
// 6. Regex DoS
const evilRegex = /^(a+)+$/;
new RegExp(userInput);
// 7. Weak Cryptography
crypto.createHash('md5').update(password);
Math.random().toString(36);
// 8. Timing Attacks
if (inputToken === storedToken) {
return true;
}
// 9. XSS
document.getElementById('output').innerHTML = userContent;
// 10. Insecure Cookies
document.cookie = `${name}=${value}`;
// 11. Dynamic Require
require(moduleName);
// 12. Buffer Issues
const buf = new Buffer(size);
safe-patterns.js (167 lines) – Contains defensive patterns that should NOT trigger warnings
// Safe: Validated key access with allowlist
const VALID_KEYS = ['name', 'email', 'age'];
if (VALID_KEYS.includes(key)) {
return obj[key];
}
// Safe: hasOwnProperty check
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return obj[key];
}
// Safe: Path validation with startsWith
if (!safePath.startsWith(SAFE_DIR)) throw new Error('Invalid');
fs.readFileSync(safePath);
// Safe: Timing‑safe comparison
crypto.timingSafeEqual(bufA, bufB);
// Safe: DOMPurify sanitization
const clean = DOMPurify.sanitize(userContent);
element.innerHTML = clean;
Benchmark Configuration
- Iterations: 5 runs per test
- Metrics: Average time, min/max time, issues found, rules triggered
- Assumption: Run‑to‑run variance ≤ 15 %; reported differences (2.83×, 3.8×) exceed this margin
Test 1 – Fair Fight (Same 14 Rules)
First, I tested both plugins with only the 14 equivalent rules that exist in both packages. This ensures an apples‑to‑apples comparison.
Results
| Metric | secure-coding | security | Winner |
|---|---|---|---|
| Performance / Issue | 24.95 ms | 25.12 ms | 🟢 secure-coding |
| Total Time | 723.54 ms | 527.58 ms | 🔵 security |
| Issues Found | 29 | 21 | 🟢 secure-coding |
| Detection Rate | 138 % | 100 % | 🟢 secure-coding |
Rule‑by‑Rule Detection
| Rule Category | security | secure-coding | Diff |
|---|---|---|---|
| Timing Attacks | 1 | 5 | +4 🟢 |
| Child Process | 2 | 4 | +2 🟢 |
| Non‑literal Regexp | 1 | 3 | +2 🟢 |
| Eval / Code Execution | 1 | 2 | +1 🟢 |
| Insufficient Randomness | 0 | 1 | +1 🟢 |
| FS Path Traversal | 5 | 5 | = |
| Object Injection | 5 | 5 | = |
| Dynamic Require | 2 | 2 | = |
| Unsafe Regex | 2 | 2 | = |
| Buffer APIs | 2 | 0 | -2 🔵 |
| TOTAL | 21 | 29 | +8 |
Key Finding: With the same rule categories, secure-coding finds 38 % more issues while maintaining nearly identical efficiency per issue.
Test 2 – Recommended Presets
Next, I tested each plugin’s recommended configuration—the out‑of‑box experience.
Results
| Metric | secure-coding | security | Winner |
|---|---|---|---|
| Performance / Issue | 9.95 ms | 28.16 ms | 🟢 secure-coding |
| Total Time | 795.99 ms | 591.41 ms | 🔵 security |
| Issues Found | 80 | 21 | 🟢 secure-coding |
| Rules Triggered | 30 | 10 | 🟢 secure-coding |
| Total Rules | 89 | 14 | 🟢 secure-coding |
Detection Breakdown
secure-coding rules triggered on vulnerable.js:
• no-unvalidated-user-input: 8 issues
• detect-non-literal-fs-filename: 5 issues
• detect-object-injection: 5 issues
• no-timing-attack: 5 issues
• detect-child-process: 4 issues
• database-injection: 4 issues
• no-unsafe-deserialization: 4 issues
• no-sql-injection: 3 issues
• detect-non-literal-regexp: 3 issues
• no-hardcoded-credentials: 2 issues
• detect-eval-with-expression: 2 issues
• no-weak-crypto: 2 issues
... and 18 more categories
security rules triggered:
• detect-non-literal-fs-filename: 5 issues
• detect-object-injection: 5 issues
• detect-child-process: 2 issues
• detect-unsafe-regex: 2 issues
... and 6 more categories
Test 3 – False Positive Analysis
This is where precision matters. I ran both plugins against safe-patterns.js—a file with only safe, validated code.
Results
| Plugin | False Positives | Precision |
|---|---|---|
secure-coding | 0 | 100 % |
security | 4 | 84 % |
The 4 False Positives from eslint-plugin-security
FP #1 – Validated key access (line 38)
// Pattern: Allowlist validation before access
const VALID_KEYS = ['name', 'email', 'age'];
function getField(obj, key) {
if (VALID_KEYS.includes(key)) {
return obj[key]; // ⚠️ security flags "Generic Object Injection Sink"
}
}
FP #2 – hasOwnProperty check (line 45)
// Pattern: Property existence check before access
function safeGet(obj, key) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return obj[key]; // ⚠️ security flags "Generic Object Injection Sink"
}
}
FP #3 – Guard clause with throw (line 153)
// Pattern: Early exit guard clause
const ALLOWED_THEMES = ['light', 'dark', 'system'];
function setTheme(userTheme) {
if (!ALLOWED_THEMES.includes(userTheme)) {
throw new Error('Invalid theme');
}
config[userTheme] = true; // ⚠️ security flags "Unsafe Assignment"
}
FP #4 – Timing‑safe comparison (line 212)
// Pattern: Timing‑safe equality check
if (crypto.timingSafeEqual(bufA, bufB)) {
// safe branch
}
FP #4: Path Validation (line 107)
// Pattern: basename + startsWith validation
function safeReadFile(userFilename) {
const safeName = path.basename(userFilename);
const safePath = path.join(SAFE_DIR, safeName);
if (!safePath.startsWith(SAFE_DIR)) {
throw new Error('Invalid path');
}
return fs.readFileSync(safePath); // ⚠️ security flags "non literal argument"
}
The path is fully validated: basename strips traversal, startsWith confirms the directory.
Why secure-coding Avoids These
We use AST‑based validation detection:
| Pattern | Detection Method |
|---|---|
allowlist.includes(key) | Check for includes() in enclosing if‑statement |
hasOwnProperty(key) | Check for hasOwnProperty / hasOwn call |
Guard clause + throw | Detect preceding IfStatement with early exit |
startsWith() validation | Detect path‑validation patterns |
OWASP Coverage Comparison
| Coverage | secure-coding | security |
|---|---|---|
| OWASP Web Top 10 | 10/10 (100 %) | ~3/10 (~30 %) |
| OWASP Mobile Top 10 | 10/10 (100 %) | 0/10 (0 %) |
| Total | 20/20 | ~3/20 |
LLM/AI Message Comparison
Security rules are increasingly consumed by AI coding assistants. Compare the messages:
eslint-plugin-security
Found child_process.exec() with non Literal first argument
eslint-plugin-secure-coding
🔒 CWE-78 OWASP:A03‑Injection CVSS:9.8 | Command injection detected | CRITICAL
Fix: Use execFile/spawn with {shell: false} and array args
📚 https://owasp.org/www-community/attacks/Command_Injection
| Feature | secure-coding | security |
|---|---|---|
| CWE ID | ✅ | ❌ |
| OWASP Category | ✅ | ❌ |
| CVSS Score | ✅ | ❌ |
| Fix Instructions | ✅ | ❌ |
| Documentation Link | ✅ | ❌ |
Feature & Documentation Comparison
| Feature | secure-coding | security |
|---|---|---|
| Total Rules | 89 | 14 |
| Documentation | Comprehensive (per‑rule) | Basic |
| Fix Suggestions/Rule | 3‑6 suggestions | 0 |
| CWE References | ✅ All rules | ❌ None |
| CVSS Scores | ✅ Yes | ❌ No |
| OWASP Mapping | ✅ Web + Mobile | ❌ None |
| TypeScript Support | ✅ Full | ⚠️ Partial |
| Flat Config Support | ✅ Native | ✅ Native |
| Presets | minimal, recommended, strict | recommended |
| Last Updated | Active | Maintenance mode |
Final Verdict
| Category | secure-coding | security | Winner |
|---|---|---|---|
| Performance/Issue | 9.95 ms | 28.16 ms | 🟢 secure-coding |
| Detection | 80 issues | 21 issues | 🟢 secure-coding |
| False Positives | 0 | 4 | 🟢 secure-coding |
| Precision | 100 % | 84 % | 🟢 secure-coding |
| Total Rules | 89 | 14 | 🟢 secure-coding |
| OWASP Coverage | 20/20 | ~3/20 | 🟢 secure-coding |
| Documentation | Comprehensive | Basic | 🟢 secure-coding |
| Fix Suggestions | 3‑6 per rule | 0 | 🟢 secure-coding |
| LLM Optimization | ⭐⭐⭐⭐⭐ | ⭐⭐ | 🟢 secure-coding |
Key Insights
- Performance per issue matters –
secure-codingis 2.83× more efficient per detected issue. - “Speed advantage” = detection gap – the incumbent appears faster only because it misses vulnerabilities.
- 0 false positives – every flagged issue is a real vulnerability.
- 6× more rules – 89 rules vs. 14, covering web, mobile, API, and AI security.
- Developer experience – each rule includes CWE/OWASP references, CVSS scores, and 3‑6 fix suggestions.
Try It Yourself
npm install eslint-plugin-secure-coding --save-dev
// eslint.config.js
import secureCoding from 'eslint-plugin-secure-coding';
export default [secureCoding.configs.recommended];
The benchmark code is open source:
benchmark on GitHub