Back to directory
ofri-peretz avatar
ofri-peretz / eslint

eslint

Security & code-quality ESLint plugins — 350+ CWE-mapped rules across 18 domains, ESLint + Oxlint. The lint layer AI-generated code needs.

13

Stars

0

Forks

0

Watchers

MIT

License

Interlace    oxlint    ESLint

The Interlace ESLint Ecosystem — security and code-quality rules that explain themselves,
to your team and to the AI writing half your code.
Every finding ships with a CWE, a CVSS score, an OWASP mapping, and the fix.

Quality Gate CodeQL OpenSSF Scorecard Coverage Last commit Published with npm provenance

ESLint 8.40 | 9 | 10 Node.js 18+ TypeScript 5.9+ License: MIT Maintained with Changesets Built with Turborepo

GitHub stars   Follow on Dev.to

Star the repo  ·  👀 Watch releases  ·  📨 Follow the writeups  ·  📊 Live metrics

If these plugins caught a real bug for you, a star is the signal that keeps the ecosystem maintained.


Install one plugin, catch a real bug

npm i -D eslint eslint-plugin-postgresql-security
// eslint.config.mjs
import pg from 'eslint-plugin-postgresql-security';

export default [pg.configs.recommended];
// app.js
import { Pool } from 'pg';
const pool = new Pool();

export async function getUser(id) {
  return pool.query(`SELECT * FROM users WHERE id = '${id}'`); // ← interpolated
}
app.js
  5:21  error  🔒 Unsafe SQL query construction detected (template literal). | CRITICAL
   Fix: Use parameterized queries ($1, $2) instead of interpolating values.
   https://owasp.org/www-community/attacks/SQL_Injection   postgresql-security/no-unsafe-query

That message is the whole thesis. Severity so you can triage it, the fix so a human doesn't have to go looking, and a citation so an LLM asked to "fix the lint errors" produces a parameterized query instead of a plausible-looking escape helper. Machine-readable equivalents ship via the SARIF formatter for GitHub code scanning.


The ecosystem

30 published plugins · 465 rules — 21 security plugins (260 rules), 7 code-quality plugins (107 rules), 2 React plugins (98 rules). Counts generated 2026-08-05 from the source tree — never hand-typed.

Every package is versioned, released, and installable on its own. Take the two that match your stack; ignore the other twenty-eight.

🔒 Security — languages, platforms & SDKs

18 plugins · 209 rules

Package Rules What it catches Docs Downloads
eslint-plugin-browser-security 45 Detects DOM XSS, postMessage abuse, tokens in localStorage, insecure cookies, clickjacking, mixed content, and CSP gaps docs downloads
eslint-plugin-node-security 37 Detects command injection, path traversal, SSRF, zip slip, and weak crypto (MD5/SHA-1, ECB, static IV) in fs, child_process, vm, and crypto docs downloads
eslint-plugin-secure-coding 28 Detects LDAP, XPath, XXE, GraphQL and template injection, unsafe deserialization, ReDoS, missing authentication, and PII in logs docs downloads
eslint-plugin-vercel-ai-security 19 Detects prompt injection, system-prompt leaks, hardcoded API keys, and unvalidated model output in generateText and streamText docs downloads
eslint-plugin-mongodb-security 16 Detects NoSQL operator injection, unsafe queries and regex, hardcoded connection strings, and missing TLS docs downloads
eslint-plugin-jwt-security 13 Detects algorithm confusion (CVE-2022-23540), alg:none, weak or hardcoded secrets, and decode-without-verify docs downloads
eslint-plugin-postgresql-security 13 Detects SQL injection, unreleased clients, floating queries, unsafe search_path, and insecure SSL docs downloads
eslint-plugin-knex-security 5 Detects SQL injection in raw queries built with string concatenation or template literals, connection configuration that disables TLS or certificate validation, and hardcoded database credentials docs downloads
eslint-plugin-drizzle-security 4 Detects SQL injection in raw queries built with string concatenation or template literals docs downloads
eslint-plugin-mcp-sdk-security 4 Catches tools registered without an input schema, handlers reading arguments the schema never declared, model-visible descriptions built from dynamic text, and tool arguments reaching a shell readme downloads
eslint-plugin-prisma-security 4 Detects SQL injection in raw queries built with string concatenation or template literals docs downloads
eslint-plugin-sequelize-security 4 Detects SQL injection in raw sequelize.query() and Sequelize.literal() calls built with string concatenation or template literals, connection configuration that disables TLS or certificate validation, and hardcoded database credentials docs downloads
eslint-plugin-typeorm-security 4 Detects SQL injection in raw queries built with string concatenation or template literals, connection configuration that disables TLS or certificate validation, and hardcoded database credentials docs downloads
eslint-plugin-anthropic-security 3 Catches hardcoded Claude API keys, the browser escape hatch that ships them to every visitor, and system prompts assembled from untrusted input readme downloads
eslint-plugin-gemini-security 3 Catches safety thresholds set to BLOCK_NONE, hardcoded API keys, and system instructions assembled from untrusted input readme downloads
eslint-plugin-mysql-security 3 Detects SQL injection in raw queries built with string concatenation or template literals, connection configuration that disables TLS or certificate validation, and hardcoded database credentials docs downloads
eslint-plugin-openai-security 3 Catches dangerouslyAllowBrowser, hardcoded API keys, and system prompts assembled from untrusted input readme downloads
eslint-plugin-sqlite-security 1 Detects SQL injection in raw queries built with string concatenation or template literals docs downloads

