# 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*
