Skip to main content
interlace
Plugin: node-securityRules

no-dynamic-require

Forbid require() calls with non-literal arguments

Forbid require() calls with non-literal arguments. This rule is part of eslint-plugin-node-security.

Quick Summary

AspectDetails
SeverityWarning (architecture)
Auto-Fix❌ No (requires architecture change)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForBundler optimization, static analysis

Rule Details

Dynamic require() calls prevent static analysis and break tree-shaking in bundlers.

Why This Matters

IssueImpactSolution
📦 Bundle sizeCan't tree-shakeStatic imports
🔍 Static analysisTools can't analyze depsLiteral paths
🔒 SecurityArbitrary module loadingExplicit imports

Examples

❌ Incorrect

const moduleName = getModuleName();
const mod = require(moduleName);  // Dynamic

const plugin = require(`./plugins/${name}`);  // Template literal

const handler = require(path.join(__dirname, name));  // Computed

✅ Correct

// Static requires
const mod = require('./module');

// Dynamic import (when truly needed)
const mod = await import(`./plugins/${name}`);

// Explicit mapping
const plugins = {
  a: require('./plugins/a'),
  b: require('./plugins/b'),
};
const plugin = plugins[name];

Configuration Examples

Basic Usage

{
  rules: {
    'node-security/no-dynamic-require': 'warn'
  }
}

Further Reading

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Variable References

Why: Static analysis cannot trace values stored in variables or passed through function parameters.

// ❌ 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

Why: Custom wrapper functions or aliased methods are not recognized by the rule.

// ❌ 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.

Cross-Module Data Flow

Why: ESLint rules analyze one file at a time. Values imported from other modules cannot be traced.

// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not tracked

Mitigation: Apply the same rule to imported modules. Use module boundaries and explicit exports.

⚙️ Options

OptionTypeDefaultDescription
allowContextsstring[][]Allow dynamic requires in specific contexts.
allowPatternsstring[][]Regex patterns matched against the source text of the require argument; a match suppresses the report.

Not a finding

The question is whether a specifier can change, not whether it is spelled as a literal. Resolution runs through ESLint's scope analysis, so all of these are silent:

CodeWhy it is silent
require(`b`)A template with no interpolation is a literal.
const d = 'debounce'; require(`lodash/${d}`)Every interpolated part resolves to a constant.
require(__dirname + '/utils')__dirname is where the module sits on disk, fixed at load.
require.resolve('eslint/package.json')A lookup against the dependency tree.

If it fires, the specifier reaches a name this file cannot resolve — a parameter, an import, a variable written more than once. That is genuinely unknowable here, which is exactly the case the rule exists for.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.