Interlace ESLint
ESLint Interlace
Secure CodingRules

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

AspectDetails
CWE ReferenceCWE-706 (Incorrect Resolution)
SeverityWarning (security best practice)
Auto-Fix⚠️ Suggests fixes (manual application)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForNode.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

OptionTypeDefaultDescription
allowDynamicImportbooleanfalseAllow 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

ScenarioRecommendation
Build ScriptsDisable for build scripts with trusted sources
Sandboxed PluginsDisable for properly sandboxed plugin systems
Validated ImportsDisable for dynamic imports with validation/whitelisting

Comparison with Alternatives

Featureno-unsafe-dynamic-requireeslint-plugin-securityeslint-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 internally

Mitigation: 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

Version

This rule is available in eslint-plugin-secure-coding v0.0.1+

On this page