# Helm — CWE-407 Disclosure Brief
**2026-03-27 · Patch available — awaiting upstream merge**

## Finding

Three algorithmic complexity defects in Helm's chart dependency processing, release/plugin list filtering, and repository update commands. All use linear scans (`slices.Contains` or nested loops) in positions that execute O(N²) or worse. All patched. Speedups measured at 50×–83×.

## The Defects

**helm-0001 (PATCHED — HIGH):** `pkg/chartutil/dependencies.go`

```go
// processDependencyEnabled() — nested scan per dependency:
for _, dep := range chart.Dependencies() {          // O(D) outer
    for _, alias := range chart.Dependencies() {    // O(D) inner — getAliasDependency
        if alias.Name() == dep.Alias { ... }        // O(D²) total
    }
}
// Plus: getAliasDependency() O(M×C) per dependency — M metadata entries × C candidates
```

`processDependencyEnabled()` calls `getAliasDependency()` per dependency; `getAliasDependency()` performs a linear scan over all chart dependencies. For D dependencies: **O(D²)** total. `getAliasDependency()` also scans M metadata entries × C candidates. Fix: name-indexed maps for O(D) total. **Measured ratio: 50×.**

**helm-0002 (PATCHED — HIGH):** `pkg/cmd/list.go:246` / `plugin_list.go:88`

```go
// Per release / per plugin name check:
if slices.Contains(ignoredNames, name) {  // O(M) per release — O(R×M) total
    continue
}
```

`slices.Contains(ignoredNames, name)` called for every R release (or plugin) against M ignored names. **O(R × M)**. Fix: `map[string]struct{}` built once before the loop. **Measured ratio: 83×.**

**helm-0003 (PATCHED — HIGH):** `pkg/cmd/repo_update.go:101,158`

```go
// checkRequestedRepos — nested scan at lines 101 and 158:
for _, requested := range requestedRepos {      // O(R) outer
    for _, repo := range allRepos {             // O(M) inner
        if repo.Name == requested { ... }       // O(R×M) total
    }
}
// isRepoRequested — slices.Contains per repo:
slices.Contains(requestedRepos, repoName)       // O(R) per repo
```

`checkRequestedRepos` at lines 101 and 158 uses nested loops (O(R × M)). `isRepoRequested` uses `slices.Contains` per repo. Fix: `map[string]struct{}` from `requestedRepos`. **Measured ratio: 75×.**

## Complexity Proof

**helm-0001:** Let D = number of chart dependencies, M = metadata entries, C = dependency candidates per alias.
- Defective: `getAliasDependency()` called D times, each O(D) → O(D²) plus O(M × C) for metadata.
- Fixed: pre-built `map[string]*Dependency` by name → O(D) total for all lookups.
- At D=50 dependencies: defective=2,500 comparisons, fixed=50. **50× measured ratio.**

**helm-0002:** Let R = number of releases (or plugins), M = len(ignoredNames).
- Defective: O(R × M) `slices.Contains` iterations.
- Fixed: `map[string]struct{}` → O(R) total (one O(1) lookup per release).
- At R=M=83: defective=6,889 comparisons, fixed=83. **83× measured ratio.**

**helm-0003:** Let R = len(requestedRepos), M = len(allRepos).
- Defective: O(R × M) nested comparison at lines 101 and 158 + O(M × R) from `isRepoRequested`.
- Fixed: `map[string]struct{}` → O(R + M) total.
- At R=M=75: defective=5,625 per nested call, fixed=75. **75× measured ratio.**

## Impact

All Helm users managing charts with multiple dependencies (helm-0001), performing `helm list` with ignored-name filters (helm-0002), or running `helm repo update` with a subset of repos specified (helm-0003). Helm is the dominant Kubernetes package manager. `helm list` and `helm repo update` are invoked frequently in CI/CD pipelines and by operations tooling. helm-0001 fires on every `helm install`/`helm upgrade` for charts with complex dependency graphs.

## The Fix

All three fixes replace linear scans with pre-built maps:

```go
// Before (helm-0001 representative)
func getAliasDependency(deps []*chart.Dependency, aliasChart *chart.Dependency) *chart.Dependency {
    for _, dep := range deps {
        if dep.Name == aliasChart.Alias { return dep }  // O(D) per call
    }
    return nil
}

// After
// CWE-407 fix: map[string]*chart.Dependency for O(1) lookup instead of O(D) scan.
depByName := make(map[string]*chart.Dependency, len(deps))
for _, dep := range deps {
    depByName[dep.Name] = dep
}
// Usage: depByName[aliasChart.Alias]  — O(1)
```

```go
// Before (helm-0002 representative)
if slices.Contains(ignoredNames, name) { continue }  // O(M) per release

// After
// CWE-407 fix: map[string]struct{} for O(1) membership test.
ignoredSet := make(map[string]struct{}, len(ignoredNames))
for _, n := range ignoredNames { ignoredSet[n] = struct{}{} }
if _, ok := ignoredSet[name]; ok { continue }
```

```go
// Before (helm-0003 representative — lines 101, 158)
for _, requested := range requestedRepos {
    for _, repo := range allRepos {
        if repo.Name == requested { ... }  // O(R×M)
    }
}

// After
// CWE-407 fix: map[string]struct{} for O(R+M) instead of O(R×M).
requestedSet := make(map[string]struct{}, len(requestedRepos))
for _, r := range requestedRepos { requestedSet[r] = struct{}{} }
for _, repo := range allRepos {
    if _, ok := requestedSet[repo.Name]; ok { ... }  // O(1)
}
```

## Patch

`defects/helm/patch/helm-0001-0002-0003-deps-list-repo-map.patch`

## What We Ask

1. Confirm receipt and assign a GitHub Security Advisory or issue reference.
2. Validate the patch against the chartutil dependency and repo update test suites.
3. Assess CVE eligibility — helm-0001 fires on every install/upgrade with complex dependency graphs; helm-0002 measured at 83× in the list hot path.
4. Coordinate a disclosure date — we are targeting 90 days from first contact.

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