java-topology/whitepaper/outreach/maven.md
russell@unturf.com c24246e2e2 feat: add 5 outreach docs (33 defects) + mastodon CWE-1333 benchmark
Outreach docs (unblock intel page generation):
- kdenlive: 10 defects (8 CWE-407 + 1 CWE-362 + 1 keyframe), C++
- libreoffice: 5 defects (Writer, Calc, SFX, Impress), C++
- maven: 7 defects (graph, lifecycle, sort-by-indexOf), Java
- cpython: 7 defects (pkgutil, codegen, mock, pmerge MRO, pydoc), C/Python
- blender: 4 defects (node runtime, USD skel, shader, anim), C++

Mastodon CWE-1333 benchmark:
- test_mastodon_cwe1333.rb: validates (.+\.)? -> ([^@]+\.)? fix
  eliminates O(2^N) backtracking in email validator
2026-04-13 14:03:16 -04:00

6.4 KiB

Apache Maven — CWE-407 Disclosure Brief

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

Finding

Seven algorithmic complexity defects in Apache Maven across dependency graph operations, lifecycle calculation, build plan logging, reactor failure cascading, and plugin group management. All patched. Defects affect Maven Core, the build engine used by millions of Java projects worldwide.

The Defects

maven-0001 (PATCHED — HIGH): impl/maven-core/.../Graph.java — Vertex children/parents

// Two identical Graph.java files (internal API + project API)
final List<Vertex> children = new ArrayList<>();
final List<Vertex> parents = new ArrayList<>();
// ArrayList.add/remove/contains are O(N) — used in topological sort, cycle detection

Vertex.children and Vertex.parents use ArrayList, making add(), remove(), and contains() O(N). These fire during dependency graph construction and topological sorting. Fix: LinkedHashSet for O(1) operations with preserved insertion order.

maven-0001-lifecycle (PATCHED — MEDIUM): DefaultLifecycleExecutionPlanCalculator.java

// O(N) linear scan per lifecycle check
if (List.of(DefaultLifecycles.STANDARD_LIFECYCLES).contains(lifecycle.getId())) {

List.of().contains() allocates a new List and performs a linear scan on every invocation of calculateLifecycleMappings(). Fix: precompute a static final Set<String> for O(1) lookup.

maven-0003 (PATCHED — HIGH): impl/maven-core/.../Graph.java — visitCycle()

// O(N) lastIndexOf on LinkedList during cycle detection
int pos = cycle.lastIndexOf(v.label);
List<String> ret = cycle.subList(pos, cycle.size());

cycle.lastIndexOf(v.label) is O(N) on a LinkedList. Called inside the DFS cycle detection loop, making total cost O(N^2) for pathological dependency graphs. Fix: maintain a parallel HashMap<String, Integer> for O(1) label-to-index lookup.

maven-0004 (PATCHED — HIGH): DefaultGraphBuilder.java — trimProjectsToRequest/trimSelectedProjects/includeAlsoMakeTransitively

// O(N^2 log N) sort — indexOf is O(N) per comparison
List<MavenProject> sortedProjects = graph.getSortedProjects();
result.sort(comparing(sortedProjects::indexOf));

sortedProjects::indexOf is O(N) per comparison, called O(N log N) times by sort(), giving O(N^2 log N) total. Appears in three separate methods. Fix: build an IdentityHashMap<MavenProject, Integer> order map once, sort by map lookup.

maven-0005 (PATCHED — MEDIUM): BuildPlanLogger.java — log method

// O(N^2) sort — indexOf is O(N) per comparison
.sorted(Comparator.comparingInt(plan.sortedNodes()::indexOf))

Same pattern as maven-0004 but in the build plan logger. plan.sortedNodes()::indexOf is O(N) per step. Fix: build IdentityHashMap index once.

maven-0006 (PATCHED — MEDIUM): ReactorManager.java — blackList

// ArrayList.contains() is O(N) in recursive DFS cascade
private List<String> blackList = new ArrayList<>();
if (!blackList.contains(id)) {
    blackList.add(id);
    // recursive DFS over dependents
}

blackList uses ArrayList with O(N) contains() in a recursive DFS over dependent projects during failure cascading. Total cost: O(N^2) for N projects. Fix: HashSet for O(1) membership.

maven-0007 (PATCHED — MEDIUM): DefaultMavenExecutionRequest.java — pluginGroups

// ArrayList.contains() is O(G) per addPluginGroup call — O(G^2) for batch add
private List<String> pluginGroups;  // ArrayList
if (!getPluginGroups().contains(pluginGroup)) {
    getPluginGroups().add(pluginGroup);
}

addPluginGroups() calls addPluginGroup() once per group, each doing ArrayList.contains() at O(G). Total: O(G^2) for G plugin groups. Fix: LinkedHashSet preserves insertion order with O(1) add/contains.

Complexity Proof

maven-0001 (Graph Vertex): At N=200 modules:

  • Defective: O(N) per add/contains on children/parents during topo sort
  • Fixed: O(1) per operation with LinkedHashSet
  • Up to 200x op reduction per graph operation

maven-0004 (sort by indexOf): At N=200 reactor modules:

  • Defective: 200 * 200 * log(200) ~ 300,000 comparisons per sort
  • Fixed: 200 * log(200) ~ 1,500 comparisons
  • 200x op reduction

Impact

Apache Maven is the dominant build tool for Java, used by millions of projects including most enterprise Java applications, Android development, and open-source foundations. maven-0001 and maven-0004 affect every multi-module Maven build. maven-0006 fires during cascading failure handling in large reactor builds. Large mono-repos with 200+ modules hit these paths hardest.

The Fix

maven-0001: Replace ArrayList with LinkedHashSet for Vertex.children and Vertex.parents in both Graph.java files. Return type changes from List to Collection.

maven-0001-lifecycle: Precompute static final Set<String> STANDARD_LIFECYCLE_IDS from DefaultLifecycles.STANDARD_LIFECYCLES.

maven-0003: Add Map<String, Integer> cycleIndexMap parameter to visitCycle() for O(1) label lookup during cycle detection.

maven-0004: Add buildOrderMap() helper returning IdentityHashMap<MavenProject, Integer>, used in three sort sites.

maven-0005: Build IdentityHashMap<BuildStep, Integer> from plan.sortedNodes() before the stream sort.

maven-0006: Replace ArrayList<String> with HashSet<String> for blackList.

maven-0007: Replace ArrayList<String> with LinkedHashSet<String> for pluginGroups.

Patch

Patches available in defects/maven/patch/:

  • maven-0001-0002-vertex-linkedhashset.patch
  • maven-0001-standard-lifecycle-set.patch
  • maven-0003-cycle-index-map.patch
  • maven-0004-graph-builder-sorted-projects-index-map.patch
  • maven-0005-build-plan-logger-sorted-nodes-index-map.patch
  • maven-0006-reactor-manager-blacklist-arraylist.patch
  • maven-0007-execution-request-plugin-groups-arraylist.patch

Language: Java

What We Ask

  1. Confirm receipt and assign a JIRA reference (issues.apache.org/jira/browse/MNG).
  2. Assess severity — maven-0001 and maven-0004 fire on every multi-module build; maven-0003 fires on every cycle detection pass.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Maven team in the public disclosure. Preferred acknowledgment format welcome.

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