DEV Community

Cover image for Same Vulnerable File, 4 Linters: Oxlint Native Caught 1, eslint-plugin-security 21, Interlace 46
Ofri Peretz
Ofri Peretz

Posted on • Edited on • Originally published at ofriperetz.dev

Same Vulnerable File, 4 Linters: Oxlint Native Caught 1, eslint-plugin-security 21, Interlace 46

I took one file with 12 classes of real vulnerabilities and ran it through
four linter configurations — two engines (ESLint, Oxlint) crossed with the rules
you'd actually reach for. The detection spread on the same file:

  • Oxlint built-in rules: 1 finding
  • Interlace flagship rules (on Oxlint): 5 — the portability subset (see below)
  • eslint-plugin-security: 21
  • Interlace plugins (on ESLint): 46

The takeaway isn't "tool X wins." It's that the engine is a commodity and the
rules are the product
— and the rules you pick should run on whichever engine
you choose. Here's the data, the false positives, and the parity proof.

The four configurations

  1. Oxlint built-in — the fast Rust engine's own rules, no security plugin.
  2. eslint-plugin-security — the generic incumbent (~1.6M weekly), on ESLint.
  3. Interlace @ ESLint — the domain plugins (secure-coding, node-security, pg, browser-security) combined, so the scope fairly matches the incumbent's monolith.
  4. Interlace @ Oxlint — the same Interlace flagship rules, loaded into Oxlint's JS-plugin runner.

Detection — vulnerable.js (12 vulnerability classes)

Config Engine Findings Notes
Oxlint built-in Oxlint 1 no-eval only
Interlace flagship (3 wired rules) Oxlint 5 the rules that are parity-wired
eslint-plugin-security (recommended) ESLint 21 the classic generic patterns
Interlace (4 plugins, recommended) ESLint 46 across 20 distinct rules

Oxlint's native ruleset caught a single security issue (no-eval) — because
Oxlint is an engine, not a security ruleset. You pick it for speed, not
coverage. The generic incumbent caught the well-known 21. The domain plugins, run
together, caught 46 across 20 rules — SQL injection (pg/no-unsafe-query),
unsafe deserialization, ReDoS, weak hashing, Math.random() for crypto, unsafe
innerHTML, insecure comparisons, and more that a generic ruleset has no rule
for.

False positives — safe-patterns.js (validated-safe code)

Detection only counts if precision holds. Run against a file of deliberately
safe
patterns:

Config False positives On what
Oxlint built-in 0
Interlace @ Oxlint 0
Interlace @ ESLint 3 pg/no-select-all (a perf/clarity rule) ×2, conservative browser-security/no-innerhtml ×1
eslint-plugin-security 5 detect-object-injection on allowlist-validated keys ×3, detect-non-literal-fs-filename on path-validated reads ×2

This is the honest difference. The incumbent's 5 are genuine false positives
— it flags obj[key] even after VALID_KEYS.includes(key), and
fs.readFileSync(p) even after path.basename + startsWith validation,
because it pattern-matches the sink without seeing the guard. The Interlace "3"
aren't security false positives: no-select-all is a performance/clarity rule
firing on SELECT *, and no-innerhtml is conservative by design (it flags
innerHTML even when the value is DOMPurify-sanitized — a deliberate choice you
can disable with a documented comment).

The parity proof: the same rule, both engines

This is the part that matters most. The Interlace flagship rules don't just also
exist
on Oxlint — they emit the identical CWE-tagged finding on both
engines. pg/no-unsafe-query on the same line, under ESLint and under Oxlint:

🔒 CWE-89 OWASP:A03-Injection CVSS:9.8 | Unsafe SQL query detected. Variable interpolation found. | CRITICAL [SOC2,PCI-DSS,NIST-CSF]
   Fix: Use parameterized queries ($1, $2) instead of string concatenation. | https://node-postgres.com/features/queries#parameterized-queries
Enter fullscreen mode Exit fullscreen mode

Same CWE, same OWASP category, same CVSS, same compliance tags — character for
character. So you are not
locked to an engine
: run the full domain set on ESLint today, run the
flagship rules on Oxlint for editor-speed feedback, and get the same security
signal either way. (The full 119-rule set is ESLint-first; the flagship rules are
the ones wired + parity-gated on Oxlint so far.)

