Interlace ESLint
ESLint Interlace
Secure CodingRules

detect-non-literal-fs-filename

Detects variable in filename argument of fs calls, which might allow an attacker to access anything on your system. This rule is part of [`eslint-plugin-secure-

Keywords: path traversal, CWE-22, security, ESLint rule, file system, fs module, directory traversal, file access, auto-fix, LLM-optimized, code security

Detects variable in filename argument of fs calls, which might allow an attacker to access anything on your system. This rule is part of eslint-plugin-secure-coding and provides LLM-optimized error messages with fix suggestions.

🚨 Security rule | 💡 Provides LLM-optimized guidance | ⚠️ Set to error in recommended

Quick Summary

AspectDetails
CWE ReferenceCWE-22 (Path Traversal)
SeverityHigh (security vulnerability)
Auto-Fix⚠️ Suggests fixes (manual application)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForNode.js applications, file processing systems, file upload handlers

Vulnerability and Risk

Vulnerability: Using non-literal (dynamic) values for filesystem operations (like opening, reading, or writing files) without strict validation allows users to control file paths.

Risk: Attackers can manipulate file paths to access files outside the intended directory (Path Traversal), overwriting critical system files, or disclosing sensitive information (like configuration files or source code).

Rule Details

This rule detects dangerous use of Node.js fs methods with dynamic paths that can lead to path traversal attacks (also known as directory traversal).

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-22 OWASP:A01 CVSS:7.5 | Path Traversal detected | HIGH [SOC2,PCI-DSS,HIPAA,ISO27001]
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-22 OWASP:A01 CVSS:7.5
Issue DescriptionSpecific vulnerabilityPath Traversal detected
Severity & ComplianceImpact assessmentHIGH [SOC2,PCI-DSS,HIPAA,ISO27001]
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

Configuration

OptionTypeDefaultDescription
allowLiteralsbooleanfalseAllow literal string paths
additionalMethodsstring[][]Additional fs methods to check

Examples

❌ Incorrect

// Path traversal - HIGH risk
const { readFile } = require('fs');
readFile(userPath, callback); // Attacker can access ../../../etc/passwd

// Directory traversal - HIGH risk
fs.readdir(userDir, callback); // Can list any directory

// File creation - MEDIUM risk
fs.writeFile(userFile, data, callback); // Can write anywhere

✅ Correct

// Safe file reading
const SAFE_UPLOADS_DIR = path.join(__dirname, 'uploads');
const safePath = path.join(SAFE_UPLOADS_DIR, path.basename(userFile));
fs.readFile(safePath, callback);

// Safe directory listing
const ALLOWED_DIRS = [path.join(__dirname, 'public')];
const resolvedDir = path.resolve(ALLOWED_DIRS[0], userSubDir);
if (resolvedDir.startsWith(ALLOWED_DIRS[0])) {
  fs.readdir(resolvedDir, callback);
}

// Safe file writing
const safeWritePath = path.join(SAFE_WRITES_DIR, path.basename(userFile));
fs.writeFile(safeWritePath, data, callback);

Path Traversal Prevention

Basic Protection

const path = require('path');

// ❌ Vulnerable
fs.readFile(userInput, callback);

// ✅ Protected
const safePath = path.resolve(SAFE_DIR, userInput);
if (!safePath.startsWith(SAFE_DIR)) {
  return callback(new Error('Invalid path'));
}
fs.readFile(safePath, callback);

Advanced Protection

function securePath(baseDir: string, userPath: string): string {
  const resolved = path.resolve(baseDir, userPath);

  // Check if resolved path is within base directory
  if (!resolved.startsWith(baseDir)) {
    throw new Error('Path traversal detected');
  }

  // Additional security: remove any remaining ..
  const normalized = path.normalize(resolved);
  if (normalized.includes('..')) {
    throw new Error('Invalid path segments');
  }

  return resolved;
}

Common Attack Vectors

AttackExample InputResult
Basic traversal../../../etc/passwdAccess system files
Windows traversal....\\....\\windows\\system32Access Windows files
Encoded traversal%2e%2e%2f%2e%2e%2fetc/passwdURL-encoded attack
Unicode traversal..\\u002f..\\u002fetc/passwdUnicode bypass

Method-Specific Guidance

File Reading (readFile, readFileSync)

  • Use path.basename() to strip directory components
  • Combine with safe base directory
  • Validate file extensions if needed

File Writing (writeFile, writeFileSync)

  • Same as reading, but consider separate write directories
  • Check disk space and file size limits
  • Validate file types on upload

Directory Operations (readdir, stat)

  • Always resolve and validate directory paths
  • Check directory existence and permissions
  • Consider rate limiting for directory listings

Stream Operations (createReadStream, createWriteStream)

  • Apply same path validation as regular file operations
  • Be careful with relative paths in streams
  • Validate stream destinations

Security Best Practices

Directory Structure

project/
├── uploads/        # User uploads (read-only from here)
├── public/         # Public files
├── temp/          # Temporary files
└── user-data/     # User-specific data

Path Validation

class SecurePath {
  constructor(private baseDir: string) {}

  resolve(userPath: string): string {
    const resolved = path.resolve(this.baseDir, userPath);

    // Security checks
    if (!resolved.startsWith(this.baseDir)) {
      throw new Error('Path traversal attempt');
    }

    // Remove dangerous segments
    const normalized = path.normalize(resolved);
    if (normalized.includes('..') || normalized.includes('\0')) {
      throw new Error('Invalid path');
    }

    return resolved;
  }
}

Migration Guide

Phase 1: Discovery

{
  rules: {
    'secure-coding/detect-non-literal-fs-filename': 'warn'
  }
}

Phase 2: Implementation

// Add security utilities
const SECURE_PATHS = {
  uploads: path.join(__dirname, 'uploads'),
  public: path.join(__dirname, 'public'),
  temp: path.join(__dirname, 'temp')
};

// Replace unsafe calls
fs.readFile(userPath) → fs.readFile(securePath.resolve(userPath))

Phase 3: Testing

// Test path traversal attempts
const attacks = [
  '../../../etc/passwd',
  '..\\..\\windows\\system32',
  '....//....//etc/passwd',
];

for (const attack of attacks) {
  expect(() => securePath.resolve(attack)).toThrow();
}

Comparison with Alternatives

Featuredetect-non-literal-fs-filenameeslint-plugin-securityeslint-plugin-node
Path Traversal Detection✅ Yes⚠️ Limited⚠️ Limited
CWE Reference✅ CWE-22 included⚠️ Limited⚠️ Limited
LLM-Optimized✅ Yes❌ No❌ No
ESLint MCP✅ Optimized❌ No❌ No
Fix Suggestions✅ Detailed⚠️ Basic⚠️ Basic

Known False Negatives

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

Path from Variable

Why: Path strings from variables not traced.

// ❌ NOT DETECTED - Path from variable
const filePath = userInput;
fs.readFile(filePath);

Mitigation: Validate and sanitize all paths.

Indirect Path Construction

Why: Complex path building not analyzed.

// ❌ NOT DETECTED - Indirect
const path = buildPath(base, userInput);
fs.readFile(path);

Mitigation: Use path whitelisting.

Custom FS Wrappers

Why: FS wrappers not recognized.

// ❌ NOT DETECTED - Wrapper
fileManager.read(userPath);

Mitigation: Apply rule to wrapper implementations.

Further Reading

On this page