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
| Aspect | Details |
|---|---|
| Severity | Warning (architecture) |
| Auto-Fix | ❌ No (requires architecture change) |
| Category | Security |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Bundler optimization, static analysis |
Rule Details
Dynamic require() calls prevent static analysis and break tree-shaking in bundlers.
Why This Matters
| Issue | Impact | Solution |
|---|---|---|
| 📦 Bundle size | Can't tree-shake | Static imports |
| 🔍 Static analysis | Tools can't analyze deps | Literal paths |
| 🔒 Security | Arbitrary module loading | Explicit 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'
}
}Related Rules
no-commonjs- Prevent CommonJS usageno-unsafe-dynamic-require- Security-focused variant
Further Reading
- Webpack Dynamic Imports - Code splitting guide
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 trackedMitigation: 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 trackedMitigation: Apply the same rule to imported modules. Use module boundaries and explicit exports.
⚙️ Options
| Option | Type | Default | Description |
|---|---|---|---|
allowContexts | string[] | [] | Allow dynamic requires in specific contexts. |
allowPatterns | string[] | [] | 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:
| Code | Why 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.