A malicious or misconfigured server sends one HTTP/2 SETTINGS frame with a single field — SETTINGS_MAX_FRAME_SIZE — set to 0. That's the entire attack. On any unpatched Go program dialing that server as an HTTP/2 client, the response is an infinite loop: the transport goroutine spins forever writing CONTINUATION frames it can never finish, burning CPU and memory until something kills the process. No auth bypass, no payload, no crafted request body — a malformed four-byte settings value is the whole exploit, and it's tracked as CVE-2026-33814.
The part worth sitting with isn't the bug itself — it's who's exposed. This is a client-side flaw in golang.org/x/net/http2, and a striking share of a Kubernetes control plane is, at some point, an HTTP/2 client of something else: kube-apiserver calls out to admission webhooks and aggregated API servers; kubelet, controller-manager, and every Cluster API provider controller call the apiserver; kubectl and anything built on client-go call whatever apiserver you point it at. Here's the exposed-component map, and the exact govulncheck commands to check whether your fleet is still running the vulnerable transport — before you get to how the bug works and why Kubernetes never gave it a K8s-branded advisory of its own.
Who's Exposed, and How to Check Right Now
At minimum, run this against every control-plane and CAPI-provider container image in your fleet:
# extract the binary from a running image, then scan it directly
crane export registry.k8s.io/kube-apiserver:v1.34.4 - | tar -x ./usr/local/bin/kube-apiserver
govulncheck -mode=binary ./usr/local/bin/kube-apiserverIf GO-2026-4918 (the Go vulnerability database entry backing CVE-2026-33814) shows up in the output, that image still links a pre-fix golang.org/x/net/http2 or standard-library net/http. Further down, this one-liner scales into a loop over an entire registry. First, the component table this command needs to be run against — and why.
The Mechanism: A Frame Size of Zero Never Terminates
The HTTP/2 spec (RFC 7540 §6.5.2) requires SETTINGS_MAX_FRAME_SIZE to be at least 16,384 bytes — it's the ceiling on how large a single frame the peer is willing to receive. Go's HTTP/2 transport accepted a value of 0 without validating it against that floor. Once a peer advertises 0, every attempt by the client to write a DATA frame computes to a zero-length write. Zero bytes isn't progress, so the write logic retries — and keeps retrying, forever, inside CONTINUATION-frame handling, because the loop's exit condition depends on making forward progress that a 0-byte frame size makes structurally impossible.
Security researcher Marwan Atia reported it; Go tracked it as golang/go#78476, opened March 31, 2026. CVE-2026-33814 published May 7, 2026 with a CVSS 3.1 score of 7.5 (network-exploitable, low complexity, no privileges or user interaction, availability impact only — no confidentiality or integrity loss). The fix — validating the settings value and resetting transport state on an invalid one — shipped in golang.org/x/net/http2 v0.53.0, and was backported into the standard library as Go 1.25.10 and 1.26.3, both released May 12, 2026. Per the official vulnerability report, the affected symbol list runs through Transport.RoundTrip, Client.Do, Client.Get, Client.Post, and ten more — essentially the entire standard HTTP client call surface.
Every Control-Plane Component That's a Client of Something
"Client-side bug" undersells the blast radius in Kubernetes specifically, because so much of the control plane's east-west traffic is one Go binary acting as an HTTP/2 client of another:
| Component | Acts as an HTTP/2 client of | What triggers the DoS |
|---|---|---|
| kube-apiserver | Admission webhooks, aggregated/extension API servers, kubelet (exec/logs/port-forward) | A compromised or buggy webhook backend, extension apiserver, or node returns SETTINGS_MAX_FRAME_SIZE=0 |
| kubelet | kube-apiserver | A malicious or mis-terminated apiserver (or a MITM'd control-plane connection) |
| kube-controller-manager / kube-scheduler | kube-apiserver | Same — any apiserver-facing goroutine hangs, not just one watch |
| Cluster API core + infra provider controllers (CAPD, CAPH, CAPO, CAPA, …) | Workload cluster API servers they manage | A compromised or misbehaving workload-cluster apiserver hangs the management-cluster controller reconciling it |
client-go, and anything built on it (kubectl, custom operators, CI deploy tooling) | Whatever apiserver they're pointed at | Same mechanism, wherever that binary runs |
The common thread: this isn't "patch kube-apiserver and move on." It's every Go binary in the fleet that dials out to a peer whose SETTINGS response it doesn't control — and in a Cluster-API-managed fleet, that specifically includes the provider controllers reconciling other clusters' API servers, which is a peer relationship a lot of threat models don't examine closely.
Kubernetes Never Issued Its Own CVE for This
Check the official Kubernetes CVE feed and CVE-2026-33814 isn't on it. That's not an oversight — Kubernetes doesn't mint a K8s-specific CVE for every vulnerable dependency it vendors; it absorbs the fix through routine golang.org/x/net version bumps in ordinary patch releases. SUSE's June 2026 security update for kubernetes1.26 is a clean illustration: the fix landed as version 1.26.15, and it touched kubernetes1.26-apiserver, -kubelet, -scheduler, -controller-manager, -proxy, -kubeadm, and -client simultaneously — nine packages patched in the same release, because they all vendor the same x/net module and none of them got individual security-advisory treatment.
That absorb-and-move-on pattern has a recurrence problem, and it's visible in the same dependency graph today. kubernetes/kubernetes#140092, still open as of this writing, flags that kubectl 1.36.2 ships golang.org/x/net v0.49.0 — vulnerable not to CVE-2026-33814 but to a later, more severe one, CVE-2026-39821, rated 9.6 critical. Two x/net CVEs, months apart, both sitting unpatched in a shipped kubectl binary at different points, both invisible to anyone checking only the Kubernetes project's own CVE list. "Wait for the next upstream Kubernetes patch release" is the thing that's supposed to make this someone else's problem, and the kubectl issue is evidence that it lags — by CVEs, not just days.
Auditing a Cluster-API Fleet's Dependency Versions
The one-liner from the top of this post scales to a fleet with a loop. Point it at every control-plane and provider image your cluster registry serves:
#!/usr/bin/env bash
# scan-fleet-vuln.sh — govulncheck every control-plane / CAPI provider image
IMAGES=(
"registry.k8s.io/kube-apiserver:v1.34.4"
"registry.k8s.io/kube-controller-manager:v1.34.4"
"registry.k8s.io/kube-scheduler:v1.34.4"
"registry.k8s.io/kubelet:v1.34.4"
"registry.k8s.io/cluster-api/cluster-api-controller:v1.9.2"
"your-registry/cluster-api-provider-yourcloud:v1.4.0"
)
for img in "${IMAGES[@]}"; do
echo "== $img =="
bin=$(mktemp)
crane export "$img" - | tar -xO --wildcards '*/kube-*' '*/cluster-api*' 2>/dev/null > "$bin" || continue
govulncheck -mode=binary -json "$bin" | jq -r '.finding.osv // empty' 2>/dev/null
rm -f "$bin"
donegovulncheck -mode=binary reads the compiled binary's symbol table and reports which known-vulnerable functions it actually links — no source access required, which matters for upstream registry.k8s.io images you didn't build. For any Cluster API provider you do build in-house, run source-mode instead against its go.mod/go.sum (govulncheck ./...), since source mode also flags vulnerable code paths that never got compiled into a reachable symbol. The -json flag turns either mode into something a CI job can gate on, rather than a human reading terminal output after the fact. The cadence that actually catches this class of bug is per dependency-bump PR — wiring govulncheck into the same CI check that runs on every Kubernetes/CAPI-provider version bump, not a quarterly audit that finds it three patch releases late.
Why a Self-Hosted Control Plane Owns This Directly
On EKS, GKE, or AKS, this entire episode is invisible. The managed provider bumps the underlying apiserver and kubelet binaries as part of routine platform maintenance, and a fix like this ships as a version-number change on a control plane you don't operate — no CVE ID a customer ever has to react to, because the provider already absorbed it before anyone downstream needed to know.
A Cluster-API-managed fleet doesn't get that absorption for free — the "someone else" who's supposed to notice, patch, and roll the fix out is whoever operates the management cluster. In practice, that noticing moment is a routine "bump Kubernetes patch version" or "bump CAPI provider version" PR that looks exactly like every other dependency-bump PR, with nothing in the diff calling out that this one closes an actively-scored DoS. Without a govulncheck gate in front of it, that PR merges on the strength of "tests pass," and the fix arrives by accident rather than by anyone having verified the exposure it closes.
That's the tradeoff "own the machines" actually carries here: a self-hosted platform never inherits a provider's silent patch cadence, which means the patching burden — and the auditing to catch dependency drift between patches — sits with whoever runs the fleet. Bex's own control-plane images run through the same govulncheck-in-CI gate described above on every Kubernetes and provider version bump, precisely because "wait for the next upstream patch" isn't a substitute for actually checking what's still linked.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Cluster API-managed control plane whose dependency versions you can actually audit. Star the repo on GitHub or deploy your first app today.
Sources
- CVE-2026-33814 — NVD
- GO-2026-4918 — Go Vulnerability Database
- golang/go#78476 — net/http/internal/http2: SETTINGS_MAX_FRAME_SIZE=0 causes Transport to loop infinitely
- SUSE-SU-2026:2325-1 — Security update for kubernetes1.26
- kubernetes/kubernetes#140092 — kubectl 1.36.2 includes vulnerable golang.org/x/net version (CVE-2026-39821)
- Official CVE Feed — Kubernetes
- govulncheck command documentation



