CVE-2026-50052: Verified Reproduction
CVE-2026-50052: HTTP/2 request smuggling in Vinyl Cache and Varnish Cache VSV00019
CVE-2026-50052 is verified against varnish/varnish · github. Vulnerability class: Request Smuggling. This low reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00231.
What Is CVE-2026-50052?
CVE-2026-50052 (VSV00019) is an HTTP/2 request-smuggling vulnerability (CWE-444) in Varnish Cache and Vinyl Cache caused by a deficiency in HTTP/2 request parsing. Pruva reproduced it (reproduction REPRO-2026-00231).
CVE-2026-50052 Severity & CVSS Score
CVE-2026-50052 is rated low severity, with a CVSS base score of 2.3 out of 10.
Low — limited impact or hard to exploit. Address in the normal cycle.
How to Reproduce CVE-2026-50052
pruva-verify REPRO-2026-00231 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00231/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-50052
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
malformed HTTP/2 request with :a pseudo-header and content-length
- HTTP/2 request parsing in bin/vinyld/http2/cache_http2_hpack.c, serialized to backend HTTP/1 connection
How the agent worked
Root Cause and Exploit Chain for CVE-2026-50052
CVE-2026-50052 (VSV00019) is an HTTP/2 request-smuggling vulnerability in Varnish/Vinyl Cache. When HTTP/2 support is enabled (feature=+http2), an attacker can send a malformed HTTP/2 request whose pseudo-header name :a is accepted as a prefix of :authority. The HPACK-to-HTTP/1 serialization in bin/vinyld/http2/cache_http2_hpack.c rewrites the header in-place, turning it into a zero-length header on the backend wire. This injects a bare \r\n into the HTTP/1 request header block, prematurely terminating the headers. By placing a content-length before the truncated header and then sending a DATA frame containing a valid HTTP/1 request, the attacker desynchronizes the frontend and backend, causing the backend to parse the DATA frame as a second request on a reused connection.
- Product / component: Varnish Cache / Vinyl Cache HTTP/2 frontend (
bin/vinyld/http2/cache_http2_hpack.c) and HTTP/1 backend serialization. - Affected versions: Varnish/Vinyl Cache 9.0.2 (and earlier affected lines 7.6.0–8.0.1, 6.0 LTS 6.0.14–6.0.17, Vinyl 9.0.0). This run reproduced the issue on
varnish-9.0.2(dc27a2dce662015cf79bbda5ff193b6bb74ba2d1). - Risk level / consequences: High. The attacker can desync a reused backend connection, leading to HTTP request smuggling, cache poisoning, and potential authentication bypass or information disclosure, as described in VSV00019.
Impact Parity
- Disclosed/claimed maximum impact: HTTP request smuggling (backend request desync) via malformed HTTP/2 requests, with follow-on effects such as cache poisoning and authentication bypass.
- Reproduced impact from this run: We demonstrated a real backend request desync: the vulnerable Varnish forwarded the attacker's crafted HTTP/2 request to a backend HTTP/1 server, and the backend received a second, attacker-controlled HTTP/1 request (
GET /smuggled) on the same pooled connection. The same payload against the fixed version (varnish-9.0.3) was rejected with an HTTP/2RST_STREAM/PROTOCOL_ERROR. - Parity:
partial– the exact network-level desync was reproduced, but the current harness stops short of demonstrating the downstream cache-poisoning / auth-bypass consequences. Those are the natural result of the desync primitive and are claimed by the advisory. - Not demonstrated: Full cache poisoning or credential theft. The PoC only proves the smuggling primitive that enables those attacks.
Root Cause
The bug is in the pseudo-header matching logic in bin/vinyld/http2/cache_http2_hpack.c, specifically in h2h_addhdr().
The vulnerable code used a macro Tstrcmp() defined in include/vdef.h as:
#define Tstrcmp(t, s) (strncmp((t).b, (s), Tlen(t)))
This compares only the first Tlen(t) bytes of the target string. As a result, any header name that is a prefix of :authority (e.g., :a, :au, :autho) is treated as :authority and enters the :authority branch. That branch writes "host" over the last four bytes of the header name and advances the start pointer by 6, leaving a zero-length header in the internal header array.
When the request is serialized to HTTP/1 for the backend, HTTP1_Write() emits a bare \r\n for that zero-length header, ending the header block early. The remaining headers (and any DATA frame) become the request body, letting a second HTTP/1 request follow on the same persistent backend connection.
The official fix is the Tstreq() macro that checks both length and content, and the migration of h2h_addhdr() to use exact equality for all four pseudo-header names. The fixed version was reproduced from varnish-9.0.3 (0a625649cd40af4b6c10be5e58a2e89a5e275baa).
Relevant upstream references:
- VSV00019 advisory: https://www.varnish.org/docs/security/vsv00019/
- Blog post with detailed exploit mechanics: https://blog.calif.io/p/mad-bugs-my-cousin-vinyl-cve-2026
- Fixed release tag:
varnish-9.0.3 - Vulnerable reproduction tag:
varnish-9.0.2
Reproduction Steps
All reproduction is driven by bundle/repro/reproduction_steps.sh. The script performs the following steps:
- Reads
bundle/project_cache_context.jsonto find the durable project cache, or falls back tobundle/artifacts/varnish. - Clones
https://github.com/varnish/varnish.gitinto the cache if needed and checks out the vulnerable tagvarnish-9.0.2. - Creates a Git worktree at
repo-fixedwith the fixed tagvarnish-9.0.3. - Installs build dependencies (
autoconf,automake,libpcre2-dev,libev-dev,libedit-dev,python3-docutils,python3-sphinx, etc.). - Builds both the vulnerable and the fixed source trees with autotools.
- Writes the custom
bin/vinyltest/tests/x_vsv00019.vtctest to both trees. The test:- Starts an HTTP/1 backend server that expects two requests.
- Starts
varnishdwithfeature=+http2and a VCL that stripsX-Varnish,Via,X-Forwarded-For, and setsHostempty so the byte layout is predictable. - Sends an HTTP/2 HEADERS frame with
-nostrendcontaining the pseudo-headers:method,:path,:scheme, a regularcontent-length: 35, the malicious pseudo-header:a: xx, and a padding headerx-pad: aaaaaaaaaaaaaaaa. - Sends a DATA frame whose 35-byte payload is the smuggled HTTP/1 request
GET /smuggled HTTP/1.1\r\nHost: x\r\n\r\n.
- Runs the test against the vulnerable build and captures the output to
bundle/logs/vulnsmuggle.log. - Runs the same test against the fixed build and captures the output to
bundle/logs/fixedsmuggle.log. - Verifies that the vulnerable run passes and that the backend receives
GET /smuggled, while the fixed run returnsRST_STREAMwithPROTOCOL_ERROR. - Writes
bundle/repro/runtime_manifest.jsonand a summary tobundle/artifacts/summary.txt.
Expected evidence of reproduction:
- In
vulnsmuggle.log: lines showing the backend received the first request (POST /attack), then a second request whose URL is/smuggled, and the test exits withx_vsv00019.vtc passed. - In
fixedsmuggle.log: the server never sees a second request; instead, Varnish responds withRST_STREAMandrst->err: PROTOCOL_ERROR.
Evidence
bundle/logs/vulnsmuggle.log– fullvarnishtest -voutput for the vulnerable run.bundle/logs/fixedsmuggle.log– fullvarnishtest -voutput for the fixed run.bundle/artifacts/summary.txt– parsed verdict summary (commit SHAs, boolean flags).bundle/repro/runtime_manifest.json– structured runtime evidence manifest.
Key excerpts from the vulnerable run (bundle/logs/vulnsmuggle.log):
**** s1 rxhdr|POST /attack HTTP/1.1\r
**** s1 rxhdr|scheme: http\r
**** s1 rxhdr|content-length: 35\r
**** s1 rxhdr|\r
**** s1 rxhdrlen = 59
...
**** s1 c-l|x-pad: aaaaaaaaaaaaaaaa\r
**** s1 c-l|Host: \r
**** s1 bodylen = 35
...
**** s1 rxhdr|GET /smuggled HTTP/1.1\r
...
**** s1 EXPECT req.url (/smuggled) == "/smuggled" match
...
# top TEST ... x_vsv00019.vtc passed (1.913)
Key excerpt from the fixed run (bundle/logs/fixedsmuggle.log):
*** c1 rx: stream: 1, type: RST_STREAM (3), flags: 0x00, size: 4
**** c1.1 rst->err: PROTOCOL_ERROR (1)
---- c1.1 Frame #1 for rxresp was of type RST_STREAM (3) instead of HEADERS (1)
...
# top TEST ... x_vsv00019.vtc FAILED (0.601) signal=6
Environment details captured:
- Vulnerable source SHA:
dc27a2dce662015cf79bbda5ff193b6bb74ba2d1(tagvarnish-9.0.2) - Fixed source SHA:
0a625649cd40af4b6c10be5e58a2e89a5e275baa(tagvarnish-9.0.3) - Build configured with
--disable-dependency-tracking, built withgccand GNU make.
Recommendations / Next Steps
- Fix approach: Apply the upstream fix that replaces
Tstrcmp()withTstreq()and enforces exact pseudo-header name matching inh2h_addhdr(). No other code should rely on prefix-only matching. - Upgrade guidance: Upgrade to a non-affected version: Varnish/Vinyl Cache 9.0.3, 8.0.2, or 6.0 LTS 6.0.18, or disable HTTP/2 by removing
+http2from thefeatureparameter until patched. - Testing recommendations: Add a regression test that sends an HTTP/2 request with the
:apseudo-header and verifies either aRST_STREAM/PROTOCOL_ERRORresponse or the absence of a smuggled request on the backend. The upstreamf00019.vtctest already covers this for the fixed branches.
Additional Notes
- Idempotency:
reproduction_steps.shis safe to run multiple times. It checks for existing clones and built binaries and skips expensive build steps when they are already present. Two consecutive runs in this session both confirmed the vulnerability. - Edge cases / limitations: The PoC uses a short
:avalue (xx) and a carefully sized padding header so that the zero-length header injects the exact\r\nneeded to terminate the backend header block. If Varnish added extra backend headers that could not be controlled, the byte offsets would need to be recalculated; the VCL used in the test removes those variables to make the reproduction deterministic. The PoC demonstrates the desync primitive but does not exercise the full cache-poisoning chain described in the advisory. - Entrypoint kind: This reproduction uses the real network protocol path:
vinyltest/varnishtestdrives a TCP connection to the realvarnishd, performs the HTTP/2 SETTINGS handshake, sends the crafted HEADERS/DATA frames, and observes the backend HTTP/1 conversation. The runtime manifest recordsentrypoint_kind: tcp_peer.
CVE-2026-50052 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-50052
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-50052
FAQ: CVE-2026-50052
How does the CVE-2026-50052 HTTP/2 request-smuggling attack work?
Which Varnish/Vinyl Cache versions are affected by CVE-2026-50052, and where is it fixed?
How severe is CVE-2026-50052?
How can I reproduce CVE-2026-50052?
References for CVE-2026-50052
Authoritative sources for CVE-2026-50052 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.