The fire alarm was wired to reset itself every time a room got too hot, so it never actually rang
CVE-2026-56859 is a bug in Go's standard library encoding/xml package — specifically, (*Decoder).DecodeElement resets the recursion-depth counter on every call, meaning the guard that is supposed to prevent unbounded nesting never fires. An attacker sends a trivially-crafted XML document (<a><a><a>… repeated tens of thousands of times) to any Go service that calls Decode, DecodeElement, Unmarshal, Token, RawToken, or Skip on untrusted input. The goroutine's stack exhausts and the Go runtime kills the process with a fatal, unrecoverable panic. Affected Go versions: all branches before 1.25.13, the 1.26 branch before 1.26.6, and the 1.27 release-candidate line before 1.27.0-rc.3.
The vendor rates this HIGH at 7.5, and the CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) accurately models the attack surface — unauthenticated, no user interaction, low complexity, availability-only impact. That said, 7.5 is slightly generous for the *typical* enterprise fleet because (a) Go's ecosystem is overwhelmingly JSON-oriented, so the subset of services actually parsing untrusted XML is a minority, and (b) most production Go workloads run in containers with orchestration-level restart policies that limit sustained outage to seconds. We downgrade modestly to 7.0 — still firmly HIGH because the exploit is trivial and the stdlib is ubiquitous, but the practical blast radius and impact ceiling (crash, not compromise) shave half a point.
4 steps from start to impact.
Identify a Go service that accepts XML input
Server, runtime fingerprints) or known technology stacks reveal the Go runtime.- Target service parses XML from untrusted network input
- Service is network-reachable (internet-facing or internal)
- Most modern Go services use JSON, not XML — the attacker must find one that actually invokes
encoding/xml - Internal-only services require prior network access
runtime: goroutine stack exceeds) are a reliable post-crash indicator.Craft a deeply-nested XML payload
<a> repeated 50,000 times followed by the same number of closing tags. This is a ~500 KB payload that any scripting language can produce in a single line. No public PoC repo is necessary because the 'exploit' is trivial to construct.- Ability to send HTTP/TCP traffic to the target
- Request-size limits (e.g.,
client_max_body_sizein nginx) could block very large payloads, though 500 KB is well under most defaults
Send the payload to trigger stack exhaustion
encoding/xml decoder begins recursing without limit. Because DecodeElement resets the depth counter on every invocation, the recursion-depth guard never fires. The goroutine stack grows until the Go runtime detects stack exhaustion and terminates the process with a fatal error.- The XML is routed to a code path that calls one of the six affected symbols:
Decode,DecodeElement,Unmarshal,Token,RawToken, orSkip
- The crash is process-wide in Go (a stack overflow is not a recoverable panic), but container orchestrators (Kubernetes, ECS, systemd) restart the process within seconds
- Sustained outage requires the attacker to continuously re-send payloads
runtime: goroutine stack exceeds [limit] with a stack trace rooted in encoding/xml.(*Decoder).unmarshal. Monitoring for OOMKilled or CrashLoopBackOff in Kubernetes detects repeated crashes.Sustain the denial of service
- Persistent network access to the target
- No rate-limiting or payload-depth inspection blocking repeated attempts
- Rate-limiting, IP-based blocking, or WAF rules can break the loop
- CrashLoopBackOff back-off timers in Kubernetes slow the restart cycle, making the outage intermittent rather than total
- SRE alerting typically triggers human intervention within minutes
The supporting signals.
| In-the-Wild Exploitation | No known exploitation. Not listed in CISA KEV. No reports from threat intelligence feeds (Mandiant, CrowdStrike, Recorded Future) as of 2026-09-20. |
|---|---|
| Proof-of-Concept | Trivially constructible — no formal PoC repo is needed. A bash one-liner (python3 -c "print('<a>'*50000 + '</a>'*50000)") produces a working payload. No weaponized tooling observed. |
| EPSS Score | 0.00568 (0.57%) — bottom quartile. The EPSS model sees low exploitation likelihood in the next 30 days, consistent with DoS-only impact and no weaponized campaigns. |
| KEV Status | Not listed. CISA has not added this to the Known Exploited Vulnerabilities catalog as of 2026-09-20. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — Network-accessible, zero-click, no auth, availability-only impact. The Scope:Unchanged means the crash is confined to the vulnerable process. |
| Affected Versions | Go < 1.25.13, Go 1.26.0 – 1.26.5, Go 1.27.0-rc.1 – 1.27.0-rc.2. KrakenD CE 2.0 – 2.13.8, KrakenD EE 2.0 – 2.13.6. Any binary compiled with an affected Go toolchain inherits the flaw. |
| Fixed Versions | Go 1.25.13, 1.26.6, 1.27.0-rc.3. KrakenD CE 2.13.9, KrakenD EE 2.13.7. Red Hat advisories (RHSA-2026:67974, RHSA-2026:67975) ship patched Go toolchains for RHEL. |
| Scanning / Exposure | No specific Shodan/GreyNoise/Censys fingerprint for this vuln. encoding/xml is imported by 79,507 Go packages per pkg.go.dev. Effective exposure is the subset parsing untrusted XML over the network — estimated 15–25% of Go service deployments. |
| Disclosure Date | 2026-08-13 (Go security advisory), public issue golang/go#80481. Patches released same day. |
| Reporter | Moran Omer (GitHub: moraneus). Coordinated disclosure via Go security process. |
Why this verdict
- Trivial, unauthenticated remote trigger: The CVSS vector is accurate — any network-reachable Go service parsing XML can be crashed with a trivially-crafted payload requiring zero authentication and zero user interaction. This keeps the baseline at HIGH.
- Impact ceiling is DoS, not compromise: The worst outcome is a process crash. There is no path to code execution, data exfiltration, or privilege escalation. The CVSS already models this (C:N/I:N/A:H), but it still bounds how high the score can go.
- XML is the minority input format in Go ecosystems: Go's ecosystem is overwhelmingly JSON-oriented. While
encoding/xmlis imported by ~79K packages, the fraction of production services parsing *untrusted* XML over the network is materially smaller than JSON-consuming services — estimated 15–25%. This narrows the effective blast radius. - Container orchestration limits sustained impact: Most enterprise Go workloads run in Kubernetes, ECS, or behind systemd with automatic restart. A single crash recovers in seconds. Sustained DoS requires continuous re-sending, which is detectable and blockable via rate-limiting.
- Role multiplier: (a) *Low-value role (dev workstation)*: Go toolchain crash during local XML test — negligible impact. (b) *Typical role (microservice)*: Process crash, auto-restart, brief blip — moderate impact. (c) *High-value role (API gateway / KrakenD)*: KrakenD is explicitly affected and sits at the network edge; a crash cascades to all backends behind it. However, the outcome is service disruption (availability), not domain takeover, fleet compromise, or data egress. Gateway operators typically deploy HA pairs, so single-instance crash is mitigated. The HIGH floor is not triggered by the strict criteria (no domain/fleet/supply-chain outcome), but the vendor's HIGH classification remains appropriate because the attack is trivially executable against a widely-deployed stdlib component.
- EPSS and exploitation status support modest downgrade: At 0.57% EPSS, no KEV listing, and no observed exploitation, the real-world urgency is lower than the CVSS score alone suggests. Combined with the DoS-only ceiling and container resilience, a half-point reduction from 7.5 to 7.0 is warranted.
Why not higher?
The impact is strictly availability — there is no path from this vulnerability to code execution, data access, or lateral movement. The crash is confined to the vulnerable process (Scope:Unchanged). Even in the worst high-value role scenario (API gateway), the outcome is service disruption, not domain takeover or fleet compromise. No active exploitation or weaponized campaigns exist to justify CRITICAL.
Why not lower?
The exploit is trivially constructible, requires zero authentication, and targets a Go *standard library* function — not an optional third-party dependency. Any Go service that touches untrusted XML is reachable, and the crash is a hard, unrecoverable process kill (not a graceful degradation). KrakenD, a widely-deployed API gateway, is explicitly affected. Dropping below HIGH would understate the risk for the significant minority of Go deployments that do handle XML.
What to do — in priority order.
- Deploy XML depth-limiting at the ingress layer — Configure your reverse proxy, WAF, or API gateway to reject XML documents exceeding a nesting depth of 100–200 elements. In nginx, use the
client_body_buffer_sizeand a Lua/njs module to inspect depth. In AWS WAF or Cloudflare, use custom rules on XML body inspection. Deploy within 30 days per the noisgate mitigation SLA for HIGH. - Recompile affected Go binaries with patched toolchain — Upgrade to Go 1.25.13, 1.26.6, or 1.27.0-rc.3+ and rebuild all binaries that import
encoding/xml. Usego version -m <binary>to audit which binaries on your fleet were compiled with a vulnerable Go version. Deploy rebuilt binaries within 180 days per the noisgate remediation SLA for HIGH. - Rate-limit XML-accepting endpoints — Apply per-IP and per-endpoint rate limits (e.g., 10 req/s) on any route that accepts XML input. This prevents an attacker from sustaining a CrashLoopBackOff condition even if the vulnerability is present. Most API gateways and ingress controllers support this natively.
- Upgrade KrakenD if deployed — KrakenD CE users should upgrade to 2.13.9+, EE users to 2.13.7+. If XML encoding/decoding is not used in your KrakenD configuration, the exposure is mitigated but the upgrade should still be applied as defense-in-depth.
- Enable crash-loop alerting — Ensure Kubernetes CrashLoopBackOff events, systemd rapid-restart conditions, and Go runtime fatal errors (
goroutine stack exceeds) trigger PagerDuty/Opsgenie alerts. This ensures active exploitation is detected within minutes.
- JSON-only input validation — if a service accepts XML on *any* endpoint (including health checks, SAML, webhooks), restricting JSON on primary API routes does not eliminate the attack surface.
- Go's
recover()in deferred functions — stack exhaustion in Go is a *fatal* error, not a panic.recover()cannot catch it. You cannot code around this bug without patching the runtime. - Request body size limits alone — a 500 KB payload with 50,000 nesting levels is well under typical
client_max_body_sizedefaults (1–10 MB). Size limits do not substitute for depth limits.
Crowdsourced verification payload.
Run this script on each target host (or via your fleet management tool) to check whether Go binaries compiled with vulnerable toolchains are present. Requires find and go (or the go version binary) on the target. Execute as any user with read access to your Go binary directories: bash check_cve_2026_56859.sh /usr/local/bin /opt/app/bin
#!/usr/bin/env bash
# check_cve_2026_56859.sh — Detect Go binaries vulnerable to CVE-2026-56859
# Usage: bash check_cve_2026_56859.sh [dir1] [dir2] ...
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
VULNERABLE=0
CHECKED=0
SEARCH_DIRS=("${@:-/usr/local/bin /usr/bin /opt}")
if ! command -v go &>/dev/null; then
echo "[WARN] go binary not found in PATH; attempting to read ELF headers directly."
fi
is_vulnerable_version() {
local ver="$1"
# Strip 'go' prefix: go1.25.12 -> 1.25.12
ver="${ver#go}"
local major minor patch
IFS='.' read -r major minor patch <<< "$ver"
patch="${patch%%[^0-9]*}" # strip rc/beta suffixes for base comparison
# 1.25.x: vulnerable if patch < 13
if [[ "$minor" -eq 25 ]] && [[ "${patch:-0}" -lt 13 ]]; then
return 0
fi
# 1.26.x: vulnerable if patch < 6
if [[ "$minor" -eq 26 ]] && [[ "${patch:-0}" -lt 6 ]]; then
return 0
fi
# 1.27.0-rc.1 and rc.2 are vulnerable; rc.3+ is patched
if [[ "$minor" -eq 27 ]]; then
if echo "$1" | grep -qE 'rc\.?[12]$'; then
return 0
fi
if [[ "${patch:-0}" -eq 0 ]] && ! echo "$1" | grep -qE 'rc'; then
# 1.27.0 release (if it exists) should be patched
return 1
fi
fi
# Older branches (< 1.25) are vulnerable if they have encoding/xml
if [[ "$minor" -lt 25 ]]; then
return 0
fi
return 1
}
for dir in ${SEARCH_DIRS[@]}; do
[ -d "$dir" ] || continue
while IFS= read -r -d '' binary; do
ver_line=$(go version "$binary" 2>/dev/null || true)
if [[ -z "$ver_line" ]] || [[ "$ver_line" == *"not a Go executable"* ]]; then
continue
fi
go_ver=$(echo "$ver_line" | grep -oP 'go[0-9]+\.[0-9]+\.[0-9]+[^\s]*' | head -1)
if [[ -z "$go_ver" ]]; then
continue
fi
CHECKED=$((CHECKED + 1))
# Check if binary imports encoding/xml
uses_xml=$(go version -m "$binary" 2>/dev/null | grep -c 'encoding/xml' || true)
if [[ "$uses_xml" -gt 0 ]] && is_vulnerable_version "$go_ver"; then
echo "[VULNERABLE] $binary (compiled with $go_ver, imports encoding/xml)"
VULNERABLE=1
elif is_vulnerable_version "$go_ver"; then
echo "[INFO] $binary (compiled with $go_ver, does NOT import encoding/xml — not directly affected)"
else
echo "[PATCHED] $binary (compiled with $go_ver)"
fi
done < <(find "$dir" -type f -executable -print0 2>/dev/null)
done
if [[ "$CHECKED" -eq 0 ]]; then
echo "UNKNOWN — no Go binaries found in specified directories."
exit 2
elif [[ "$VULNERABLE" -eq 1 ]]; then
echo "VULNERABLE — one or more Go binaries compiled with affected toolchain and importing encoding/xml."
exit 1
else
echo "PATCHED — all detected Go binaries use a fixed toolchain version."
exit 0
fiWhat defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.