{"id":"GHSA-2hw4-c9m4-f2fj","kind":"vulnerability","aliases":["GHSA-2hw4-c9m4-f2fj"],"related":[],"title":"knative.dev/pkg admission webhooks allow unbounded request-body memory exhaustion","description":"## ⚠️ Update (verification, 2026-05-19): C-2 confirmed end-to-end — **4.5× amplification observed** (worse than the advisory's conservative 2× claim)\n\nBuilt a Go PoC ([`cmd/poc-c2/main.go`](https://github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/cmd/poc-c2/main.go)) that runs the exact 8-line vulnerable snippet from [`webhook/admission.go:109-117`](https://github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#L109-L117) verbatim under the same `http.HandlerFunc` shape (the full production `admissionHandler` at `:85` requires `*Webhook` + `AdmissionController` + an informer-synced channel + controller-runtime / client-go scaffolding — the bug is the missing `http.MaxBytesReader`, nothing else in the handler). Bound to `127.0.0.1:18098`.\n\nPoC client streamed 2 GiB of `a` bytes over a raw TCP socket. `runtime.MemStats.HeapAlloc` sampler:\n\n```\nbaseline:  HeapAlloc =     0.32 MiB\npeak:      HeapAlloc = 9 217.38 MiB  =  9.00 GiB\nratio:     4.50× the body size\n```\n\nThe advisory predicted ~2× from `io.TeeReader` doubling the body once in `bodyBuffer`. The observed 4.5× ratio comes from the interaction of two amplifiers: `bytes.Buffer.ReadFrom` (used by `TeeReader`-fed `bodyBuffer`) doubles its backing slice on every grow, so peak occupancy during a realloc is `old + new = 3×` the live data — and `json.Decoder` holds its own working buffer in parallel. The advisory's 2× floor is comfortably exceeded.\n\n**Downstream impact reminder**: this library backs every Knative serving / eventing controller, every Tekton Triggers / Pipelines webhook, KEDA, Sigstore policy-controller, Kpack, and many more. A 2 GiB body to **any** of those projects' admission webhook ports OOMs the controller at 4.5× the body — i.e., even a 128 MiB request OOMs a 512 MiB-limited Pod.\n\n---\n\n# Summary\n\n`knative.dev/pkg`'s admission webhook handler reads the incoming HTTP request body via `json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer))` with **no `http.MaxBytesReader` wrapping the body**. Because every consumer of `knative.dev/pkg/webhook` inherits this handler (Knative serving / eventing, Tekton Pipelines, KEDA, Sigstore policy-controller, and others), a single oversized POST to **any of those projects' admission webhook ports** drives the controller pod into memory exhaustion and OOM-kill. Worse, the `io.TeeReader` design keeps a verbatim copy of the body in `bodyBuffer` while the decoder also materializes the parsed struct — so the peak memory usage is roughly **twice the body size**.\n\nThis is the same shape as **CVE-2026-24514** (ingress-nginx) and `GHSA-9539-c3xr-w9vm` (Kyverno admission body OOM) filed today against the same antipattern.\n\n# Affected versions\n\n- `knative.dev/pkg` — `main` HEAD on `github.com/knative/pkg` (verified 2026-05-18 against [`webhook/admission.go`](https://github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go) sha `1ad5a1ce0ee7043081cb50fd1acc6d36a9126844`).\n- Every release that ships the current [`webhook/admission.go`](https://github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go) handler.\n\n# Vulnerable code\n\n[`webhook/admission.go:109-117`](https://github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#L109-L117):\n\n```go\nreview := admissionv1.AdmissionReview{}\nvar bodyBuffer bytes.Buffer\nif err := json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer)).Decode(&review); err != nil {\n    http.Error(w, fmt.Sprintf(\"could not decode body: %v\", err), http.StatusBadRequest)\n    return\n}\n```\n\nThere is no `http.MaxBytesReader` around `r.Body`. The standard library's `json.NewDecoder` will read until EOF or the matching closing `}`. The `io.TeeReader` simultaneously copies every byte read from `r.Body` into `bodyBuffer`, which the rest of the handler uses for the response trailer / observability.\n\n# Downstream impact (the reason this matters)\n\n`knative.dev/pkg/webhook` is the **default admission-webhook scaffolding for the entire Knative + CNCF webhook ecosystem**:\n\n| Consumer | Repo | Webhook surface |\n|---|---|---|\n| Knative serving | `knative/serving` | resource validation / defaulting |\n| Knative eventing | `knative/eventing` | resource validation / defaulting |\n| Tekton Pipelines | `tektoncd/pipeline` | Pipeline / Task / TaskRun validation |\n| Tekton Triggers | `tektoncd/triggers` | EventListener / Trigger validation |\n| KEDA | `kedacore/keda` | ScaledObject / ScaledJob validation |\n| Sigstore policy-controller | `sigstore/policy-controller` | ClusterImagePolicy mutating + validating |\n| Kpack | `buildpacks-community/kpack` | Image / Builder validation |\n| ... | | |\n\nEvery one of these consumers ships the same `io.ReadAll`-equivalent body read with **no upper bound**. A pod-network-reachable attacker can OOM-kill any of them with a single large POST. `failurePolicy=Fail` admission webhooks (the secure-default) then take the namespace's or cluster's admission flow offline until the controller restarts.\n\n# Impact\n\n- **Class**: CWE-770 unbounded allocation. Reachable from any pod-network client.\n- **Severity per consumer**: depends on how each consumer configures `failurePolicy`. For projects with `failurePolicy=Fail` (e.g., Sigstore policy-controller's image-policy enforcement), the consequence is cluster-wide admission outage. For `failurePolicy=Ignore`, the consequence is \"this project's webhook is offline\" but the API server continues admitting requests.\n- **Severity**: matches the broader CVE-2026-24514 / GHSA-9539-c3xr-w9vm. Single-packet DoS on the library; each downstream consumer inherits the bug.\n\n# Reproduction\n\nThe exact same PoC built against Kyverno's admission handler reproduces this:\n\n```python\nimport socket\ntotal = 2 << 30   # 2 GiB\ns = socket.create_connection(('<knative-or-tekton-webhook-host>', 8443))\ns.sendall(b'POST /<admission-path> HTTP/1.1\\r\\nHost: x\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\nConnection: close\\r\\n\\r\\n' % total)\nchunk = b'a' * (1 << 20)\nfor _ in range(total >> 20): s.sendall(chunk)\n```\n\nObserved against the Kyverno admission handler (same antipattern):\n\n```\nbaseline:        HeapAlloc=7 MiB     Sys=22 MiB\nafter read:      HeapAlloc=9500 MiB  Sys=9548 MiB  (4 GiB body → 9.5 GiB peak; io.TeeReader doubles)\n```\n\nFor the knative/pkg version, the `io.TeeReader` doubles the resident memory (one copy in the decoder's accumulating slice, one in `bodyBuffer`). A `kyverno-admission-controller`-equivalent Pod with a 256 MiB memory limit OOM-kills with ~128 MiB of body.\n\n# Suggested fix\n\nWrap the request body with `http.MaxBytesReader` (and add a `http.StatusRequestEntityTooLarge` branch on the `MaxBytesError` path):\n\n```go\nconst maxAdmissionRequestSize = 8 << 20 // 8 MiB — controller-runtime's default; large enough for Kubernetes' own admission objects\nr.Body = http.MaxBytesReader(w, r.Body, maxAdmissionRequestSize)\nvar bodyBuffer bytes.Buffer\nreview := admissionv1.AdmissionReview{}\nif err := json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer)).Decode(&review); err != nil {\n    var mbe *http.MaxBytesError\n    if errors.As(err, &mbe) {\n        http.Error(w, \"admission body exceeds maximum size\", http.StatusRequestEntityTooLarge)\n        return\n    }\n    http.Error(w, fmt.Sprintf(\"could not decode body: %v\", err), http.StatusBadRequest)\n    return\n}\n```\n\nThis propagates a fix to **every downstream consumer** with no API change, and the cap can be made configurable per-server via a new field on the `webhook.Options` struct.\n\n# Disclosure\n\nReporting privately under your security policy. No public disclosure on my side until you have a fix or coordinated timeline. Encourage maintainers to coordinate with downstream consumers (Knative, Tekton, KEDA, Sigstore) on simultaneous release.\n\n# Reporter\n\ntonghuaroot (tonghuaroot@gmail.com)","summary":"The knative.dev/pkg admission webhook handler decodes the incoming request body without a size limit. A single very large POST to a webhook port can make the controller allocate memory until it is OOM-killed.","severity":"medium","cvss":6.5,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"knative.dev/pkg","fixed":["0.0.0-20260531000007-52dbd5ece63f"],"affected":"< 0.0.0-20260531000007-52dbd5ece63f","ecosystem":"go"}],"ecosystems":["go"],"tags":["cwe-770","go","keda","knative","kubernetes","package","self-hosted","sigstore","tekton"],"not_affected_if":["fixed-version","no-knative-pkg-webhook"],"how_to_check":"- Run `go list -m knative.dev/pkg` in each Go module that builds a webhook, and check whether the version is earlier than `0.0.0-20260531000007-52dbd5ece63f`.\n- Check `go.sum` and vendored copies for `knative.dev/pkg` at an affected version.\n- Check whether a controller deployed from these images imports `knative.dev/pkg/webhook`.","fix":"- Upgrade `knative.dev/pkg` to `0.0.0-20260531000007-52dbd5ece63f` or later.\n- Rebuild and redeploy every controller that vendors the library, including Knative, Tekton, KEDA, or Sigstore policy-controller builds you run yourself.\n- Until patched, limit which pods can reach admission webhook ports with network policy.","status":"open","published_at":"2026-10-28T21:20:53+00:00","first_seen_at":"2026-10-28T21:22:55+00:00","triaged_at":"2026-10-28T21:24:12+00:00","updated_at":"2026-10-28T21:24:12+00:00","category":"package","confidence":0.7,"triage":{"at":"2026-10-28T21:24:12Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade `knative.dev/pkg` to `0.0.0-20260531000007-52dbd5ece63f` or later.\n- Rebuild and redeploy every controller that vendors the library, including Knative, Tekton, KEDA, or Sigstore policy-controller builds you run yourself.\n- Until patched, limit which pods can reach admission webhook ports with network policy.","reach":"network","title":"knative.dev/pkg: admission webhooks read unbounded request bodies, exhausting memory","impact":"medium","summary":"The knative.dev/pkg admission webhook handler decodes the incoming request body without a size limit. A single very large POST to a webhook port can make the controller allocate memory until it is OOM-killed.","category":"package","nicknames":[],"who_cares":["go","knative","tekton","keda","sigstore","kubernetes","self-hosted"],"confidence":0.7,"needs_auth":"no","worst_case":"Webhook controller runs out of memory and is killed by a large request.","analyst_note":"","how_to_check":"- Run `go list -m knative.dev/pkg` in each Go module that builds a webhook, and check whether the version is earlier than `0.0.0-20260531000007-52dbd5ece63f`.\n- Check `go.sum` and vendored copies for `knative.dev/pkg` at an affected version.\n- Check whether a controller deployed from these images imports `knative.dev/pkg/webhook`.","needs_analyst":false,"not_affected_if":["fixed-version","no-knative-pkg-webhook"],"who_is_affected":"Any Go service built on knative.dev/pkg/webhook is exposed, because the vulnerable handler is shared scaffolding. This covers many Knative, Tekton, KEDA, and Sigstore policy-controller deployments, and any in-house admission webhook that uses this package. Teams whose webhooks do not use this package, or who run a fixed version, can skip it.","user_interaction":"no","needs_special_setup":"no","severity_if_unknown":"medium"},"cost_usd":0.001386,"input_tokens":6728,"output_tokens":1427,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-05':78C,460C '-117':117C,484C '-18':461C '-19':79C '-2':81C '-2026':426C '-24514':427C '-770':27B '-9539':433C '/knative/pkg':457C '/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/cmd/poc-c2/main.go))':105C '/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#l109-l117)':120C '/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#l109-l117):':487C '/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go)':466C,478C '/pkg':3A,25A,41B,320C,451C '/pkg/webhook':348C,565C '0.32':180C '109':116C,483C '127.0.0.1':161C '128':307C '18098':162C '1ad5a1ce0ee7043081cb50fd1acc6d36a9126844':468C '2':96C,166C,197C,255C,286C '2026':77C,459C '217.38':185C '2hw4':15A,20A '3':239C '4.5':87C,208C,301C '4.50':190C '512':312C '8':110C '85':132C '9':184C '9.00':187C 'admiss':4A,42B,294C,322C,373C,438C 'admissioncontrol':135C 'admissionhandl':130C 'admissionv1.admissionreview':490C 'advisori':93C,195C,253C 'affect':447C 'alloc':67B 'allow':6A 'also':403C 'amplif':88C 'amplifi':216C 'antipattern':446C 'around':519C 'back':225C,265C 'baselin':178C 'bodi':10A,49B,192C,202C,288C,303C,330C,341C,397C,417C,439C,510C 'bodybuff':205C,222C,335C,399C,492C,499C,544C 'bound':159C 'buffer':249C 'bug':149C 'built':98C 'byte':170C,539C 'bytes.buffer':493C 'bytes.buffer.readfrom':217C 'c':80C 'c3xr':435C 'c3xr-w9vm':434C 'c9m4':16A,21A 'channel':140C 'claim':97C 'client':145C,164C 'client-go':144C 'close':533C 'cmd/poc-c2/main.go':102C 'code':481C 'come':210C 'comfort':258C 'confirm':82C 'conserv':95C 'consum':344C 'control':66B,142C,270C,280C,299C,361C,378C 'controller-runtim':141C 'copi':394C,537C 'could':507C 'current':474C 'cve':425C 'cwe':26B 'data':242C 'decod':45B,402C,500C,509C 'default':568C 'design':390C 'doubl':200C,223C 'downstream':260C,557C 'drive':376C 'els':155C 'end':84C,86C 'end-to-end':83C 'eof':529C 'err':495C,502C,512C 'even':305C 'event':269C,354C 'everi':228C,266C,271C,343C,469C,538C 'exact':109C 'exceed':259C 'exhaust':12A,382C 'f2fj':17A,22A 'fed':221C 'file':441C 'floor':256C 'fmt.sprintf':506C 'full':128C 'ghsa':14A,19A,432C 'ghsa-2hw4-c9m4-f2fj':13A,18A 'gib':167C,188C,287C 'github.com':104C,119C,456C,465C,477C,486C 'github.com/knative/pkg':455C 'github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/cmd/poc-c2/main.go))':103C 'github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#l109-l117)':118C 'github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go#l109-l117):':485C 'github.com/knative/pkg/blob/1ad5a1ce0ee7043081cb50fd1acc6d36a9126844/webhook/admission.go)':464C,476C 'go':28B,100C,146C,488C 'grow':229C 'handler':44B,158C,324C,351C,479C,550C 'head':453C 'heapalloc':179C,183C 'hold':245C 'host':35B 'http':328C 'http.error':504C 'http.handlerfunc':125C 'http.maxbytesreader':153C,338C,518C 'http.statusbadrequest':513C 'i.e':304C 'impact':261C,558C 'incom':47B,327C 'inform':138C 'informer-sync':137C 'ingress':429C 'ingress-nginx':428C 'inherit':349C 'interact':213C 'io.teereader':199C,333C,389C,497C,535C 'json.decoder':244C 'json.newdecoder':332C,496C,525C 'keda':29B,276C,357C 'keep':391C 'kill':74B,386C 'knativ':30B,267C,352C 'knative.dev':2A,24A,40B,319C,347C,450C,564C 'knative.dev/pkg':1A,23A,39B,318C,449C 'knative.dev/pkg/webhook':346C,563C 'kpack':281C 'kubernet':31B 'kyverno':437C 'larg':57B 'librari':264C,523C 'limit':53B,315C 'line':111C 'live':241C 'main':452C 'make':64B 'mani':283C 'match':532C 'materi':404C 'matter':562C 'memori':11A,68B,381C,411C 'mib':181C,186C,308C,314C 'mib-limit':313C 'miss':152C 'new':238C 'nginx':430C 'nil':503C 'noth':154C 'observ':89C,207C,556C 'occup':232C 'old':237C 'oom':73B,297C,310C,385C,440C 'oom-kil':72B,384C 'other':363C 'overs':366C 'packag':32B 'parallel':251C 'pars':406C 'peak':182C,231C,410C 'pipelin':274C,356C 'poc':101C,163C 'pod':316C,379C 'polici':279C,360C 'policy-control':278C,359C 'port':62B,296C,375C 'post':58B,367C 'predict':196C 'product':129C 'project':293C,372C 'r.body':334C,498C,520C,542C 'ratio':189C,209C 'raw':173C 'read':325C,527C,540C 'realloc':235C 'reason':560C 'releas':470C 'remind':262C 'request':9A,48B,309C,329C 'request-bodi':8A 'requir':133C 'respons':554C 'rest':547C 'return':514C 'review':489C,501C 'rough':414C 'run':107C 'runtim':143C 'runtime.memstats.heapalloc':176C 'sampler':177C 'scaffold':147C 'self':34B 'self-host':33B 'serv':268C,353C 'sha':467C 'shape':126C,423C 'ship':472C 'sigstor':36B,277C,358C 'simultan':536C 'singl':55B,365C 'size':52B,193C,418C 'slice':226C 'snippet':113C 'socket':175C 'standard':522C 'stream':165C 'struct':407C 'summari':317C 'sync':139C 'tcp':174C 'teeread':220C 'tekton':37B,272C,355C 'today':442C 'trailer':555C 'trigger':273C 'twice':415C 'two':215C 'unbound':7A 'updat':75C 'usag':412C 'use':218C,551C 'v':511C 'var':491C 'verbatim':121C,393C 'verif':76C 'verifi':458C 'version':448C 'via':331C 'vulner':112C,480C 'w':505C 'w9vm':436C 'webhook':5A,43B,61B,134C,275C,295C,323C,374C 'webhook/admission.go':115C,463C,475C,482C 'without':50B 'work':248C 'wors':90C,387C 'wrap':339C","nicknames":[],"mitigations":[],"who_affected":"Any Go service built on knative.dev/pkg/webhook is exposed, because the vulnerable handler is shared scaffolding. This covers many Knative, Tekton, KEDA, and Sigstore policy-controller deployments, and any in-house admission webhook that uses this package. Teams whose webhooks do not use this package, or who run a fixed version, can skip it.","tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-28T21:20:53+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-2hw4-c9m4-f2fj","url":"https://github.com/advisories/GHSA-2hw4-c9m4-f2fj","severity":"medium","published_at":"2026-10-28T21:20:53+00:00","modified_at":"2026-10-28T21:20:55+00:00","first_seen_at":"2026-10-28T21:22:55+00:00","fetched_at":"2026-10-28T21:28:09+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}