DevSecOps

ESLint Security Rules Configuration: A Practical Guide

ESLint can catch security issues before they reach production. Here is how to configure security-focused rules that actually help without drowning you in noise.

Michael
DevSecOps Lead
5 min read

ESLint is already running in most JavaScript and TypeScript projects. It checks code style, enforces best practices, and catches common mistakes. But most teams never configure it for security, which means they are leaving free vulnerability detection on the table.

The default ESLint configuration catches nothing security-relevant. You need to add security-focused plugins and rules deliberately. The challenge is doing this without creating so much noise that developers start ignoring the warnings.

The Core Security Plugins

eslint-plugin-security

This is the most widely used security plugin for ESLint. It flags patterns that are commonly associated with vulnerabilities in Node.js applications.

The useful rules include:

  • detect-eval-with-expression -- Flags eval() calls with dynamic expressions. This catches the most straightforward code injection vector in JavaScript.
  • detect-non-literal-require -- Flags require() calls with dynamic arguments. Dynamic requires can be exploited for directory traversal or loading unexpected modules.
  • detect-no-csrf-before-method-override -- Catches a specific Express.js misconfiguration where CSRF protection is applied before the method-override middleware, allowing CSRF bypass.
  • detect-possible-timing-attacks -- Flags string comparisons that might be used for authentication tokens, where timing side channels could leak information.

The noisy rules you should probably disable:

  • detect-object-injection -- Flags any bracket notation property access. In practice, this fires on virtually every object operation and produces overwhelming false positives. The pattern it is trying to catch (prototype pollution through user-controlled keys) is real, but the rule is too blunt to be useful.
  • detect-unsafe-regex -- Flags regular expressions that could cause catastrophic backtracking. The concept is valuable, but this rule has a high false positive rate. Use a dedicated ReDoS detection tool instead.

eslint-plugin-no-unsanitized

This plugin is maintained by Mozilla and specifically targets DOM-based cross-site scripting (XSS) in browser JavaScript. It flags direct assignment to innerHTML, outerHTML, and similar DOM properties, as well as calls to document.write() and insertAdjacentHTML().

For frontend applications, this plugin catches the most common XSS patterns. It is less useful for Node.js backend code.

eslint-plugin-security-node

A newer plugin focused specifically on Node.js server-side security patterns. It includes rules for detecting:

  • Hardcoded credentials and API keys
  • Insecure cryptographic algorithms
  • Missing rate limiting on authentication endpoints
  • Path traversal patterns in file system operations

Configuration Strategy

The biggest mistake teams make is enabling every security rule at maximum severity and then reverting when the build breaks. Instead, roll out security rules in phases.

Phase 1: Error on the obvious. Start with rules that have very low false positive rates: eval() detection, innerHTML assignment, hardcoded credentials. Set these to error so they block the build.

Phase 2: Warn on the probable. Add rules with moderate false positive rates as warnings. Let developers see them in their editors but do not block the build. Monitor how many warnings are legitimate versus false positives.

Phase 3: Tune and promote. After a few weeks, review the warning data. Promote rules with low false positive rates to errors. Disable or configure rules that produce too much noise. Add inline suppressions for known false positives with a comment explaining why.

// .eslintrc.js security configuration
module.exports = {
  plugins: ['security', 'no-unsanitized'],
  rules: {
    // Phase 1: Block the build
    'security/detect-eval-with-expression': 'error',
    'security/detect-non-literal-require': 'error',
    'no-unsanitized/method': 'error',
    'no-unsanitized/property': 'error',

    // Phase 2: Warn but do not block
    'security/detect-possible-timing-attacks': 'warn',
    'security/detect-non-literal-fs-filename': 'warn',

    // Disabled: too noisy
    'security/detect-object-injection': 'off',
  },
};

Custom Rules for Your Codebase

Generic security rules catch generic patterns. Your application probably has specific patterns that are dangerous in your context but would not be flagged by any generic rule.

Database query construction. If your team should always use parameterized queries, write a custom rule that flags string concatenation in functions that execute SQL. This is far more effective than a generic SQL injection rule because it knows your codebase specific patterns.

Authentication enforcement. If every API endpoint should go through your authentication middleware, write a rule that checks route definitions for the middleware call. This catches endpoints that were accidentally left unprotected.

Secret handling. If secrets should only be accessed through a specific configuration service, write a rule that flags direct process.env access for sensitive variable names. This ensures secrets flow through your approved path where they get proper audit logging.

Integration With CI/CD

Run ESLint security rules in both the IDE and CI/CD. IDE integration gives developers immediate feedback. CI/CD integration ensures nothing slips through.

In CI/CD, consider running security rules separately from style rules. A security scan that takes 30 seconds is fine. A full lint pass that takes five minutes and mostly reports formatting issues makes developers impatient.

Use the --rule flag or a separate ESLint config file to run only security rules in a dedicated CI step. This also lets you set different failure thresholds -- maybe formatting warnings are acceptable, but security warnings are not.

Limitations

ESLint is a static analysis tool that operates on AST patterns. It cannot track data flow across function boundaries, understand runtime behavior, or reason about complex control flow. It catches patterns, not vulnerabilities.

A require() call with a variable argument is flagged regardless of whether that variable is ever controlled by user input. A string comparison is flagged as a possible timing attack even if it is comparing configuration values that are not security-sensitive.

ESLint security rules are a first line of defense, not a comprehensive security analysis. They complement -- but do not replace -- dedicated SAST tools, security testing, and code review.

How Safeguard.sh Helps

Safeguard.sh integrates with your JavaScript and TypeScript supply chain to provide vulnerability detection that goes beyond what static linting can achieve. While ESLint catches patterns in your own code, Safeguard.sh monitors the dependencies that code relies on -- flagging vulnerable npm packages, detecting malicious updates, and generating SBOMs that give you complete visibility into your JavaScript supply chain.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.