🏗️ Security — application frameworks

3 plugins · 51 rules

Package Rules What it catches Docs Downloads
eslint-plugin-express-security 28 Detects permissive CORS, missing CSRF protection, missing helmet headers, insecure cookies, and GraphQL introspection in production docs downloads
eslint-plugin-lambda-security 14 Detects overly permissive IAM policies and CORS, unvalidated event bodies, secrets in env vars, and leaked error details docs downloads
eslint-plugin-nestjs-security 9 Detects missing auth guards, missing validation pipes, unthrottled routes, and exposed private fields docs downloads

🧭 Architecture

1 plugin · 55 rules

Package Rules What it catches Docs Downloads
eslint-plugin-import-next 55 Drop-in replacement for eslint-plugin-import, 3.1x faster end-to-end, zero-config migration docs downloads

✨ Code quality

6 plugins · 52 rules

Package Rules What it catches Docs Downloads
eslint-plugin-conventions 15 Enforces filename case, magic-number bans, commented-out code, expiring TODOs, and deprecated-API usage docs downloads
eslint-plugin-maintainability 12 Limits cognitive complexity, nesting depth, parameter counts, duplicate functions, and unhandled or silent errors docs downloads
eslint-plugin-reliability 9 Enforces error handling, network timeouts, null checks, and safe type narrowing docs downloads
eslint-plugin-modularity 6 Enforces DDD value objects and anemic-model checks, naming, REST conventions, and utility isolation docs downloads
eslint-plugin-operability 6 Bans debug code and console logging in production, verbose error messages, and process.exit calls docs downloads
eslint-plugin-modernization 4 Auto-fixes legacy patterns to ES2022+ (Array.at, template literals, EventTarget, Array.isArray) docs downloads

⚛️ React

2 plugins · 98 rules

Package Rules What it catches Docs Downloads
eslint-plugin-react-features 61 Hooks, prop-types, JSX correctness, render performance, and class-to-hooks migration rules for modern React codebases docs downloads
eslint-plugin-react-a11y 37 WCAG 2.1 rules for ARIA, alt text, keyboard interaction, and focus management, with auto-fixes docs downloads

Supporting tools

Package What it does
@interlace/eslint-devkit Build your own team-specific rules — AST helpers, typed rule creator, and the LLM message formatter these plugins use

The table and counts above are generated from the source tree by npm run sync:root-readme, and CI fails if this file drifts from it. The canonical counts contract is interlace-numbers.json.


Why an independent ecosystem?

Every rule here is a clean-room implementation — familiar naming, different engineering.

What that buys you
🚀 No upstream queue A CVE lands, a rule ships. We measure that latency ourselves — cve-rule-latency.md
🤖 AI-optimized messages CWE + CVSS + OWASP + compliance tags inside the message body, where the model actually reads them
One codebase Shared AST utilities, shared perf budget, one per-rule latency gate across every rule
🏗️ Consistent surface Same preset names, same option shapes, same docs layout in every plugin
📚 Educational by default Each message explains why, not just what — the guardrail teaches on the way past
🔧 Modern flat config ESLint 8.40 / 9 / 10, plus an oxlint parity gate for the rules oxlint can run natively

The deeper case — why any of this is worth money:

  • cicd-impact/value-philosophy.md — what value is, the two ruling systems (capitalism + humanism), and the unbroken chain from human incentives down to a single ESLint rule.
  • cicd-impact/philosophy.md — how CI/CD friction expresses itself along money, velocity, and deliverability.
  • cicd-impact/ — a forkable calculator that turns that into a $/CI-minute figure for any GitHub Actions repo.

How we measure quality (FP / FN / TP / TN)

Every finding is one of four things, and we track all four — per rule, per CWE, per OSS repo:

Code IS vulnerable Code is clean
Rule fires TP — signal FP — noise
Rule silent FN — miss TN — correct quiet
  • Precision = TP / (TP + FP) — of what we flag, how much is real?
  • Recall = TP / (TP + FN) — of what we should flag, how much did we catch?
  • F1 — the harmonic mean, so neither can be gamed alone.

Recall first, precision second. A missed CWE is worse than a noisy rule, and we don't regress recall to chase FPs.

On the ILB-Arena corpus (40 vulnerable / 38 safe fixtures, 18 plugins scored, 17 security-relevant) Interlace ranks 1st; the next-best plugin scores 66.1% F1. We publish the ordinal result and not the absolute one on purpose — a perfect score on a self-authored 40-fixture corpus is a regression test wearing a benchmark's clothes, and we withdrew our own headline number when an audit found it unsupported by its own evidence file.

That is what CLAIMS.md is for: every marketing claim in this repo maps to the evidence file that produced it, carries a verification date, goes stale after 90 days, and is withdrawn in public when it doesn't hold. npm run audit:claims fails the build if a withdrawn claim reappears anywhere.


Performance — measured weekly, not claimed

last verified corpus file-set parity

Stack Cold Warm Findings Files
Interlace on ESLint cold warm findings files
Interlace on oxlint cold warm findings files
Community plugins (ESLint) cold warm findings files
oxlint built-ins (different scope) cold warm findings files

Cold = --no-cache. Warm = --cache, primed — the number you feel on every save and every CI run.

Interlace on oxlint runs the same rulesets through the oxlint engine via our JS-plugin shims. Same rules, different engine.

Head-to-head, by job

Whole-plugin comparisons mislead in both directions: a plugin bundles jobs its rival does not have, so an aggregate delta is partly a difference in scope rather than speed. The unit of comparison here is a job — a concrete capability — with the specific rules named on both sides.

head-to-head uncontested

Job Result
Circular dependencies circular deps
DOM XSS sinks dom xss
Hardcoded secrets secrets
Command / shell injection command injection
ReDoS redos
Path traversal path traversal
Timing-attack comparison timing

Two of those rows are losses. They stay: a table where every row favours us is authored, not measured. The full breakdown — including where competitors are genuinely better — lives in benchmarks/suites/ilb-headline/matchups.ts, where every cited rule is CI-verified to exist.

Who we compare against

Named, versioned, and linked — so you can check we did not pick a weak opponent:

eslint-plugin-security · eslint-plugin-sonarjs · @microsoft/eslint-plugin-sdl · eslint-plugin-no-unsanitized · eslint-plugin-security-node · eslint-plugin-no-secrets · eslint-plugin-regexp · eslint-plugin-import

How we benchmark the benchmark

  • Scope: only our SDK-agnostic plugins — secure-coding, node-security, browser-security, import-next. Framework-bound plugins (pg, jwt, nestjs-security…) have no comparable competitor; an uncontested win there tells you nothing.
  • Corpus: two real repos — nestjs (Node) and shadcn-ui (frontend), shallow-cloned at a recorded commit SHA.
  • Same file set: every stack lints an identical glob, and parity is asserted — a run where the stacks saw different files is refused, not published.
  • Median of N after a discarded warmup, with min–max spread recorded so a noisy machine is visible rather than hidden.
  • Failures are recorded, never dropped. A crash cannot be timed as a fast run; a stack that processed 0 files fails the gate instead of rendering an impossibly fast bar.
  • oxlint built-ins run a different rule scope (no secrets, injection, or CSP analysis). Shown for context and excluded from "fastest" highlighting — a different job, not a peer.

