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
•Technically reviewed by Anupam Mediratta•Published August 31, 2026•Reviewed 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."

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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #941

Related Articles

high

CWE-778: Tribute WebHookHandler Logs No Subscription Events

The Tribute payment webhook handler verified request signatures and then mutated subscription state — granting new subscriptions and cancelling existing ones — while logging nothing on success. Only error paths and a no-op test hook produced log lines, so a forged or replayed `NewSubscription` event that passed signature verification left no record of which `TelegramUserID` was affected. The fix adds structured `slog.Info` records at signature verification and at both state-changing branches.

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The