java-topology/whitepaper/outreach/npm.md
russell@unturf.com 652608142a feat: close outreach doc gap — 276 docs (batches 11-16)
All projects with patches now have outreach docs. 276 new docs covering
CWE-407, CWE-312, CWE-362 across C, C++, Java, Python, Go, Rust, C#,
PHP, Ruby, JavaScript, Dart, Erlang, R, and more.

Outreach gap: 276 -> 0.
2026-04-15 13:57:42 -04:00

2.6 KiB
Raw Blame History

npm — CWE-407 Disclosure Brief (npm-0002)

2026-04-13 · Patch available — awaiting upstream merge

Finding

One O(N²) defect in npm's peer dependency cycle detection. Patched. CanPlaceDep.canPlacePeers() uses Array.includes() on the peerPath array for cycle detection during dependency resolution, producing quadratic behavior in deep peer dependency graphs.

The Defect

npm-0002 (PATCHED — HIGH): lib/can-place-dep.js:365

// In canPlacePeers() — fires per peer dependency edge:
const peerPath = [...this.peerPath, this.dep]
for (const peerEdge of this.dep.edgesOut.values()) {
    if (!peerEdge.peer || !peerEdge.to || peerPath.includes(peerEdge.to)) {  // O(D) scan
        continue
    }
    // ...
}

peerPath is a plain Array. Array.includes() is O(D) where D = depth of the peer dependency path. Each recursive canPlacePeers() call creates a copy of the path and scans it. With D depth and P peer edges, total cost: O(D² × P).

Complexity Proof

At D=50 depth, P=10 peer edges per level:

  • Defective: 50 × 50 × 10 = 25,000 reference comparisons per tree branch
  • Fixed: 50 × 1 × 10 = 500 Set lookups per tree branch
  • 50× op reduction per peer resolution branch.

Impact

npm is the world's most used package manager, serving millions of JavaScript developers. npm install resolves peer dependencies for every package in the tree. Projects with deep peer dependency chains (React component libraries, monorepos with many workspace packages) trigger quadratic cycle detection. This slows down npm install for large projects.

The Fix

Add a shared Set for O(1) cycle detection with DFS backtracking:

// Before: O(D) Array.includes per check
peerPath.includes(peerEdge.to)

// After: O(1) Set.has per check
this.peerPathSet = peerPathSet || new Set(peerPath)
this.peerPathSet.has(peerEdge.to)
// Backtrack after recursion:
this.peerPathSet.delete(this.dep)

Patch

Fix available: defects/npm/patch/npm-0002-peerpath-set.patch

Touches lib/can-place-dep.js. Adds shared peerPathSet with DFS backtracking pattern. 50× speedup at depth=50.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a GitHub issue reference (npm/cli).
  2. Assess severity — fires during npm install peer dependency resolution.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the npm team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.