Back to Blog
high SEVERITY7 min read

How Infinite Loop Denial of Service happens in Go's golang.org/x/text and how to fix it

A high-severity denial-of-service flaw (CVE-2026-56852) in golang.org/x/text's Unicode normalization iterator (`norm.Iter`) could cause an infinite loop when processing specially crafted input. The `mcp` module's `go.mod`/`go.sum` pinned a vulnerable v0.14.0 release; upgrading to v0.39.0 closes the hole.

O
By Orbis AppSec
Published August 31, 2026Reviewed August 31, 2026

Answer Summary

CVE-2026-56852 is a denial-of-service vulnerability (related to CWE-835, Loop with Unreachable Exit Condition) in the `norm.Iter` type of Go's `golang.org/x/text/unicode/norm` package, where malformed or adversarially-crafted Unicode input can trigger an infinite loop and hang the process. The fix is a dependency upgrade — bumping `golang.org/x/text` from v0.14.0 to v0.39.0 in `mcp/go.mod` and `mcp/go.sum` — which contains the upstream patch to the normalization iterator's exit logic. No application code changes were needed since the vulnerable logic lives entirely inside the third-party library.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade `golang.org/x/text` from v0.14.0 to v0.39.0 in `mcp/go.mod` and `mcp/go.sum`
riskA malicious or malformed string passed through text normalization can hang the goroutine handling it, exhausting CPU/threads and denying service
languageGo
root cause`norm.Iter` in golang.org/x/text v0.14.0 fails to guarantee forward progress on certain crafted byte sequences
vulnerabilityInfinite Loop / Denial of Service in Unicode normalization

Introduction

The mcp/go.mod file doesn't contain a single line of vulnerable application logic — yet it was flagged as high severity. That's because go.mod and go.sum pin the exact version of every dependency your Go module trusts at build time, and one of those dependencies, golang.org/x/text, shipped a text-normalization iterator with a subtle non-termination bug. The mcp module declared:

golang.org/x/text v0.14.0 // indirect

That pinned version contains a vulnerable norm.Iter implementation in the unicode/norm package. If any code path in the dependency tree — directly or transitively — feeds attacker-influenced text through Unicode normalization, a crafted input can make norm.Iter loop forever instead of advancing to completion. This matters for anyone building services (like the mcp server here) that parse or normalize user-supplied strings, JSON payloads, or schema definitions before further processing, because a single hung goroutine caused by one malicious request can quietly eat a CPU core and, at scale, take down the service.

The Vulnerability Explained

golang.org/x/text/unicode/norm implements Unicode Normalization Forms (NFC, NFD, NFKC, NFKD) used throughout the Go ecosystem — often indirectly, through libraries that do case folding, string comparison, or text sanitization. The core type driving normalization is norm.Iter, which walks over a byte sequence and yields normalized runes one segment at a time.

CVE-2026-56852 describes a class of input where norm.Iter's internal state machine fails to make forward progress:

"A norm.Iter can enter an infinite loop when handling input containing ..." — certain malformed or boundary-condition Unicode byte sequences cause the iterator's internal cursor to stall, so the loop that's supposed to consume the buffer and terminate never reaches its exit condition.

Conceptually, this is CWE-835 — Loop with Unreachable Exit Condition. The iterator's Next()-style loop assumes every call either advances the read position or signals end-of-input. When that invariant breaks for a specific byte pattern, the loop spins on the same offset forever.

Why this is dangerous in practice: in the mcp module, golang.org/x/text is listed as an indirect dependency — meaning it's pulled in by another package (likely something handling JSON schema validation, URI templates, or MCP protocol messages, judging by the neighboring entries like santhosh-tekuri/jsonschema and yosida95/uritemplate in the same go.mod). If any of those upstream consumers pass untrusted, attacker-controlled text through normalization — for example, normalizing a field name, a URI template variable, or a schema string before comparison — an attacker who controls that input can craft a payload that triggers the infinite loop.

Example attack scenario: Imagine an MCP client sends a request whose payload includes a string field that eventually flows into a normalization call inside the dependency chain. A well-formed request would normalize instantly. But a request containing a crafted sequence of Unicode combining characters or malformed UTF-8 boundary bytes could cause the goroutine processing that request to hang in norm.Iter's loop indefinitely. Since Go's default HTTP/RPC handling spawns a goroutine per request, a handful of such requests sent concurrently could pin multiple CPU cores at 100%, starving legitimate traffic — a classic low-cost, high-impact DoS.

The Fix

The fix here is intentionally simple and low-risk: upgrade the dependency, not patch application code. Since the bug lives entirely inside golang.org/x/text, the correct remediation is to pull in the version where the Go team fixed the iterator's loop logic.

Before:

// mcp/go.mod
golang.org/x/text v0.14.0 // indirect

After:

// mcp/go.mod
golang.org/x/text v0.39.0 // indirect

And the corresponding go.sum entries were updated to pin the new module's verified checksums:

+golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
+golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=

The old v0.14.0 lines remain in go.sum (Go keeps historical checksums for reproducibility across the module graph), but the require directive in go.mod now resolves to v0.39.0, so every build and go mod verify picks up the patched norm.Iter.

Why both files needed changing:
- go.mod declares which version of the module your build actually uses — bumping the version number here is what triggers the upgrade.
- go.sum is Go's integrity ledger; it must contain the cryptographic hash of the exact version referenced in go.mod, or go build/go mod verify will refuse to proceed. Updating only go.mod without go.sum would break the build entirely.

Twenty-five minor releases sit between v0.14.0 and v0.39.0, so this upgrade also picks up numerous other unrelated fixes and improvements in golang.org/x/text. Because the change is confined to dependency metadata, application behavior for valid inputs is unaffected — the PR description correctly notes the fix "only tightens handling of untrusted input and leaves valid inputs unaffected."

Prevention & Best Practices

  • Pin and patch dependencies proactively. Run go list -u -m all or tools like govulncheck regularly to catch known CVEs in your module graph before a scanner flags them in production.
  • Treat indirect dependencies as first-class risk. The vulnerable package here was marked // indirect — it wasn't imported directly by mcp's code, but it was still part of the attack surface through a transitive dependency.
  • Bound untrusted-input processing. For any code path that normalizes, parses, or transforms attacker-controlled strings, consider wrapping the operation with a context.WithTimeout or a worker pool with cancellation, so a single misbehaving call can't hang a goroutine forever.
  • Fuzz text-processing code. Go's built-in fuzzing (go test -fuzz) is well-suited to catching non-terminating loops in string/byte processing — feed it malformed UTF-8 and boundary Unicode sequences.
  • Automate dependency scanning in CI. Tools like govulncheck, Trivy, Dependabot, or Snyk should run on every go.mod/go.sum change so upgrades like this one happen before a CVE becomes exploitable in production.
  • Map CWE-835 patterns. Any loop that advances based on parsed input state — lexers, iterators, decoders — should have an explicit assertion or test proving the cursor always advances, even on malformed input.

Key Takeaways

  • golang.org/x/text v0.14.0's norm.Iter could infinite-loop on crafted Unicode input (CVE-2026-56852) — the fix is purely a version bump, not a code rewrite.
  • The vulnerable package was an indirect dependency in mcp/go.mod, proving that transitive dependencies deserve the same scrutiny as direct ones.
  • Both go.mod and go.sum had to be updated together — updating one without the other breaks Go's module verification.
  • Upgrading to v0.39.0 closes the loop-termination bug in the Unicode normalization iterator without changing any application-level behavior for valid input.
  • Any service normalizing or comparing user-supplied strings (schema fields, URI templates, protocol payloads) should audit whether that text ever reaches golang.org/x/text/unicode/norm.

How Orbis AppSec Detected This

  • Source: Untrusted text (e.g., protocol payload fields, schema strings, or URI template variables) processed anywhere in the mcp module's dependency chain that invokes Unicode normalization.
  • Sink: norm.Iter's internal iteration loop inside golang.org/x/text/unicode/norm (pulled in transitively via mcp/go.mod).
  • Missing control: No dependency-version gate ensured the module resolved to a patched golang.org/x/text release; go.mod was pinned to the vulnerable v0.14.0.
  • CWE: CWE-835 — Loop with Unreachable Exit Condition.
  • Fix: Bumped golang.org/x/text from v0.14.0 to v0.39.0 in mcp/go.mod and mcp/go.sum, pulling in the upstream fix to the normalization iterator's loop logic.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-56852 is a good reminder that denial-of-service bugs don't need memory corruption or complex exploit chains — a single loop that forgets to guarantee forward progress is enough to let an attacker hang your service with one crafted string. In this case, the vulnerable code wasn't even written by the mcp team; it was three dependency layers deep in golang.org/x/text, pinned by a stale go.mod entry. The fix — bumping from v0.14.0 to v0.39.0 — took two lines in go.mod and two lines in go.sum, but it closes a real attack surface against any code path that normalizes untrusted Unicode text. Keep your dependency graph current, scan it continuously, and remember that "indirect" dependencies can still be direct attack vectors.

References

Frequently Asked Questions

What is an infinite loop denial-of-service vulnerability?

It's a bug where a loop's exit condition can never be satisfied for certain inputs, causing the program to hang indefinitely and consume CPU resources, effectively denying service to legitimate users.

How do you prevent infinite loop DoS vulnerabilities in Go?

Keep dependencies patched, add iteration bounds/timeouts around untrusted-input processing, use context cancellation for long-running operations, and fuzz-test parsers/iterators with malformed input.

What CWE is associated with infinite loop denial of service?

CWE-835, "Loop with Unreachable Exit Condition," which covers loops that can fail to terminate under attacker-controlled conditions.

Is a timeout wrapper enough to prevent this kind of DoS?

A timeout mitigates impact by killing the stuck goroutine, but it doesn't fix the underlying bug; the real fix is patching the flawed loop logic in the library itself, which is why upgrading `golang.org/x/text` is required.

Can static analysis detect infinite loop vulnerabilities like this one?

Traditional static analysis struggles to prove loop termination, but dependency scanners like Trivy can flag known-vulnerable package versions (as it did here with CVE-2026-56852), and fuzzing is effective at discovering non-terminating inputs.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #941

Related Articles

critical

How a vulnerable websocket-driver dependency happens in Node.js lockfiles and how to fix it

A Trivy scan flagged `websocket-driver@0.7.4` in this repository's `bun.lock` as affected by CVE-2026-54466, a critical issue in a WebSocket protocol handler that parses untrusted HTTP upgrade requests and frame data. The fix upgrades the package to `0.7.5` and adds an explicit `websocket-driver` entry to the lockfile's override block so every transitive consumer — webpack-dev-server, sockjs, faye-websocket — resolves to the patched build instead of the pinned vulnerable one.

high

How Dependabot Missing Cooldown Periods Enable Supply Chain Attacks and How to Fix It

A critical security vulnerability in `.github/dependabot.yml` was exposing a Node.js library to supply chain attacks by automatically updating to newly published packages without a safety delay. By adding a 7-day cooldown period to each package ecosystem configuration, the project now protects against malicious or unstable package versions that could affect downstream consumers.

high

How Exponential-Time Complexity Causes Denial of Service in brace-expansion and How to Fix It

A critical vulnerability in brace-expansion versions 1.1.13 and earlier allowed attackers to cause denial of service through crafted brace pattern inputs. The fix upgrades to patched versions 1.1.16, 2.1.2, and 5.0.7, eliminating the exponential-time complexity that made exploitation possible.

high

How unrestricted file upload via extension-only validation happens in Deno/JavaScript and how to fix it

The review image upload handler in this Deno-based app trusted the client-supplied filename extension to decide whether a file was a "safe" image, without ever inspecting the actual file bytes. The fix adds magic-byte signature verification for PNG, JPEG, GIF, and WEBP formats before the file is written to disk, closing the door on disguised executables and malicious payloads.

high

How Missing Dependabot Cooldown Periods Enable Supply Chain Attacks in CI/CD Pipelines and How to Fix Them

We fixed a high-severity supply chain security gap in `.github/dependabot.yml` where missing cooldown periods allowed immediate adoption of newly published packages. The fix adds `cooldown: default-days: 7` to all package ecosystems, creating a critical security buffer against typosquatting and malicious dependency attacks.

high

How Dependabot Missing Cooldown Vulnerability Happens in GitHub Actions and How to Fix It

Dependabot configurations without cooldown periods can automatically propose updates from newly published packages within hours—potentially including malicious or unstable versions. This vulnerability in `.github/dependabot.yml` was fixed by adding a `cooldown` block with `default-days: 7` to delay updates and allow time for community vetting.