no-unsafe-dynamic-require
Disallows dynamic `require()` calls with non-literal arguments that could lead to security vulnerabilities. This rule is part of [`eslint-plugin-secure-coding`]
Keywords: require, code injection, security, ESLint rule, dynamic require, path traversal, arbitrary code execution, module loading, auto-fix, LLM-optimized, code security
Disallows dynamic require() calls with non-literal arguments that could lead to security vulnerabilities. This rule is part of eslint-plugin-secure-coding and provides LLM-optimized error messages with fix suggestions.
⚠️ This rule warns by default in the recommended config.
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-706 (Incorrect Resolution) |
| Severity | Warning (security best practice) |
| Auto-Fix | ⚠️ Suggests fixes (manual application) |
| Category | Security |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Node.js applications, plugin systems, dynamic module loading |
Vulnerability and Risk
Vulnerability: Dynamic require() (or "dynamic imports") allows an application to load modules based on variable input. If this input is controlled by a user, it can be manipulated.
Risk: Attackers can manipulate the input to load malicious code (Remote Code Execution) or read sensitive files from the server's filesystem that were not intended to be exposed (Information Disclosure).
Rule Details
Dynamic require() calls with user-controlled input can lead to:
- Arbitrary code execution: Attackers loading malicious modules
- Path traversal attacks: Reading files outside intended directories
- Data exfiltration: Accessing sensitive configuration files
Examples
❌ Incorrect
// User input in require
const userModule = require(req.params.moduleName);
// String concatenation
const config = require('./configs/' + environment);
// Template literals with variables
const plugin = require(`./plugins/${pluginName}`);
// Variable paths
const modulePath = getUserInput();
const module = require(modulePath);✅ Correct
// Static require
const config = require('./config/production');
// Whitelist approach
const allowedModules = {
config: './config',
utils: './utils',
};
const modulePath = allowedModules[userInput];
if (modulePath) {
const module = require(modulePath);
}
// Import maps (hardcoded)
const modules = {
dev: require('./config/dev'),
prod: require('./config/prod'),
};
const config = modules[environment];
// ES6 dynamic import with validation
async function loadModule(name: string) {
if (!/^[a-z0-9-]+$/.test(name)) {
throw new Error('Invalid module name');
}
return await import(`./plugins/${name}`);
}Configuration
{
rules: {
'secure-coding/no-unsafe-dynamic-require': ['error', {
allowDynamicImport: true // Allow dynamic import() but warn on require()
}]
}
}Options
| Option | Type | Default | Description |
|---|---|---|---|
allowDynamicImport | boolean | false | Allow ES6 dynamic import() (generally safer than require()) |
Allow Dynamic Import
{
rules: {
'secure-coding/no-unsafe-dynamic-require': ['error', {
allowDynamicImport: true
}]
}
}// ❌ Still flags require()
const module = require(modulePath);
// ✅ Allows dynamic import()
const module = await import(`./plugins/${pluginName}`);Error Message Format
This rule provides LLM-optimized error messages:
🚨 CWE-407 | Unsafe dynamic require detected | CRITICAL
Fix: Use whitelist: const allowed = { 'plugin1': './plugins/plugin1' }; require(allowed[userInput]) | https://nodejs.org/en/docs/guides/security/Why this format?
- Structured - AI assistants can parse and understand
- Actionable - Shows both problem and solution
- Educational - Includes security best practices
- Auto-fixable - AI can apply the fix automatically
When Not To Use It
| Scenario | Recommendation |
|---|---|
| Build Scripts | Disable for build scripts with trusted sources |
| Sandboxed Plugins | Disable for properly sandboxed plugin systems |
| Validated Imports | Disable for dynamic imports with validation/whitelisting |
Comparison with Alternatives
| Feature | no-unsafe-dynamic-require | eslint-plugin-security | eslint-plugin-node |
|---|---|---|---|
| Dynamic Require Detection | ✅ Yes | ⚠️ Limited | ⚠️ Limited |
| LLM-Optimized | ✅ Yes | ❌ No | ❌ No |
| ESLint MCP | ✅ Optimized | ❌ No | ❌ No |
| Fix Suggestions | ✅ Detailed | ⚠️ Basic | ⚠️ Basic |
| Dynamic Import Support | ✅ Configurable | ❌ No | ⚠️ Limited |
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Values from Variables
Why: Values stored in variables are not traced.
// ❌ NOT DETECTED - Value from variable
const value = userInput;
dangerousOperation(value);Mitigation: Validate all user inputs.
Wrapper Functions
Why: Custom wrappers not recognized.
// ❌ NOT DETECTED - Wrapper
myWrapper(userInput); // Uses dangerous API internallyMitigation: Apply rule to wrapper implementations.
Dynamic Invocation
Why: Dynamic calls not analyzed.
// ❌ NOT DETECTED - Dynamic
obj[method](userInput);Mitigation: Avoid dynamic method invocation.
Further Reading
- Node.js Security Best Practices - Node.js security guidelines
- OWASP Path Traversal - Path traversal attack guide
- Arbitrary Code Execution Risks - Code injection prevention
- ESLint MCP Setup - Enable AI assistant integration
Related Rules
no-sql-injection- Prevents SQL injection vulnerabilitiesdetect-eval-with-expression- Prevents code injection via eval()detect-non-literal-fs-filename- Prevents path traversal
Version
This rule is available in eslint-plugin-secure-coding v0.0.1+
no-unsafe-deserialization
Detects unsafe deserialization of untrusted data. This rule is part of [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding
no-unsafe-prompt-concatenation
Prevent prompt injection via direct string concatenation of user input into LLM prompts.