Skip to content

fix: prevent temp directory leak in node-modules-utils#788

Open
parker-snyk wants to merge 2 commits intomainfrom
CN-1040-fix-temp-dir-leaks
Open

fix: prevent temp directory leak in node-modules-utils#788
parker-snyk wants to merge 2 commits intomainfrom
CN-1040-fix-temp-dir-leaks

Conversation

@parker-snyk
Copy link
Copy Markdown
Contributor

What does this PR do?

  • Fixes a variable shadowing bug that caused temporary directories to leak when node_modules persistence failed
  • Adds type annotations to the createFile function
Problem

persistNodeModules() creates a temporary directory to write extracted node_modules files for dependency resolution. The function had a scoping bug:

async function persistNodeModules(...): Promise<ScanPaths> {
  const tmpDir: string = "";           // outer scope — always empty
  const tempProjectRoot: string = "";  // outer scope — always empty

  if (!modules || modules.size === 0) {
    return { tempDir: tmpDir, tempProjectPath: tempProjectRoot };  // OK, legitimately empty
  }

  try {
    // BUG: This creates NEW block-scoped variables that shadow the outer ones
    const { tmpDir, tempProjectRoot } = await createTempProjectDir(project);
    //     ^^^^^^  ^^^^^^^^^^^^^^^^^ — these are different variables!

    await saveOnDisk(tmpDir, modules, filePathToContent);  // uses inner vars ✓
    // ... more work with inner vars ...
    return result;  // uses inner vars ✓
  } catch (error) {
    return {
      tempDir: tmpDir,            // uses OUTER empty string ✗
      tempProjectPath: tempProjectRoot,  // uses OUTER empty string ✗
    };
  }
}

If createTempProjectDir succeeds (creating a real directory on disk) but saveOnDisk or any later step throws, the catch block returns the outer empty strings instead of the actual temp directory path. The caller checks:

if (!tempDir) {   // "" is falsy → skips cleanup
  continue;
}

This skips cleanup, leaving the temp directory orphaned on disk. Over many scans (especially in CI), these leaked directories accumulate.

Fix

Changed from const destructuring (which creates new block-scoped variables) to assigning to let variables declared in the function scope:

let tmpDir = "";
let tempProjectRoot = "";

try {
  const created = await createTempProjectDir(project);
  tmpDir = created.tmpDir;              // updates outer scope
  tempProjectRoot = created.tempProjectRoot;  // updates outer scope
  // ...
} catch (error) {
  return {
    tempDir: tmpDir,            // now has real path if dir was created
    tempProjectPath: tempProjectRoot,
  };
}
Also

Added string type annotations to createFile(filePath, fileContent) parameters (were implicit any).

Risk

Low. The fix changes variable scoping to prevent shadowing. The happy path behavior is identical. The error path now correctly returns the temp directory path so the caller can clean it up.

How should this be manually tested?

  • Verify node_modules scanning works for images with package.json + node_modules
  • Verify temp directories are cleaned up after scanning
  • Verify empty module directories are handled gracefully

Any background context you want to provide?

What are the relevant tickets?

https://snyksec.atlassian.net/browse/CN-1040

Additional questions

The persistNodeModules function had a scoping bug that could leak
temporary directories on disk.

The outer variables tmpDir and tempProjectRoot were initialized as
empty strings (lines 79-80). Inside the try block, createTempProjectDir
created a real temp directory and destructured the result into NEW
block-scoped variables with the same names (line 92: const { tmpDir,
tempProjectRoot } = ...). If saveOnDisk or any subsequent step threw,
the catch block returned the OUTER empty strings — not the temp
directory that was actually created. The caller, seeing empty strings,
would skip cleanup (line 116: if (!tempDir) continue), leaving the
temp directory orphaned.

Fix: removed the shadowing const destructure and instead assign to
the outer let variables, so the catch block always returns the actual
temp directory path for cleanup.

Also added type annotations to createFile() parameters (filePath,
fileContent were implicit any).
parker-snyk added a commit that referenced this pull request Apr 9, 2026
Adds unit tests for the persistNodeModules function to ensure that
temporary directories are not leaked when an error occurs during
disk write operations, verifying the fix implemented for PR #788.
parker-snyk added a commit that referenced this pull request Apr 9, 2026
Adds unit tests for the persistNodeModules function to ensure that
temporary directories are not leaked when an error occurs during
disk write operations.

Verifying the fix implemented for PR #788.
@parker-snyk parker-snyk force-pushed the CN-1040-fix-temp-dir-leaks branch from 838fe07 to f5d9543 Compare April 9, 2026 19:57
Adds unit tests for the persistNodeModules function to ensure that
temporary directories are not leaked when an error occurs during
disk write operations.
@parker-snyk parker-snyk force-pushed the CN-1040-fix-temp-dir-leaks branch from f5d9543 to ffe2547 Compare April 9, 2026 19:58
@parker-snyk parker-snyk marked this pull request as ready for review April 9, 2026 19:59
@parker-snyk parker-snyk requested a review from a team as a code owner April 9, 2026 19:59
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected
📚 Repository Context Analyzed

This review considered 8 relevant code sections from 7 files (average relevance: 0.92)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants