# User-triggered code paths have infinite-loop guards

- **Pattern:** `ab-000249` (`ai-slop-cost-bombs.job-hygiene.infinite-loop-guards-present`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000249
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000249)

## Why it matters

Recursive functions without base cases or depth limits crash on cyclic data structures and blow the call stack on deeply nested inputs. CWE-674 (Uncontrolled Recursion) and CWE-835 (Loop with Unreachable Exit Condition) both apply. In a user-facing API, an attacker who can control a tree or graph structure — JSON, AST, category hierarchy, file system path — can trigger a stack overflow that crashes the Node process for all users. Even without adversarial input, an AI-generated recursive function that processes database-fetched hierarchical data will eventually encounter a self-referencing node in production and take down the server.

## Severity rationale

Medium because recursion without a guard requires either user-controlled input or corrupt data to trigger, but the crash affects all users on the same process when it fires.

## Remediation

Add a `depth` parameter with a hard limit to every recursive function. Throw an explicit error rather than returning silently, so the guard failure is visible in logs.

```ts
const MAX_DEPTH = 100

function processNode(node: TreeNode, depth = 0): ProcessedNode {
  if (depth > MAX_DEPTH) {
    throw new Error(`processNode: max depth ${MAX_DEPTH} exceeded — possible cycle`)
  }
  return {
    ...node,
    children: node.children.map(child => processNode(child, depth + 1)),
  }
}
```

For graph traversal (not trees), add a `visited: Set<string>` parameter and check `visited.has(node.id)` before recursing. This catches cycles that a depth limit alone would miss.

## Detection

- **ID:** `infinite-loop-guards-present`
- **Severity:** `medium`
- **What to look for:** Walk source files for recursive function definitions. Count all functions that call themselves by name. For each recursive function, verify the function body contains one of: a numeric depth parameter with a base case (`if (depth > MAX) return`), a counter guard (`if (count >= LIMIT) throw`), an explicit early return on a base case (`if (!node) return`), OR is a tail-recursive function on a bounded data structure.
- **Pass criteria:** 100% of recursive functions have explicit base cases or depth limits. Report: "X recursive functions inspected, Y with guards, 0 unbounded."
- **Fail criteria:** At least 1 recursive function has no visible base case or depth guard.
- **Skip (N/A) when:** No recursive function definitions found in source.
- **Detail on fail:** `"1 unguarded recursion: 'function processNode(node) { node.children.forEach(processNode) }' in src/lib/tree.ts — no cycle detection, will infinite-loop on a self-referencing node"`
- **Remediation:** Recursion without a base case can blow the stack OR loop forever on cyclic data. Add a depth limit:

  ```ts
  // Bad: no guard
  function processNode(node: Node) {
    node.children.forEach(processNode)
  }

  // Good: depth-limited
  function processNode(node: Node, depth = 0) {
    if (depth > 100) throw new Error('Max depth exceeded')
    node.children.forEach(child => processNode(child, depth + 1))
  }
  ```

## External references

- cwe CWE-674
- cwe CWE-835
- iso-25010 reliability

Taxons: cost-efficiency, error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000249
