Skip to content

fix: detect missing packages CN-612#744

Merged
parker-snyk merged 3 commits intomainfrom
n8n-zeroday
Jan 9, 2026
Merged

fix: detect missing packages CN-612#744
parker-snyk merged 3 commits intomainfrom
n8n-zeroday

Conversation

@parker-snyk
Copy link
Copy Markdown
Contributor

@parker-snyk parker-snyk commented Jan 8, 2026

  • Ready for review
  • Follows CONTRIBUTING rules
  • Reviewed by Snyk internal team

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

What does this PR do?

Add support for detecting dependencies in pnpm-based Node.js projects
when scanning container images.

Problem:
When scanning container images containing pnpm projects (like n8n),
dependencies were not being detected. This is because pnpm stores
packages in a .pnpm/ virtual store directory structure:

node_modules/.pnpm/@/node_modules//package.json

The existing snyk-resolve-deps library expects packages directly at
node_modules// and doesn't understand pnpm's directory layout.

Solution:
Added tryBuildDepGraphFromPnpmStore() as a fallback that:

  1. Detects when .pnpm/ package.json files are present
  2. Parses them directly to extract package names and versions
  3. Builds a dependency graph from those packages

This runs before snyk-resolve-deps and only activates for pnpm projects.

@parker-snyk parker-snyk requested a review from a team as a code owner January 8, 2026 20:05
@parker-snyk parker-snyk requested a review from SteveShani January 8, 2026 20:05
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incorrect dependency graph (Version Flattening)

The logic builds a map keyed only by package name and retains only the highest version found in the .pnpm store. Since pnpm relies on installing multiple versions of dependencies to handle conflicts and isolation, this flattening will cause the generated dependency graph to be incorrect. Dependents requiring older versions will be linked to the newer version in the map, potentially masking vulnerabilities or creating invalid dependency chains. The map should support multiple versions per package name.

const existing = pnpmPackages.get(pkg.name);
if (!existing || pkg.version > existing.version) {
  pnpmPackages.set(pkg.name, {
    version: pkg.version,
    dependencies: { ...pkg.dependencies, ...pkg.optionalDependencies },
  });
}
Arbitrary recursion limit

The recursion limit of 5000 visited nodes is hardcoded. Large projects or monorepos with extensive dependency trees might exceed this limit, resulting in a silently truncated graph. Consider making this configurable or logging a warning if the limit is reached.

if (visited.size < 5000) {
  addDependencies(nodeId, pkgInfo.dependencies);
}
Naive Dependency Resolution

Dependencies are wired by looking up the package name in the flattened pnpmPackages map (lines 328-330). This ignores semver requirements defined in the package's dependencies list and the specific resolution path pnpm would have taken (often involving nested node_modules or symlinks). Linking to "whatever version is in the map" creates a potentially inaccurate graph compared to the actual installed tree.

for (const depName of Object.keys(deps)) {
  const pkgInfo = pnpmPackages.get(depName);
  if (!pkgInfo) {
📚 Repository Context Analyzed

This review considered 13 relevant code sections from 4 files (average relevance: 0.88)

Comment thread lib/analyzer/applications/node.ts Outdated
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Flat Dependency Graph Structure

The new tryBuildDepGraphFromPnpmStore function adds all packages found in the .pnpm directory as direct dependencies of the root node (lines 240). This creates a "flat" dependency graph, losing the actual dependency hierarchy.

This has implications for vulnerability remediation advice, as transitive dependencies will appear as direct dependencies. Users might be advised to upgrade packages that are not listed in their package.json. Consider if it's possible to reconstruct the tree or if this trade-off is intentional for this specific analyzer.

builder.addPkgNode({ name: pkg.name, version: pkg.version }, nodeId);
builder.connectDep(builder.rootNodeId, nodeId);
Redundant Path Derivation

The code derives rootManifestPath by performing a regex match on a file path (lines 198-202). Since project (the directory path) is passed as an argument to the function and used to retrieve projectFiles, rootManifestPath could likely be constructed more safely and simply using path.join(project, "package.json"). This assumes project correctly points to the root directory containing the node_modules.

const pnpmMatch = pnpmPackageJsons[0].match(/^(.+?)\/node_modules\/\.pnpm\//);
if (!pnpmMatch) {
  return null;
}
const rootManifestPath = path.posix.join(pnpmMatch[1], "package.json");
📚 Repository Context Analyzed

This review considered 12 relevant code sections from 5 files (average relevance: 0.87)

@tyler-catlin
Copy link
Copy Markdown
Contributor

This functionality will essentially give us a flat dependency graph for these pnpm dependencies, because we have no point of reference for where they are coming from, only that they are there. I'll think on this and see if there is a way for us to recreate the actual dep tree, but otherwise I think this is an acceptable tradeoff for now.

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Windows Path Incompatibility

The use of hardcoded forward slashes in string includes and regex matchers (lines 191, 198), combined with path.posix.join (line 202), will likely cause this logic to fail on Windows systems where file paths use backslashes. The repository context shows that path (and thus OS-specific separators) is used elsewhere (e.g., lib/analyzer/applications/node.ts:458). Ensure paths are normalized or handled using path.sep to support cross-platform execution.

const pnpmPackageJsons = Array.from(projectFiles).filter(
  (f) => f.includes("/node_modules/.pnpm/") && f.endsWith("/package.json"),
);
if (pnpmPackageJsons.length === 0) {
  return null;
}

// Find the root package.json (parent of node_modules/.pnpm)
const pnpmMatch = pnpmPackageJsons[0].match(/^(.+?)\/node_modules\/\.pnpm\//);
if (!pnpmMatch) {
  return null;
}
const rootManifestPath = path.posix.join(pnpmMatch[1], "package.json");
Flat Dependency Graph Topology

The current implementation connects all packages found in the .pnpm store directly to the root node (lines 245-246). This creates a flat dependency graph where every package appears as a direct dependency, causing the loss of the actual dependency tree structure (transitive relationships). While this enables detection, it misrepresents the project structure compared to standard graph generation. Consider whether it is feasible to parse the dependencies from each package's manifest to reconstruct the proper tree.

builder.addPkgNode({ name: pkg.name, version: pkg.version }, nodeId);
builder.connectDep(builder.rootNodeId, nodeId);
Relaxed Linting Rules

The PR disables variable-name and no-unnecessary-initializer rules globally. Disabling linting rules is generally discouraged unless strictly necessary. Inspect if the new code introduced violations (e.g. redundant initializations) that can be fixed instead of relaxing the project-wide configuration.

"variable-name": false,
"no-unnecessary-initializer": false
📚 Repository Context Analyzed

This review considered 17 relevant code sections from 6 files (average relevance: 0.84)

@parker-snyk parker-snyk enabled auto-merge (squash) January 9, 2026 14:54
@parker-snyk parker-snyk merged commit aa97861 into main Jan 9, 2026
17 checks passed
@parker-snyk parker-snyk deleted the n8n-zeroday branch January 9, 2026 15:09
@parker-snyk parker-snyk changed the title fix: detect missing packages fix: detect missing packages CN-612 Jan 9, 2026
@snyksec
Copy link
Copy Markdown

snyksec commented Jan 9, 2026

🎉 This PR is included in version 8.16.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants