Skip to content

CVE-2026-50052: Verified Repro With Script Download

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.

REPRO-2026-00231 varnish/varnish · github Request Smuggling Variant found Jul 6, 2026 CVE entry ↗ .txt
Severity
LOW
CVSS
2.3
Confidence
HIGH
Reproduced in
28m 5s
Tool calls
186
Spend
$4.08
01 · Overview

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).

02 · Severity & CVSS

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 threat level
2.3 / 10 CVSS base

How to Reproduce CVE-2026-50052

$ pruva-verify REPRO-2026-00231
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00231/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-50052

Security impact — reproduced
  • 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
Trigger

malformed HTTP/2 request with :a pseudo-header and content-length

Attack chain
  1. HTTP/2 request parsing in bin/vinyld/http2/cache_http2_hpack.c, serialized to backend HTTP/1 connection
Variants tested

No bypass found: the VSV00019 fix (Tstrcmp -> Tstreq) blocks all tested H2 pseudo-header prefix variants on varnish-9.0.3; the original :a vector still works on the vulnerable 9.0.2 commit.

How the agent worked 505 events · 186 tool calls · 28 min
28 minDuration
186Tool calls
156Reasoning steps
505Events
1Dead-ends
Agent activity over 28 min
Support
20
Hypothesis
2
Repro
278
Judge
19
Variant
182
0:0028:05

Root Cause and Exploit Chain for CVE-2026-50052

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).

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/2 RST_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:

Reproduction Steps

All reproduction is driven by bundle/repro/reproduction_steps.sh. The script performs the following steps:

  1. Reads bundle/project_cache_context.json to find the durable project cache, or falls back to bundle/artifacts/varnish.
  2. Clones https://github.com/varnish/varnish.git into the cache if needed and checks out the vulnerable tag varnish-9.0.2.
  3. Creates a Git worktree at repo-fixed with the fixed tag varnish-9.0.3.
  4. Installs build dependencies (autoconf, automake, libpcre2-dev, libev-dev, libedit-dev, python3-docutils, python3-sphinx, etc.).
  5. Builds both the vulnerable and the fixed source trees with autotools.
  6. Writes the custom bin/vinyltest/tests/x_vsv00019.vtc test to both trees. The test:
    • Starts an HTTP/1 backend server that expects two requests.
    • Starts varnishd with feature=+http2 and a VCL that strips X-Varnish, Via, X-Forwarded-For, and sets Host empty so the byte layout is predictable.
    • Sends an HTTP/2 HEADERS frame with -nostrend containing the pseudo-headers :method, :path, :scheme, a regular content-length: 35, the malicious pseudo-header :a: xx, and a padding header x-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.
  7. Runs the test against the vulnerable build and captures the output to bundle/logs/vulnsmuggle.log.
  8. Runs the same test against the fixed build and captures the output to bundle/logs/fixedsmuggle.log.
  9. Verifies that the vulnerable run passes and that the backend receives GET /smuggled, while the fixed run returns RST_STREAM with PROTOCOL_ERROR.
  10. Writes bundle/repro/runtime_manifest.json and a summary to bundle/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 with x_vsv00019.vtc passed.
  • In fixedsmuggle.log: the server never sees a second request; instead, Varnish responds with RST_STREAM and rst->err: PROTOCOL_ERROR.

Evidence

  • bundle/logs/vulnsmuggle.log – full varnishtest -v output for the vulnerable run.
  • bundle/logs/fixedsmuggle.log – full varnishtest -v output 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 (tag varnish-9.0.2)
  • Fixed source SHA: 0a625649cd40af4b6c10be5e58a2e89a5e275baa (tag varnish-9.0.3)
  • Build configured with --disable-dependency-tracking, built with gcc and GNU make.

Recommendations / Next Steps

  • Fix approach: Apply the upstream fix that replaces Tstrcmp() with Tstreq() and enforces exact pseudo-header name matching in h2h_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 +http2 from the feature parameter until patched.
  • Testing recommendations: Add a regression test that sends an HTTP/2 request with the :a pseudo-header and verifies either a RST_STREAM/PROTOCOL_ERROR response or the absence of a smuggled request on the backend. The upstream f00019.vtc test already covers this for the fixed branches.

Additional Notes

  • Idempotency: reproduction_steps.sh is 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 :a value (xx) and a carefully sized padding header so that the zero-length header injects the exact \r\n needed 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/varnishtest drives a TCP connection to the real varnishd, performs the HTTP/2 SETTINGS handshake, sends the crafted HEADERS/DATA frames, and observes the backend HTTP/1 conversation. The runtime manifest records entrypoint_kind: tcp_peer.

Variant Analysis & Alternative Triggers for CVE-2026-50052

Versions: versions tested: vulnerable 9.0.2 (dc27a2d); fixed 9.0.3 (0a62564). The advisory also lists affected 7.6.0–8.0.1 and 6.0 LTS 6.0.14–6.0.17, with corresponding fixes 8.0.2 and 6.0.18.

No distinct bypass of the official VSV00019 fix was found. The official patch (0a625649cd40af4b6c10be5e58a2e89a5e275baa, varnish-9.0.3) replaces the prefix-matching Tstrcmp macro with the exact-match Tstreq macro in bin/vinyld/http2/cache_http2_hpack.c. This blocks every tested prefix pseudo-header variant (:a, :m, :p, :s, :au, :auth, :) on the fixed version. The original :a attack still reproduces on the vulnerable varnish-9.0.2 (dc27a2dce662015cf79bbda5ff193b6bb74ba2d1), but the fixed version returns RST_STREAM PROTOCOL_ERROR. The upstream regression test f00019.vtc explicitly covers :a, :m, :p, :s and passes on the fixed version, confirming the fix's intent. The only meaningful variant is the same :a pseudo-header prefix; it is not a bypass because it is also blocked by the patched code.

Fix Coverage / Assumptions

  • The fix assumes the only valid H2 request pseudo-headers are :method, :path, :scheme, and :authority, and that they must match exactly in both length and content.
  • It explicitly covers the h2h_addhdr() decision block in bin/vinyld/http2/cache_http2_hpack.c (lines 171–216) by converting each !Tstrcmp(...) check to Tstreq(...) and adding a new Tstreq macro in include/vdef.h.
  • It does not cover unrelated HTTP/1.1 or HTTP/2 desync vectors, and it does not add a generic lookup table for future pseudo-headers. The vtest2 submodule still contains a Tstrcmp macro, but that is test-harness code, not on the request-handling path.

Variant / Alternate Trigger

The variant search focused on the most natural class of alternate triggers: other prefix pseudo-header names that the old Tstrcmp macro would have matched as one of the four standard pseudo-headers. The tested candidates are:

Variant Reason tested Result on vulnerable 9.0.2 Result on fixed 9.0.3
:a Baseline (prefix of :authority) Backend desync reproduced (GET /smuggled received) Blocked: RST_STREAM PROTOCOL_ERROR
: Shortest possible prefix of all four pseudo-headers Rejected as duplicate pseudo-header (: matched :method) Blocked: PROTOCOL_ERROR
:au Longer prefix of :authority Test passed but did not smuggle a second request; malformed h: header emitted Blocked: PROTOCOL_ERROR
:auth Longer prefix of :authority Same as :au Blocked: PROTOCOL_ERROR
:m Prefix of :method Treated as method; no second request observed Blocked: PROTOCOL_ERROR
:p Prefix of :path Treated as path; no second request observed Blocked: PROTOCOL_ERROR
:s Prefix of :scheme Treated as scheme; no second request observed Blocked: PROTOCOL_ERROR
:authorityfoo Negative control (not a prefix) Rejected as unknown pseudo-header Rejected as unknown pseudo-header

Entry point: untrusted HTTP/2 client (tcp_peer) sends a crafted HEADERS frame to varnishd with the +http2 feature enabled. The vulnerable code path is bin/vinyld/http2/cache_http2_hpack.c:h2h_addhdr()Tstrcmp prefix match → :authority branch → memcpy(d->out + 6, "host", 4) and hdr.b += 6, producing a zero-length header that serializes as a bare \r\n to the backend HTTP/1 connection.

  • Product / component: Varnish Cache / Vinyl Cache HTTP/2 frontend (bin/vinyld/http2/cache_http2_hpack.c) and HTTP/1 backend serialization.
  • Affected versions tested: vulnerable 9.0.2 (dc27a2d); fixed 9.0.3 (0a62564). The advisory also lists affected 7.6.0–8.0.1 and 6.0 LTS 6.0.14–6.0.17, with corresponding fixes 8.0.2 and 6.0.18.
  • Risk level: The original vulnerability is high when HTTP/2 is enabled. The variant run confirms that the same root cause can be exercised with the same :a pseudo-header on the vulnerable version, but not on the fixed version.

Impact Parity

  • Disclosed/claimed maximum impact: HTTP/2 request smuggling (backend request desync) leading to cache poisoning, authentication bypass, or information disclosure/manipulation.
  • Reproduced impact from this variant run: Only the baseline :a vector reproduced the backend desync on the vulnerable commit. None of the other prefix variants produced a second attacker-controlled HTTP/1 request, and none of them bypassed the fixed version.
  • Parity: none for the tested variants as a bypass; partial for the alternate trigger on the vulnerable version (same :a vector, no new path).
  • Not demonstrated: Cache poisoning, authentication bypass, or information disclosure chain. Only the primitive (backend request desync on a reused connection) was demonstrated, matching the repro stage.

Root Cause

The underlying bug is the Tstrcmp macro in include/vdef.h:

#define Tstrcmp(t, s) (strncmp((t).b, (s), Tlen(t)))

When t is the HPACK-decoded pseudo-header name, Tstrcmp returns 0 for any string that starts with t. Thus a header named :a (length 2) is considered equal to :authority (length 10) because strncmp(":a", ":authority", 2) == 0. The :authority handling code then does:

memcpy(d->out + 6, "host", 4);
hdr.b += 6;

For a 2-character name (:a), hdr.b ends up equal to the end of the serialized header, producing a zero-length header field. When the HTTP/1 backend serializer emits the header list, the zero-length field is emitted as a bare \r\n, prematurely terminating the HTTP/1 header block. The following DATA frame body (a complete HTTP/1 request) is then parsed by the backend as a second request on the same connection.

The fix changes the match to Tstreq, which requires both the same length and the same bytes, so :a no longer matches :authority and falls into the else if (nm.b[0] == ':') branch that returns H2SE_PROTOCOL_ERROR.

  • Link to fix commit: 0a625649cd40af4b6c10be5e58a2e89a5e275baa (varnish-9.0.3)
  • Link to vulnerable commit: dc27a2dce662015cf79bbda5ff193b6bb74ba2d1 (varnish-9.0.2)

Reproduction Steps

  1. Run the variant reproduction script:
    bash bundle/vuln_variant/reproduction_steps.sh
    
  2. The script reuses the Varnish/Vinyl Cache source and binaries built by the repro stage. It tests the pseudo-header prefix variants listed above against both the vulnerable (9.0.2) and fixed (9.0.3) commits.
  3. The script creates per-variant VTC files under bundle/vuln_variant/ and writes execution logs under bundle/logs/.
  4. Expected evidence:
    • On the vulnerable commit, the baseline :a variant logs EXPECT req.url (/smuggled) == "/smuggled" match and the VTC passes.
    • On the fixed commit, every prefix variant logs rst->err: PROTOCOL_ERROR and does not match the smuggled request.
    • The upstream f00019.vtc regression test passes on the fixed commit.

Evidence

  • Variant summary: bundle/logs/variant_summary.txt
  • Baseline :a vulnerable log: bundle/logs/variant_ca_vuln.log
  • Baseline :a fixed log: bundle/logs/variant_ca_fixed.log
  • Upstream regression test log: bundle/logs/variant_f00019_fixed.log
  • Per-variant logs: bundle/logs/variant_<safe_name>_{vuln,fixed}.log

Key excerpts from the summary:

HEADER               VULN_SM    VULN_PASS  FIX_SM     FIX_RST
:a                   yes        yes        no         yes
:                    no         no         no         yes
:au                  no         yes        no         yes
:auth                no         yes        no         yes
:m                   no         no         no         yes
:p                   no         no         no         yes
:s                   no         no         no         yes
:authorityfoo        no         no         no         yes
f00019_fixed=passed

Recommendations / Next Steps

  • The official fix is complete for the reported root cause. No additional code change is required for the prefix-match class of variants.
  • Hardening suggestion: replace the four explicit if/else if pseudo-header checks with a bounded lookup table or a length-checked switch so that future pseudo-headers cannot be accidentally matched by prefix.
  • Operators who cannot upgrade should continue to follow the VSV00019 advisory: disable HTTP/2 or deploy the VCL-level desync-closing mitigation.
  • Future variant work should monitor for other HTTP/2 desync primitives not addressed by this patch (e.g., HTTP/1.1 interleaving, CONTINUATION frame edge cases, or new H2 extensions).

Additional Notes

  • The script is idempotent: it does not mutate the source checkout and can be rerun; it will overwrite the per-variant VTC files and logs with fresh results.
  • The repo checkout state was preserved at the vulnerable 9.0.2 tag; the fixed code was tested through the existing repo-fixed worktree at 9.0.3.
  • The only zero-length-header smuggling vector observed was the original :a prefix of :authority. All other prefixes either matched a different pseudo-header (producing a non-empty malformed header) or were rejected as duplicates/unknown.

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.

Event 1/40
0:004:21
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-50052 · REPRO-20
0:03
0:04
web search
0:06
0:07
0:09
web search
0:14
0:16
web search
0:16
0:26
1:06
1:32
1:32
extract_facts
no facts extracted
1:33
1:33
1:33
supportrepro
4:11
4:13
4:13
4:13
4:14
4:14
4:14
4:15
4:15
4:17
4:19
web search
4:21
08 · How to Fix

How to Fix CVE-2026-50052

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-50052 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-50052

How does the CVE-2026-50052 HTTP/2 request-smuggling attack work?

The attacker sends a malformed HTTP/2 request that exploits the :a/:authority pseudo-header confusion to truncate the backend HTTP/1 header block. By placing a content-length header before the truncated header and then sending a DATA frame containing a full valid HTTP/1 request, the attacker desynchronizes the frontend and backend so the backend parses the DATA frame's contents as a second request on a reused connection.

Which Varnish/Vinyl Cache versions are affected by CVE-2026-50052, and where is it fixed?

Affected versions include Vinyl Cache 9.0.0, Varnish Cache by Varnish Software up to 9.0.2, Varnish Cache releases 7.6.0 through 8.0.1, and Varnish Cache 6.0 LTS 6.0.14 through 6.0.17. Fixed versions are Vinyl Cache 9.0.1, Varnish Cache by Varnish Software 9.0.3, Varnish Cache 8.0.2, and Varnish Cache 6.0 LTS 6.0.18. The attack vector only exists when HTTP/2 support is enabled; it is disabled by default.

How severe is CVE-2026-50052?

Pruva rates this reproduction as low severity, reflecting that HTTP/2 is disabled by default and must be explicitly enabled. The underlying desync can lead to cache poisoning, authentication bypass, or information disclosure/manipulation when the feature is on.

How can I reproduce CVE-2026-50052?

Download the verified script from this page and run it in an isolated environment against Varnish/Vinyl Cache with feature=+http2 enabled (reproduced on varnish-9.0.2). It sends a malformed HTTP/2 request with a truncated :authority pseudo-header followed by a smuggled HTTP/1 request in a DATA frame and shows the backend desynchronizing, then confirms the fixed version rejects the malformed header.
11 · References

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.