java-topology/whitepaper/outreach/build-tools.md
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

5.9 KiB
Raw Blame History

Build Tools & Package Managers — CWE-407 Disclosure Brief

2026-03-26 · Confidential pre-disclosure

Finding

Six O(n²) defects across Apache Maven, CMake, npm arborist, pip/distlib, and Composer. All patched in our working branch. The same algorithmic defect pattern — linear-scan membership tests inside graph traversal loops — appears independently in every major build tool ecosystem. This is a systemic CWE-407 pattern, not an isolated incident.

This brief covers all six tools together because the fix strategy is identical across all of them.

The Defects

Maven — 3 sites (PATCHED)

  • maven-0001/0002: project/Graph.java:63 and internal/impl/Graph.java:63ArrayList.remove() in removeEdge(), called during cycle detection. ArrayList.remove() scans from index 0 to find the element before removing it. O(E) per removal, O(E²) total over the graph traversal. The defect exists in two parallel Graph implementations.
  • maven-0003: project/Graph.java:102LinkedList.lastIndexOf() in the cycle reporter. Scans the full list to find the last occurrence of a node. O(V) per call in a V-node cycle report.

Fix: Replace ArrayList<String> edge storage with LinkedHashSet<String>. Preserves insertion order (needed for deterministic output), provides O(1) contains() and remove().

CMake — 1 site (PATCHED)

  • cmake-0001: cmComputeLinkDepends.cxx lines 521, 1167, 1363 — std::find on std::vector group membership in link dependency computation. Three call sites in the same algorithm. O(G) per check, O(G²) total where G = link group count.

Fix: Replace the group membership vectors with std::unordered_set. CMake already uses std::unordered_map elsewhere in the same file — the pattern is established.

npm arborist — 1 site (PATCHED)

  • npm-0002: can-place-dep.js:370peerPath.includes() in peer dependency conflict resolution. peerPath is a plain Array. O(P) per call inside the peer dependency walk. O(P²) total where P = peer dependency chain length.

Fix: Replace peerPath array membership tests with Set.has(). JavaScript Set provides O(1) has(). The array is retained for ordered traversal; the Set is added for membership.

pip / distlib — 1 site (PATCHED)

  • distlib-0001: util.py:1180,1204successor in stack in Tarjan's SCC algorithm for package dependency resolution. stack is a list. O(V) per check, O(V²) total where V = package count.

Fix: Maintain a companion stack_set = set() alongside the list. The list preserves SCC output order (required by Tarjan); the set provides O(1) membership for the successor in stack check. This is the standard Tarjan implementation pattern.

Composer / PHP — 2 sites (PATCHED)

  • composer-0001: RepositoryUtils.php:46in_array() in filterRequiredPackages(). Scans the required packages list for each candidate package. O(P²) where P = package count.
  • composer-0002: InstalledRepository.php:128-180in_array() × 4 in getDependents(). Four separate linear scans over the installed package list in a single method.

Fix: Build a $seen = [] keyed array (PHP associative array, O(1) hash lookup) for membership testing. Replace all in_array($pkg, $list) with isset($seen[$pkg]).

Complexity Proof

The pattern is identical across all six tools:

  1. A graph traversal loop runs over V vertices or E edges
  2. Inside the loop, a membership test runs against a growing list/array/vector
  3. The membership test is O(n) — it scans from the start every time
  4. Total cost: O(V²) or O(E²) instead of O(V) or O(E)

For a project with 200 direct dependencies and 1,000 transitive dependencies:

Tool Hot path Defect cost Fixed cost
Maven removeEdge during cycle detect O(E²) ≈ 1,000,000 ops O(E) ≈ 1,000 ops
CMake link group membership O(G²) O(G)
npm peer dep conflict check O(P²) O(P)
pip Tarjan stack check O(V²) ≈ 1,000,000 ops O(V) ≈ 1,000 ops
Composer package filter + dependents O(P²) × 5 O(P)

Impact

These defects fire during the dependency resolution and cycle detection phases of every build and install invocation. The cost scales with:

  • Number of direct dependencies
  • Number of transitive dependencies
  • Depth of dependency graph

Projects with 100+ transitive dependencies — which describes every non-trivial enterprise project — see measurable build-time inflation from these defects. The inflation is quadratic: doubling the dependency count quadruples the wasted work.

For CI/CD pipelines running hundreds of builds per day, this is compounded energy and compute cost. For local developer workflows, this is seconds-to-minutes of unnecessary wait on every mvn install, cmake .., npm install, pip install, and composer install.

The Fix

All fixes follow the same pattern: replace linear-scan collection membership tests with O(1) hash-based lookup.

The fix in every case is 2-5 lines of code:

  • Add a companion Set / unordered_set / HashSet / set() / keyed array
  • Insert into it when inserting into the original collection
  • Replace the linear scan with a hash lookup

The original collection is retained where ordered traversal or ordered output is required. Only the membership test is changed.

We have working patches for all six tools available on request.

What We Ask

  1. Confirm receipt and assign a tracker reference for your project.
  2. Evaluate severity — these are performance defects (CWE-407), not memory safety issues. CVE assignment is at your discretion; we defer to each project's security policy.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit each team individually in the public disclosure. Preferred acknowledgment format welcome.

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