Tools: eslint-plugin-security Is Unmaintained. Here's What Nobody Tells You.

Tools: eslint-plugin-security Is Unmaintained. Here's What Nobody Tells You.

Source: Dev.to

TL;DR — Migrate in 60 Seconds ## The Numbers Don't Lie ## Is eslint-plugin-security Still Maintained? ## What Does eslint-plugin-security Miss? ## 1. Modern SQL Injection Patterns ## 2. AI/LLM Vulnerabilities ## 3. JWT Security Issues ## 4. Connection Leaks ## 5. Path Traversal with Modern APIs ## The 13 Rules, Reviewed ## Why This Matters ## What Should I Use Instead of eslint-plugin-security? ## The Security Ecosystem: 10 Plugins, 194 Rules ## Quick Start: Choose Your Stack ## Does This Support ESLint 9 Flat Config? ## Rule-by-Rule Migration ## OWASP Top 10 Coverage ## Ready to Upgrade? ## A Note on Open Source Maintenance ## The Bottom Line ## Explore the Full Ecosystem Skip to: What It Misses | The Alternative | Migration Guide | OWASP Coverage That's it. You now have 27 rules instead of 13, with full OWASP Top 10 mapping. Add the full security ecosystem and you get 194 security rules. Want the full story? Keep reading. Let's talk about the elephant in the Node.js security room. eslint-plugin-security is the most-installed ESLint security plugin. It has 1.5M+ weekly downloads. It's recommended by countless tutorials. And it's been effectively unmaintained for years. This isn't a hit piece—it's a reality check. And a thank you. eslint-plugin-security pioneered JavaScript security linting. When it launched, it was the only game in town. The maintainers did important work that inspired everything that came after. But the threat landscape has changed. Let's see where we are in 2026. The plugin was groundbreaking when it launched. But the JavaScript security landscape has changed dramatically since 2020. Let's look at the actual repository: Note: The plugin was transferred to eslint-community in 2023, which extended its life. But activity remains minimal, and the rule set hasn't grown. Let's be specific. Here are vulnerability categories the plugin cannot detect: The plugin has no PostgreSQL-aware rules. No understanding of parameterized queries. No detection of string concatenation in database contexts. Detection: eslint-plugin-pg catches this with pg/no-unsafe-query. AI security didn't exist when the plugin was written. There are zero rules for prompt injection, system prompt leakage, or tool abuse. Detection: eslint-plugin-vercel-ai-security provides 19 rules for Vercel AI SDK patterns. JWT attacks are some of the most common vulnerabilities in Node.js applications. The plugin has no JWT-specific rules. Detection: eslint-plugin-jwt catches algorithm confusion, missing expiration, and weak secrets. Production outages from connection exhaustion are common. No detection. Detection: pg/no-missing-client-release ensures every connect() has a matching release(). The plugin's path traversal detection is limited to older fs patterns. Detection: node-security/detect-non-literal-fs-filename understands modern node:fs/promises imports and validates path safety. Let's look at what eslint-plugin-security actually provides: Verdict: ~4 rules are still fully relevant. ~5 are partially useful. ~4 are obsolete. If you're using eslint-plugin-security as your primary security linting: The modern approach is domain-specific security plugins that understand context. Think of it as a layered security architecture: Node.js Backend (Express/Fastify): Serverless (AWS Lambda): MongoDB/Mongoose Backend: Yes. All 10 security plugins are built for the modern ESLint ecosystem with native flat config support: Every eslint-plugin-security rule has a modern replacement: But that's just the migration. The real value is the 181 additional security rules you gain: The ultimate test of a security plugin is OWASP coverage: *A06 (Vulnerable Components) requires Software Composition Analysis (SCA) tools like npm audit, Snyk, or Socket—not static analysis. ESLint can't detect outdated dependencies. Total coverage: eslint-plugin-security ~20% | Interlace ~100% Option 1: Quick Migration (60 seconds) Option 2: Full Security Suite (5 minutes) Option 3: See What You're Missing First Run ESLint with the new plugins on your codebase. You'll likely find vulnerabilities that were invisible before. Maintaining open-source projects is hard, often thankless work. The eslint-plugin-security maintainers gave the community years of value. This article isn't criticism—it's recognition that the community has evolved, and our tools should too. If you use and benefit from open-source security tooling, consider sponsoring maintainers who keep the ecosystem alive. eslint-plugin-security was important. It pioneered JavaScript security linting. But we owe it to our codebases to use tools that match today's threat landscape. 13 rules from 2020 aren't enough for 2026. 194 security rules. 10 specialized plugins. 100% OWASP Top 10 coverage. The Interlace ESLint Ecosystem provides comprehensive security static analysis for modern Node.js applications. 📖 Documentation | ⭐ GitHub | 📦 NPM 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. ofriperetz.dev | LinkedIn | GitHub Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security const query = `SELECT * FROM users WHERE email = '${userInput}'`; await pool.query(query); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security const query = `SELECT * FROM users WHERE email = '${userInput}'`; await pool.query(query); CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security const query = `SELECT * FROM users WHERE email = '${userInput}'`; await pool.query(query); CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { generateText } from "ai"; const response = await generateText({ model: openai("gpt-4"), prompt: userInput, // Prompt injection - no system prompt protection }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { generateText } from "ai"; const response = await generateText({ model: openai("gpt-4"), prompt: userInput, // Prompt injection - no system prompt protection }); CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { generateText } from "ai"; const response = await generateText({ model: openai("gpt-4"), prompt: userInput, // Prompt injection - no system prompt protection }); CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security jwt.verify(token, secret, { algorithms: ["none"] }); // Algorithm confusion attack Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security jwt.verify(token, secret, { algorithms: ["none"] }); // Algorithm confusion attack CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security jwt.verify(token, secret, { algorithms: ["none"] }); // Algorithm confusion attack CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security async function getUser(id) { const client = await pool.connect(); return client.query("SELECT * FROM users WHERE id = $1", [id]); // client.release() never called - connection leak } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security async function getUser(id) { const client = await pool.connect(); return client.query("SELECT * FROM users WHERE id = $1", [id]); // client.release() never called - connection leak } CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security async function getUser(id) { const client = await pool.connect(); return client.query("SELECT * FROM users WHERE id = $1", [id]); // client.release() never called - connection leak } CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { readFile } from "node:fs/promises"; const content = await readFile(`./uploads/${filename}`); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { readFile } from "node:fs/promises"; const content = await readFile(`./uploads/${filename}`); CODE_BLOCK: // ❌ NOT detected by eslint-plugin-security import { readFile } from "node:fs/promises"; const content = await readFile(`./uploads/${filename}`); COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-node-security \ eslint-plugin-express-security \ eslint-plugin-pg \ eslint-plugin-jwt Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-node-security \ eslint-plugin-express-security \ eslint-plugin-pg \ eslint-plugin-jwt COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-node-security \ eslint-plugin-express-security \ eslint-plugin-pg \ eslint-plugin-jwt COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-lambda-security \ eslint-plugin-pg Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-lambda-security \ eslint-plugin-pg COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-lambda-security \ eslint-plugin-pg COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-mongodb-security \ eslint-plugin-node-security \ eslint-plugin-jwt Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-mongodb-security \ eslint-plugin-node-security \ eslint-plugin-jwt COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-mongodb-security \ eslint-plugin-node-security \ eslint-plugin-jwt COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-vercel-ai-security \ eslint-plugin-node-security Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-vercel-ai-security \ eslint-plugin-node-security COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-vercel-ai-security \ eslint-plugin-node-security COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-browser-security Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-browser-security COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding \ eslint-plugin-browser-security CODE_BLOCK: // eslint.config.js - Full security suite import secureCoding from "eslint-plugin-secure-coding"; import nodeSecurity from "eslint-plugin-node-security"; import express from "eslint-plugin-express-security"; import pg from "eslint-plugin-pg"; import jwt from "eslint-plugin-jwt"; export default [ secureCoding.configs.recommended, nodeSecurity.configs.recommended, express.configs.recommended, pg.configs.recommended, jwt.configs.recommended, ]; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // eslint.config.js - Full security suite import secureCoding from "eslint-plugin-secure-coding"; import nodeSecurity from "eslint-plugin-node-security"; import express from "eslint-plugin-express-security"; import pg from "eslint-plugin-pg"; import jwt from "eslint-plugin-jwt"; export default [ secureCoding.configs.recommended, nodeSecurity.configs.recommended, express.configs.recommended, pg.configs.recommended, jwt.configs.recommended, ]; CODE_BLOCK: // eslint.config.js - Full security suite import secureCoding from "eslint-plugin-secure-coding"; import nodeSecurity from "eslint-plugin-node-security"; import express from "eslint-plugin-express-security"; import pg from "eslint-plugin-pg"; import jwt from "eslint-plugin-jwt"; export default [ secureCoding.configs.recommended, nodeSecurity.configs.recommended, express.configs.recommended, pg.configs.recommended, jwt.configs.recommended, ]; COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding npx eslint . --max-warnings 0 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding npx eslint . --max-warnings 0 COMMAND_BLOCK: npm uninstall eslint-plugin-security npm install -D eslint-plugin-secure-coding npx eslint . --max-warnings 0 COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding eslint-plugin-pg \ eslint-plugin-jwt eslint-plugin-node-security Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding eslint-plugin-pg \ eslint-plugin-jwt eslint-plugin-node-security COMMAND_BLOCK: npm install -D eslint-plugin-secure-coding eslint-plugin-pg \ eslint-plugin-jwt eslint-plugin-node-security CODE_BLOCK: npx eslint . --format stylish Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: npx eslint . --format stylish CODE_BLOCK: npx eslint . --format stylish - You're missing ~90% of detectable vulnerabilities - You have no OWASP Top 10 coverage map for compliance - You have no AI/LLM protection as your team adopts AI tools - You're running on 2020-era detection in a 2026 threat landscape - Getting Started with eslint-plugin-secure-coding - SQL Injection in Node.js: The Pattern 80% Get Wrong - The JWT Algorithm 'None' Attack - Mapping Your Codebase to OWASP Top 10 - The 30-Minute Security Audit