Every number is regenerated by weekly-benchmark.yml (Mondays, 09:00 UTC) on public runners, stored append-only, and published as the badges above. Reproduce locally: npm run ilb:headline -- --repo=nestjs --repeat=5.

Compatibility

ESLint

Last refresh: 2026-08-02 (source: npm registry — npm run stats:eslint-versions)

ESLint major Weekly downloads Share Status
v10 23.6M 11.08% ✅ Supported (forward-looking)
v9 109.1M 51.13% ✅ Supported (current default)
v8 (≥ 8.40) 60.3M 28.29% ✅ Supported (legacy active)
v7 and older 20.3M 9.51% ❌ Unsupported (EOL)

Supported majors cover 90.49% of weekly ESLint downloads. Every published package declares "eslint": "^8.40.0 || ^9.0.0 || ^10.0.0".

Why the floor is 8.40 and not 8.0: releases before 8.40 predate context.sourceCode / context.filename, which this repo reads at 333 call sites. Measured on [email protected], ESLint 8.0.0 and 8.39.0 throw on load and 8.40.0 works — so the range now states the oldest minor the rules actually run on (#407).

When a major gets supported: either it holds ≥20% of weekly npm downloads, or it is the next major after a currently-supported one (we ship support pre-emptively so you can upgrade ahead of the curve, not behind it). A major is dropped only after two consecutive refreshes below the gate and a supported successor exists. Full policy: docs/ESLINT_VERSION_SUPPORT.md.

Node.js

Node.js Status
24.x ✅ Active development — the repo's engines.node pin, what CI runs
22.x LTS ✅ Supported — recommended for production
20.x LTS ✅ Supported — long-term-stable baseline
18.x ✅ Supported (minimum) — every package's engines.node: ">=18.0.0"
≤ 17 ❌ Unsupported (EOL upstream)

Who this is for

Role What it does for you
Security engineers Catch CWEs at edit time with CVSS + OWASP already attached, and export SARIF into GitHub code scanning
Tech leads Enforce architectural decisions automatically instead of re-litigating them in review
Platform teams One guardrail set that scales across repos, with a per-rule latency budget so CI doesn't pay for it
Teams shipping with AI Messages an LLM can act on correctly — the difference between "fixed the lint error" and "fixed the vulnerability"
Engineering managers New engineers learn the codebase through guardrails, not tribal knowledge

Contributing & security

Also worth reading: ROADMAP.md for what's next, and AGENTS.md / CLAUDE.md if you're pointing an AI agent at this repo.


📚 Docs eslint.interlace.tools
📦 npm All Interlace packages
📊 Live metrics ofriperetz.dev/stats
✍️ Writeups dev.to/ofri-peretz

License

MIT © Ofri Peretz — see LICENSE.

Interlace

Made with ❤️ from lessons learned in the trenches

Releases

1.5.4 Patch Changes

#407 5ecf4d1 Thanks @ofri-peretz! - Correct the declared ESLint floor: ^8.0.0 → ^8.40.0. context.sourceCode landed in ESLint 8.40. The shared devkit reads it without a fallback a...

3.5.0 Minor Changes

#372 a7520c8 Thanks @ofri-peretz! - Drop detect-object-injection from the recommended preset Measured over express + axios + sequelize, the rule fired 535 times — 85% of everythi...

0.3.2 Patch Changes

#407 5ecf4d1 Thanks @ofri-peretz! - Correct the declared ESLint floor: ^8.0.0 → ^8.40.0. context.sourceCode landed in ESLint 8.40. The shared devkit reads it without a fallback a...

0.1.5 Patch Changes

#407 5ecf4d1 Thanks @ofri-peretz! - Correct the declared ESLint floor: ^8.0.0 → ^8.40.0. context.sourceCode landed in ESLint 8.40. The shared devkit reads it without a fallback a...

0.3.2 Patch Changes

#407 5ecf4d1 Thanks @ofri-peretz! - Correct the declared ESLint floor: ^8.0.0 → ^8.40.0. context.sourceCode landed in ESLint 8.40. The shared devkit reads it without a fallback a...