Your ESLint Security Plugin is Missing 80% of Vulnerabilities (I Have Proof)

Published: (December 20, 2025 at 11:25 AM EST)
5 min read
Source: Dev.to

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

Metricsecure-codingsecurityWinner
Performance / Issue24.95 ms25.12 ms🟢 secure-coding
Total Time723.54 ms527.58 ms🔵 security
Issues Found2921🟢 secure-coding
Detection Rate138 %100 %🟢 secure-coding

Rule‑by‑Rule Detection

Rule Categorysecuritysecure-codingDiff
Timing Attacks15+4 🟢
Child Process24+2 🟢
Non‑literal Regexp13+2 🟢
Eval / Code Execution12+1 🟢
Insufficient Randomness01+1 🟢
FS Path Traversal55=
Object Injection55=
Dynamic Require22=
Unsafe Regex22=
Buffer APIs20-2 🔵
TOTAL2129+8

Key Finding: With the same rule categories, secure-coding finds 38 % more issues while maintaining nearly identical efficiency per issue.

Next, I tested each plugin’s recommended configuration—the out‑of‑box experience.

Results

Metricsecure-codingsecurityWinner
Performance / Issue9.95 ms28.16 ms🟢 secure-coding
Total Time795.99 ms591.41 ms🔵 security
Issues Found8021🟢 secure-coding
Rules Triggered3010🟢 secure-coding
Total Rules8914🟢 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

PluginFalse PositivesPrecision
secure-coding0100 %
security484 %

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:

PatternDetection Method
allowlist.includes(key)Check for includes() in enclosing if‑statement
hasOwnProperty(key)Check for hasOwnProperty / hasOwn call
Guard clause + throwDetect preceding IfStatement with early exit
startsWith() validationDetect path‑validation patterns

OWASP Coverage Comparison

Coveragesecure-codingsecurity
OWASP Web Top 1010/10 (100 %)~3/10 (~30 %)
OWASP Mobile Top 1010/10 (100 %)0/10 (0 %)
Total20/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
Featuresecure-codingsecurity
CWE ID
OWASP Category
CVSS Score
Fix Instructions
Documentation Link

Feature & Documentation Comparison

Featuresecure-codingsecurity
Total Rules8914
DocumentationComprehensive (per‑rule)Basic
Fix Suggestions/Rule3‑6 suggestions0
CWE References✅ All rules❌ None
CVSS Scores✅ Yes❌ No
OWASP Mapping✅ Web + Mobile❌ None
TypeScript Support✅ Full⚠️ Partial
Flat Config Support✅ Native✅ Native
Presetsminimal, recommended, strictrecommended
Last UpdatedActiveMaintenance mode

Final Verdict

Categorysecure-codingsecurityWinner
Performance/Issue9.95 ms28.16 ms🟢 secure-coding
Detection80 issues21 issues🟢 secure-coding
False Positives04🟢 secure-coding
Precision100 %84 %🟢 secure-coding
Total Rules8914🟢 secure-coding
OWASP Coverage20/20~3/20🟢 secure-coding
DocumentationComprehensiveBasic🟢 secure-coding
Fix Suggestions3‑6 per rule0🟢 secure-coding
LLM Optimization⭐⭐⭐⭐⭐⭐⭐🟢 secure-coding

Key Insights

  • Performance per issue matterssecure-coding is 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

Back to Blog

Related posts

Read more »