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

## Finding

Two O(N²) defects in OpenTofu's configuration parser and plan file snapshot reader. Both use slice-based membership tests inside loops over the same collection. Both patched. Speedups measured at 50×–250×.

## The Defects

**opentofu-0001 (PATCHED — HIGH):** `internal/configs/parser_config_dir.go:295`

```go
// filterTfPathsWithTofuAlternatives() — called during config directory parsing:
for _, p := range paths {                              // O(N) outer
    parallelTofuPath := toTofuPath(p)
    if slices.Contains(paths, parallelTofuPath) {     // O(N) inner scan
        // this path has a .tofu alternative — skip
    }
}
// O(N²) total — N = number of .tf/.tofu files in config directory
```

`filterTfPathsWithTofuAlternatives()` calls `slices.Contains(paths, parallelTofuPath)` inside a range over `paths`. Both the outer loop and the inner `slices.Contains` scan are O(N) over the same slice. Total: **O(N²)**. Fix: pre-build `map[string]bool` from `paths` before the loop. **Measured ratio: 250×.**

**opentofu-0002 (PATCHED — HIGH):** `internal/plans/planfile/config_snapshot.go:135`

```go
// readConfigSnapshot() — plan file deserialization:
for k := range snap.Modules {                      // O(M) outer
    found := false
    for _, record := range manifest {              // O(M) inner
        if record.Key == k {                       // O(M²) total
            found = true
            break
        }
    }
    if !found { return error }
}
// O(M²) — M = number of snapshot module entries
```

`readConfigSnapshot()` validates that every module key in `snap.Modules` exists in the manifest using a nested loop with string comparison. For M module entries: **O(M²)**. Fix: pre-build `map[string]bool` from manifest keys before the outer loop. **Measured ratio: 50×.**

## Complexity Proof

**opentofu-0001:** Let N = number of `.tf` and `.tofu` files in the configuration directory being parsed.
- Defective: for each of N paths, `slices.Contains` scans all N paths → N² string comparisons.
- Fixed: `pathSet := make(map[string]bool, len(paths))` before loop; each lookup O(1).
- Total work: O(N) map build + O(N) lookups = O(N) vs O(N²).
- At N=250 files (large modular config): defective=62,500 comparisons, fixed=250. **250× measured ratio.**

**opentofu-0002:** Let M = number of module entries in the plan file config snapshot.
- Defective: for each of M module keys, scan all M manifest records → M² comparisons.
- Fixed: `manifestKeys := make(map[string]bool, len(manifest))` before outer loop; each check O(1).
- Total work: O(M) map build + O(M) lookups = O(M) vs O(M²).
- At M=50 modules: defective=2,500 comparisons, fixed=50. **50× measured ratio.**

## Impact

opentofu-0001 fires on every `tofu init`, `tofu plan`, and `tofu apply` invocation when the working directory contains both `.tf` and `.tofu` files — which is the expected migration state for any project transitioning from Terraform to OpenTofu. Large module directories with many `.tf` files hit worst case on every run.

opentofu-0002 fires on every `tofu show` and plan file deserialization operation. Plan files from large infrastructure configurations with many modules (common in monorepo patterns) hit worst case on every read.

OpenTofu is the open-source fork of Terraform adopted as the CNCF-hosted infrastructure-as-code standard following the HashiCorp license change.

## The Fix

**opentofu-0001:** Pre-build a set from `paths` before the filter loop:

```go
// Before
for _, p := range paths {
    parallelTofuPath := toTofuPath(p)
    if slices.Contains(paths, parallelTofuPath) { ... }  // O(N) per iteration
}

// After
// CWE-407 fix: map[string]bool for O(1) membership test instead of O(N) slices.Contains.
pathSet := make(map[string]bool, len(paths))
for _, p := range paths {
    pathSet[p] = true
}
for _, p := range paths {
    parallelTofuPath := toTofuPath(p)
    if pathSet[parallelTofuPath] { ... }  // O(1)
}
```

**opentofu-0002:** Pre-build a set from `manifest` keys before the validation loop:

```go
// Before
for k := range snap.Modules {
    for _, record := range manifest {
        if record.Key == k { found = true; break }  // O(M) per module key
    }
}

// After
// CWE-407 fix: map[string]bool for O(1) key validation instead of O(M) scan per key.
manifestKeys := make(map[string]bool, len(manifest))
for _, record := range manifest {
    manifestKeys[record.Key] = true
}
for k := range snap.Modules {
    if !manifestKeys[k] { return error }  // O(1)
}
```

## Patch

`defects/opentofu/patch/opentofu-0001-0002-parser-snapshot-map.patch`

## What We Ask

1. Confirm receipt and assign a GitHub Security Advisory or issue reference (opentofu/opentofu).
2. Validate the patch against the config parser and planfile test suites.
3. Assess CVE eligibility — opentofu-0001 measured at 250× and fires on every invocation with mixed `.tf`/`.tofu` directories.
4. Coordinate a disclosure date — we are targeting 90 days from first contact.

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