Microsoft's ESLint Security Plugin Catches 10% of Vulnerabilities. Here's What It Misses.
A head-to-head benchmark between @microsoft/eslint-plugin-sdl and the Interlace security ecosystem. Microsoft's SDL standard covers 1 of 14 security categories.
Skip to: Results | Every Test Case | False Positives | Verdict
TL;DR
Microsoft's SDL (Security Development Lifecycle) is a respected enterprise security methodology that has shaped how Microsoft builds software since 2004. Their @microsoft/eslint-plugin-sdl brings a subset of those checks to ESLint โ and the rules it has are well-implemented and precise. But the ESLint plugin covers a narrow scope: browser-side DOM security. When tested against the full spectrum of Node.js security vulnerabilities, it caught 4 out of 40.
| Metric | @microsoft/eslint-plugin-sdl | Interlace Ecosystem |
|---|---|---|
| Rules | 17 | 201 |
| Security Detections | 4/40 (10%) | 40/40 (100%) |
| Missed | 36 vulnerabilities | 0 |
| False Alarms | 1 | 0 |
| Precision | 80.0% | 100.0% |
| F1 Score | 17.8% | 100.0% |
๐ก Key takeaway: The SDL ESLint plugin is excellent at what it does โ browser DOM security and code execution prevention. But if your app has a server-side backend, the plugin leaves 12 of 14 security categories uncovered. Use it alongside Node.js-specific security plugins for full coverage.
What Is @microsoft/eslint-plugin-sdl?
Microsoft's SDL (Security Development Lifecycle) is the company's internal security standard that has governed product development since 2004. It's one of the most influential security frameworks in the industry โ every Microsoft product goes through SDL review.
The @microsoft/eslint-plugin-sdl ESLint plugin packages a subset of SDL checks as static analysis rules. With ~100K weekly downloads and active maintenance, it brings Microsoft's security expertise to the ESLint ecosystem.
What Makes SDL Valuable
- Enterprise pedigree โ Built from the same methodology that secures Windows, Azure, and Office
- High precision โ 80% precision means most warnings are real issues, not noise
- Clear error messages โ Every rule produces actionable, well-written diagnostics
- ESLint 9 compatible โ Actively maintained with flat config support
- Smart rule re-exporting โ Bundles relevant ESLint core rules (
no-eval,no-new-func) alongside custom SDL rules
This benchmark tests a specific question: how far does the ESLint plugin go when your goal is comprehensive Node.js security coverage?
Test Setup
| Component | Microsoft SDL | Interlace |
|---|---|---|
| Version | 1.1.0 | 3.0.2 (secure-coding lead) |
| Total Rules | 17 | 201 (11 security plugins) |
| Configuration | recommended | recommended (all 11 plugins) |
| ESLint | 9.39.2 | 9.39.2 |
| Node.js | v20.19.5 | v20.19.5 |
| Platform | macOS (darwin/arm64) | Same |
| Fixtures | 40 vulnerable + 38 safe | Same fixtures |
Both plugins tested with their recommended presets โ the out-of-box experience a developer gets after npm install.
The Results
Detection Summary
Vulnerable Code Detections (out of 40 patterns):
Interlace: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 40/40 (100%)
Microsoft SDL: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 4/40 (10%)
Category-by-Category Summary
| Category | Cases | MS SDL | Interlace | MS SDL Rules Triggered |
|---|---|---|---|---|
| SQL Injection | 4 | โ 0/4 | โ 4/4 | โ |
| Command Injection | 4 | โ 0/4 | โ 4/4 | โ |
| Path Traversal | 4 | โ 0/4 | โ 4/4 | โ |
| Hardcoded Credentials | 4 | โ 0/4 | โ 4/4 | โ |
| JWT Vulnerabilities | 3 | โ 0/3 | โ 3/3 | โ |
| XSS / Code Execution | 4 | โ 4/4 | โ 4/4 | no-inner-html, no-document-write, no-eval, no-new-func |
| Prototype Pollution | 3 | โ 0/3 | โ 3/3 | โ |
| Insecure Randomness | 2 | โ 0/2 | โ 2/2 | โ |
| Weak Cryptography | 3 | โ 0/3 | โ 3/3 | โ |
| Timing Attacks | 2 | โ 0/2 | โ 2/2 | โ |
| NoSQL Injection | 2 | โ 0/2 | โ 2/2 | โ |
| SSRF | 2 | โ 0/2 | โ 2/2 | โ |
| Open Redirect | 1 | โ 0/1 | โ 1/1 | โ |
| ReDoS | 2 | โ 0/2 | โ 2/2 | โ |
| TOTAL | 40 | 4/40 | 40/40 | 4 unique rules |
Microsoft SDL achieved a perfect 4/4 in its focus area (XSS / code execution). It was designed for browser DOM security, and that's exactly what it delivers.
Every Test Case: Detailed Results
Below is every vulnerable pattern in the benchmark, organized by what SDL detected and what it didn't.
XSS / Code Execution (CWE-79, CWE-94) โ MS SDL: 4/4 โ
This is SDL's strength. It caught every XSS and code execution pattern:
// Test 1: innerHTML โ MS SDL โ
@microsoft/sdl/no-inner-html | Interlace โ
export function vuln_xss_innerhtml(userContent) {
document.getElementById("output").innerHTML = userContent;
}
// MS SDL: "Do not write to DOM directly using innerHTML/outerHTML property"
// Test 2: document.write โ MS SDL โ
@microsoft/sdl/no-document-write | Interlace โ
export function vuln_xss_document_write(userInput) {
document.write("<div>" + userInput + "</div>");
}
// MS SDL: "Do not write to DOM directly using document.write or document.writeln methods"
// Test 3: eval() โ MS SDL โ
no-eval (re-exported) | Interlace โ
export function vuln_xss_eval(userCode) {
return eval(userCode);
}
// MS SDL: "eval can be harmful."
// Test 4: new Function() โ MS SDL โ
no-new-func (re-exported) | Interlace โ
export function vuln_xss_new_function(userCode) {
const fn = new Function(userCode);
return fn();
}
// MS SDL: "The Function constructor is eval."
Full marks for Microsoft SDL here. The
no-inner-htmlandno-document-writerules are custom SDL rules with clear, actionable messages. The plugin also smartly re-exports ESLint's coreno-evalandno-new-funcrules, ensuring XSS/code execution is fully covered from onerecommendedconfig.
SQL Injection (CWE-89) โ MS SDL: 0/4
// Test 1: String concatenation โ MISSED by MS SDL โ | Interlace โ
export function vuln_sql_string_concat(userId) {
const query = "SELECT * FROM users WHERE id = '" + userId + "'";
return db.query(query);
}
// Test 2: Template literal โ MISSED by MS SDL โ | Interlace โ
export function vuln_sql_template_literal(email) {
const query = `SELECT * FROM users WHERE email = '${email}'`;
return db.query(query);
}
// Test 3: Dynamic column name โ MISSED by MS SDL โ | Interlace โ
export function vuln_sql_dynamic_column(sortColumn) {
const query = `SELECT * FROM users ORDER BY ${sortColumn}`;
return db.query(query);
}
// Test 4: Conditional query building โ MISSED by MS SDL โ | Interlace โ
export function vuln_sql_conditional(filters) {
let query = "SELECT * FROM products WHERE 1=1";
if (filters.name) {
query += ` AND name = '${filters.name}'`;
}
return db.query(query);
}
SQL injection is a server-side concern. SDL's ESLint plugin focuses on browser-side code, so it doesn't include database query rules. Interlace covers this with
pg/no-sql-injectionandsecure-coding/database-injection.
Command Injection (CWE-78) โ MS SDL: 0/4
// Test 1: exec() with concatenation โ MISSED by MS SDL โ | Interlace โ
export function vuln_cmd_exec_concat(filename) {
const { exec } = require("child_process");
exec("ls -la " + filename, callback);
}
// Test 2: exec() with template literal โ MISSED by MS SDL โ | Interlace โ
export function vuln_cmd_exec_template(filename) {
const { exec } = require("child_process");
exec(`convert ${filename} output.png`, callback);
}
// Test 3: execSync() โ MISSED by MS SDL โ | Interlace โ
export function vuln_cmd_execsync(command) {
const { execSync } = require("child_process");
return execSync(command).toString();
}
// Test 4: spawn() with shell: true โ MISSED by MS SDL โ | Interlace โ
export function vuln_cmd_spawn_shell(userCommand) {
const { spawn } = require("child_process");
return spawn(userCommand, { shell: true });
}
child_processis a Node.js API โ outside SDL's browser-focused scope. Interlace covers this withnode-security/detect-child-process.
Path Traversal (CWE-22) โ MS SDL: 0/4
// Test 1: path.join with user input โ MISSED by MS SDL โ | Interlace โ
export function vuln_path_join(filename) {
const filepath = path.join("./uploads", filename);
return fs.readFileSync(filepath);
}
// Test 2: String concatenation โ MISSED by MS SDL โ | Interlace โ
export function vuln_path_concat(userId) {
return fs.readFileSync("./data/" + userId + "/profile.json");
}
// Test 3: No validation โ MISSED by MS SDL โ | Interlace โ
export async function vuln_path_no_validation(userDir) {
return fs.readdir(`./storage/${userDir}`);
}
// Test 4: URL pathname โ MISSED by MS SDL โ | Interlace โ
export function vuln_path_url_pathname(url) {
const parsedUrl = new URL(url);
return fs.readFileSync(`./static${parsedUrl.pathname}`);
}
Hardcoded Credentials (CWE-798) โ MS SDL: 0/4
// Test 1: Database password โ MISSED by MS SDL โ | Interlace โ
export function vuln_creds_db_password() {
return new Pool({
password: "secretPassword123",
});
}
// Test 2: API key โ MISSED by MS SDL โ | Interlace โ
export function vuln_creds_api_key() {
const apiKey = "sk-prod-abc123def456ghi789jkl012mno345pqr678";
return fetch("https://api.example.com", {
headers: { Authorization: `Bearer ${apiKey}` },
});
}
// Test 3: AWS credentials โ MISSED by MS SDL โ | Interlace โ
export function vuln_creds_aws() {
AWS.config.update({
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
});
}
// Test 4: JWT secret โ MISSED by MS SDL โ | Interlace โ
export function vuln_creds_jwt_secret(user) {
return jwt.sign(user, "my-super-secret-jwt-key-12345");
}
JWT Vulnerabilities (CWE-757, CWE-347) โ MS SDL: 0/3
// Test 1: Algorithm "none" โ MISSED by MS SDL โ | Interlace โ
export function vuln_jwt_alg_none(token) {
return jwt.verify(token, "secret", { algorithms: ["none", "HS256"] });
}
// Test 2: No algorithm restriction โ MISSED by MS SDL โ | Interlace โ
export function vuln_jwt_no_algorithm(token, secret) {
return jwt.verify(token, secret);
}
// Test 3: No expiration โ MISSED by MS SDL โ | Interlace โ
export function vuln_jwt_no_expiry(user) {
return jwt.sign(user, process.env.JWT_SECRET);
}
Prototype Pollution (CWE-1321) โ MS SDL: 0/3
// Test 1: Bracket notation โ MISSED by MS SDL โ | Interlace โ
export function vuln_proto_bracket(obj, key, value) {
obj[key] = value;
return obj;
}
// Test 2: Deep nested manipulation โ MISSED by MS SDL โ | Interlace โ
export function vuln_proto_nested(obj, path, value) {
const keys = path.split(".");
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
// Test 3: Object.assign with parsed JSON โ MISSED by MS SDL โ | Interlace โ
export function vuln_proto_assign(userInput) {
const config = {};
Object.assign(config, JSON.parse(userInput));
return config;
}
Insecure Randomness (CWE-330) โ MS SDL: 0/2
// Test 1: Math.random() for token โ MISSED by MS SDL โ | Interlace โ
export function vuln_random_token() {
return Math.random().toString(36).substring(2);
}
// Test 2: Math.random() for session โ MISSED by MS SDL โ | Interlace โ
export function vuln_random_session() {
return "session_" + Math.floor(Math.random() * 1000000);
}
Weak Cryptography (CWE-327, CWE-328) โ MS SDL: 0/3
// Test 1: MD5 hash โ MISSED by MS SDL โ | Interlace โ
export function vuln_crypto_md5(password) {
return crypto.createHash("md5").update(password).digest("hex");
}
// Test 2: SHA1 hash โ MISSED by MS SDL โ | Interlace โ
export function vuln_crypto_sha1(sensitiveData) {
return crypto.createHash("sha1").update(sensitiveData).digest("hex");
}
// Test 3: DES encryption โ MISSED by MS SDL โ | Interlace โ
export function vuln_crypto_des(plaintext) {
const cipher = crypto.createCipher("des", "password");
return cipher.update(plaintext, "utf8", "hex") + cipher.final("hex");
}
Timing Attacks (CWE-208) โ MS SDL: 0/2
// Test 1: Direct comparison โ MISSED by MS SDL โ | Interlace โ
export function vuln_timing_direct(input, secret) {
return input === secret;
}
// Test 2: Token comparison โ MISSED by MS SDL โ | Interlace โ
export function vuln_timing_token(userToken, storedToken) {
if (userToken === storedToken) {
return { authenticated: true };
}
}
NoSQL Injection (CWE-943) โ MS SDL: 0/2
// Test 1: MongoDB findOne โ MISSED by MS SDL โ | Interlace โ
export async function vuln_nosql_mongo(username) {
return db.collection("users").findOne({ username });
}
// Test 2: $where operator โ MISSED by MS SDL โ | Interlace โ
export async function vuln_nosql_where(userInput) {
return db.collection("users").find({ $where: userInput });
}
SSRF (CWE-918) โ MS SDL: 0/2
// Test 1: fetch with user URL โ MISSED by MS SDL โ | Interlace โ
export async function vuln_ssrf_fetch(userUrl) {
const response = await fetch(userUrl);
return response.json();
}
// Test 2: axios with user URL โ MISSED by MS SDL โ | Interlace โ
export async function vuln_ssrf_axios(endpoint) {
return axios.get(endpoint);
}
Open Redirect (CWE-601) โ MS SDL: 0/1
// Test 1: Express redirect โ MISSED by MS SDL โ | Interlace โ
export function vuln_redirect(req, res) {
const returnUrl = req.query.returnTo;
res.redirect(returnUrl);
}
ReDoS (CWE-1333) โ MS SDL: 0/2
// Test 1: Evil regex โ MISSED by MS SDL โ | Interlace โ
export function vuln_redos_evil(input) {
const evilRegex = /^(a+)+$/;
return evilRegex.test(input);
}
// Test 2: User-controlled regex โ MISSED by MS SDL โ | Interlace โ
export function vuln_redos_user(pattern, input) {
const regex = new RegExp(pattern);
return regex.test(input);
}
The False Positive Analysis
Microsoft SDL produced 1 false positive โ impressively low. Here's what happened:
FP 1: DOMPurify-Sanitized innerHTML
// โ
SAFE: innerHTML with DOMPurify sanitization โ MS SDL flags โ
export function safe_xss_dompurify(userContent) {
const DOMPurify = require("dompurify");
const sanitized = DOMPurify.sanitize(userContent);
document.getElementById("output").innerHTML = sanitized;
}
// MS SDL @microsoft/sdl/no-inner-html:
// "Do not write to DOM directly using innerHTML/outerHTML property"
Why it's a false positive: DOMPurify is the industry-standard sanitization library. Content passed through
DOMPurify.sanitize()is safe forinnerHTMLassignment. SDL's rule flags allinnerHTMLusage regardless of sanitization โ a pragmatic design choice that prioritizes safety over precision. Interlace correctly passes this pattern because it recognizes DOMPurify as a trusted sanitizer.
To be fair: Only 1 false positive out of 38 safe patterns is a 2.6% false positive rate โ one of the lowest in the entire benchmark. SDL's rules are clearly well-calibrated.
The Verdict
| Dimension | Microsoft SDL | Interlace | Winner |
|---|---|---|---|
| Total Rules | 17 | 201 | ๐ข Interlace |
| Security Detection | 10% | 100% | ๐ข Interlace |
| Precision | 80% | 100% | ๐ข Interlace |
| False Positive Rate | 2.6% | 0% | ๐ข Interlace |
| Category Coverage | 1/14 | 14/14 | ๐ข Interlace |
| ESLint 9 Support | โ | โ | Tie |
| Active Maintenance | โ | โ | Tie |
Where Microsoft SDL Excels
Let's give credit where it's due. Microsoft SDL does its job well:
Browser DOM security (its focus area):
- โ
XSS Prevention: 4/4 โ Perfect score. Catches
innerHTML,document.write,eval, andnew Function() - โ Precision: 80% โ Most warnings are real issues, not noise
- โ Error Messages: Clear, actionable diagnostics that tell developers what's wrong
- โ Smart Bundling: Re-exports relevant ESLint core rules so one config covers the full XSS attack surface
Additional browser rules not covered in this benchmark:
- ๐ก๏ธ
no-document-domainโ Prevents frame security bypass - ๐ก๏ธ
no-cookiesโ Flags directdocument.cookieaccess - ๐ก๏ธ
no-postmessage-star-originโ Prevents cross-origin data leaks viapostMessage("*") - ๐ก๏ธ
no-msapp-exec-unsafeโ Windows UWP security - ๐ก๏ธ
no-winjs-html-unsafeโ WinJS security
For teams building browser-only JavaScript (no Node.js backend), SDL provides a solid, enterprise-grade foundation.
Where SDL Needs Complementary Tools
SDL's ESLint plugin was built for browser JavaScript. For a modern Node.js application, these server-side categories aren't covered:
- SQL Injection (0/4) โ No database query rules
- Command Injection (0/4) โ No
child_processrules - Path Traversal (0/4) โ No
fsmodule rules - Hardcoded Credentials (0/4) โ No secret detection
- JWT (0/3) โ No authentication rules
- Prototype Pollution (0/3) โ No object injection rules
- Weak Crypto (0/3) โ No
cryptomodule rules - Insecure Randomness (0/2) โ No
Math.random()detection - Timing Attacks (0/2) โ No timing-safe rules
- NoSQL Injection (0/2) โ No MongoDB rules
- SSRF (0/2) โ No outbound request rules
- Open Redirect (0/1) โ No Express rules
- ReDoS (0/2) โ No regex complexity rules
This isn't a flaw in SDL โ it's a scope boundary. The SDL methodology itself covers all of these categories. The ESLint plugin implements only the browser-relevant subset. For the rest, you need Node.js-specific tools.
Recommendation: Use Both
The best enterprise config combines SDL's browser security with Node.js-specific security plugins:
// eslint.config.js โ Enterprise security: SDL + Interlace
import sdl from "@microsoft/eslint-plugin-sdl";
import secureCoding from "eslint-plugin-secure-coding";
import nodeSecurity from "eslint-plugin-node-security";
import pg from "eslint-plugin-pg";
import jwt from "eslint-plugin-jwt";
export default [
...sdl.configs.recommended, // Browser DOM security โ
secureCoding.configs.recommended, // Core OWASP patterns โ
nodeSecurity.configs.recommended, // Node.js runtime โ
pg.configs.recommended, // Database layer โ
jwt.configs.recommended, // Auth layer โ
];
SDL gives you enterprise compliance and browser security. Interlace gives you full-stack Node.js coverage. Together, they cover the entire attack surface.
Methodology
Fixture Design
All 40 vulnerable patterns are real-world code from production codebases, annotated with CWE identifiers and severity ratings. The 38 safe patterns are correctly-implemented secure alternatives that should NOT trigger warnings.
Reproducibility
git clone https://github.com/AshDevFr/eslint-benchmark-suite
cd eslint-benchmark-suite
npm install
npm run benchmark:fn-fp
Every claim in this article comes from the published benchmark results and can be independently verified.
Part of the Benchmark Series
This article is part of the ESLint Security Benchmark Series:
- ๐ 17 Plugins Benchmarked: The Full Ecosystem Report
- SonarJS vs Interlace: 269 Rules, 65% Missed
- ๐ You are here: Microsoft SDL vs Interlace
- eslint-plugin-security Is Abandoned
Explore the Full Ecosystem
201 security rules. 11 specialized plugins. 100% detection. 0 false positives.
Next in the ESLint Security Benchmark Series:
- 17 ESLint Security Plugins Benchmarked: The Full Ecosystem Report
- Quality Linters Benchmarked: Unicorn vs SonarJS vs Interlace
Follow @ofri-peretz to get notified.
Build Securely.
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.