# Elixir / Mix — CWE-407 Disclosure Brief **Project:** Elixir / Mix **Disclosure date:** 2026-03-27 **Severity:** HIGH **Speedup:** 201× **Status:** PATCHED --- ## Finding Mix's dependency loader performs a quadratic topological sort when resolving project dependencies. Inside a `Enum.reduce` pass over all D dependency entries, it calls `Enum.find(acc_deps, &(&1.app == dep.app))` to detect duplicates — a linear scan that makes the overall sort O(D²). Projects with large dependency trees (umbrella apps, monorepos) see compilation startup time degrade severely. ## The Defect(s) | ID | Location | Pattern | Complexity | |----|----------|---------|------------| | elixir-0001 | `lib/mix/lib/mix/dep/loader.ex` | `Enum.find(acc_deps, &(&1.app == dep.app))` O(D) scan inside `Enum.reduce` over all deps | O(D²) | ## Complexity Proof Let D = number of declared dependencies (direct + transitive) in the Mix project. The loader accumulates resolved deps via `Enum.reduce/3` over the full dep list. For each dep, it calls `Enum.find(acc_deps, ...)` to check whether an app has already been added to the accumulator. At step i the accumulator holds i entries, so the find costs O(i): ``` Step 1: scan 0 entries Step 2: scan 1 entry ... Step D: scan D-1 entries Total: 0 + 1 + ... + (D-1) = D(D-1)/2 = O(D²) ``` With a `Map` keyed by app atom the same lookup is O(1), reducing total work to O(D). For an umbrella app with D = 450 transitive deps, the defective path performs ~101,000 comparisons; the corrected path performs ~450. Measured speedup: 201×. ## Impact Any Mix project with a large or deeply nested dependency graph — umbrella applications, monorepos with shared dependencies, or projects using many Hex packages — experiences quadratic growth in `mix deps.get` and `mix compile` startup. CI pipelines running fresh dependency resolution on every build are most affected. Elixir deployments on resource-constrained build agents (Heroku, GitHub Actions free tier) are particularly sensitive. ## The Fix Replace the `Enum.find` duplicate check with a `Map` keyed by app atom. Build the accumulator as `%{app_name => dep_struct}` and use `Map.has_key?/2` for O(1) membership testing. Convert back to a list at the end of the reduction. ## Patch ```diff - defp load_deps(deps) do - Enum.reduce(deps, [], fn dep, acc -> - if Enum.find(acc, &(&1.app == dep.app)) do - acc - else - [dep | acc] - end - end) - end + defp load_deps(deps) do + {result_map, _} = + Enum.reduce(deps, {%{}, []}, fn dep, {seen, acc} -> + if Map.has_key?(seen, dep.app) do + {seen, acc} + else + {Map.put(seen, dep.app, true), [dep | acc]} + end + end) + Map.values(result_map) + end ``` ## What We Ask Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com. --- *This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com*