How to read this (it's a landscape, not a leaderboard)

  • Oxlint is the right call when you want a fast engine — pair it with real security rules, because its built-in set isn't one.
  • eslint-plugin-security is the familiar generic floor; it catches the classics but pattern-matches sinks, so it costs you false positives on validated code.
  • The domain plugins add depth (database, crypto, DOM, deserialization) the generic set has no rules for — that's the gap, not a verdict.
  • Portability is the point: pick rules that run on both engines so the engine decision stays a performance choice, not a coverage lock-in.

Methodology — reproduce it

Honest disclosure: the fixtures are team-authored (vulnerable.js, 12
vulnerability classes; safe-patterns.js, validated-safe patterns), so these
numbers measure coverage of the surface we designed the rules around — run it on
your own code for an unbiased read. Versions: eslint@9.39.4, oxlint@1.67.0,
eslint-plugin-secure-coding@3.2.0 (27 rules), node-security@4.2.0 (34),
pg@1.4.3 (13), browser-security@1.2.3 (45), eslint-plugin-security@4.0.0.
Method: each plugin's recommended preset, --format json, counted by ruleId
(restricted to the four plugins' rule IDs — a raw run also surfaces a couple of
core/TypeScript notices from the fixture).
The fixtures and the eslint-plugin-security config live in the repo's
packages/eslint-plugin-secure-coding/benchmark/;
the flagship Oxlint config is .oxlintrc.flagship.json at the repo root.

The Interlace side combines the four plugins (fair scope vs the monolith) in one
flat config:

// eslint.config.mjs
import secureCoding from "eslint-plugin-secure-coding";
import nodeSecurity from "eslint-plugin-node-security";
import pg from "eslint-plugin-pg";
import browserSecurity from "eslint-plugin-browser-security";

export default [
  {
    files: ["**/*.js"],
    plugins: {
      "secure-coding": secureCoding,
      "node-security": nodeSecurity,
      pg,
      "browser-security": browserSecurity,
    },
    rules: {
      ...secureCoding.configs.recommended.rules,
      ...nodeSecurity.configs.recommended.rules,
      ...pg.configs.recommended.rules,
      ...browserSecurity.configs.recommended.rules,
    },
  },
];
Enter fullscreen mode Exit fullscreen mode
# install the engines + the plugins
npm i -D eslint@9 oxlint eslint-plugin-secure-coding eslint-plugin-node-security \
  eslint-plugin-pg eslint-plugin-browser-security eslint-plugin-security

# 1) Interlace @ ESLint — the eslint.config.mjs above
npx eslint test-files/vulnerable.js --format json

# 2) the incumbent — same shape, one plugin:
#    plugins: { security }, rules: { ...security.configs.recommended.rules }
npx eslint --config eslint.config.security.mjs test-files/vulnerable.js --format json

# 3) Oxlint built-in (npm-installed — reproducible as-is)
npx oxlint test-files/vulnerable.js
Enter fullscreen mode Exit fullscreen mode

Config 4 (Interlace @ Oxlint) is the only step that needs the repo rather
than npm: the interlace-* Oxlint shims load each plugin's built dist/, so
reproduce it from a clone —
git clone https://github.com/ofri-peretz/eslint && npx turbo run build, then
npx oxlint -c .oxlintrc.flagship.json <file> from the repo root.


Compatibility

Surface Support
Package managers npm, yarn, pnpm, bun
Node >= 18.0.0
ESLint `^8.0.0 \
Oxlint flagship rules run via the {% raw %}interlace-* JS-plugin ports, ESLint↔Oxlint parity-gated in CI
Module system Plugins ship CommonJS; your config can be eslint.config.js or .mjs
# the four plugins benchmarked here
npm install --save-dev eslint-plugin-secure-coding eslint-plugin-node-security eslint-plugin-pg eslint-plugin-browser-security
# yarn add -D … · pnpm add -D … · bun add -d …
Enter fullscreen mode Exit fullscreen mode

Links

⭐ Star on GitHub if you'd rather run security rules that aren't locked to one engine.


I'm Ofri Peretz, a security engineering leader and the author of the
Interlace ESLint ecosystem — domain-specific static analysis for security,
reliability, and performance on the Node.js stack.

ofriperetz.dev · LinkedIn · GitHub

Top comments (0)