# UNDF: UNDF-2026-000000189 diff --git a/lib/can-place-dep.js b/lib/can-place-dep.js index 1a3ccff..0708321 100644 --- a/lib/can-place-dep.js +++ b/lib/can-place-dep.js @@ -61,6 +61,7 @@ class CanPlaceDep { preferDedupe, parent = null, peerPath = [], + peerPathSet = null, explicitRequest = false, } = options @@ -95,6 +96,10 @@ class CanPlaceDep { // preventing cycles when we check peer sets this.peerPath = peerPath + // CWE-407 fix: shared Set for O(1) peerPath membership tests. + // Initialized once at the root; child CPDs receive the parent's reference. + // canPlacePeers() adds/removes this.dep using a backtracking DFS pattern. + this.peerPathSet = peerPathSet || new Set(peerPath) // we always prefer to dedupe peers, because they are trying // a bit harder to be singletons. this.preferDedupe = !!preferDedupe || edge.peer @@ -365,9 +370,12 @@ class CanPlaceDep { // TODO: represent peerPath in ERESOLVE error somehow? const peerPath = [...this.peerPath, this.dep] + // CWE-407 fix: use shared peerPathSet for O(1) cycle detection. + // Add this.dep now; children share the same Set reference; backtrack after. + this.peerPathSet.add(this.dep) let sawConflict = false for (const peerEdge of this.dep.edgesOut.values()) { - if (!peerEdge.peer || !peerEdge.to || peerPath.includes(peerEdge.to)) { + if (!peerEdge.peer || !peerEdge.to || this.peerPathSet.has(peerEdge.to)) { continue } const peer = peerEdge.to @@ -381,6 +389,7 @@ class CanPlaceDep { parent: this, edge: peerEdge, peerPath, + peerPathSet: this.peerPathSet, // always place peers in preferDedupe mode preferDedupe: true, }) @@ -396,6 +405,9 @@ class CanPlaceDep { sawConflict = true } } + // Backtrack: remove this.dep from the shared peerPathSet so the parent's + // subsequent peers are checked against the correct path state. + this.peerPathSet.delete(this.dep) this._canPlacePeers = sawConflict ? CONFLICT : state return this._canPlacePeers