{"repro_id":"REPRO-2026-00347","version":6,"title":"curl: Secure cookie attribute bypass when a TAB character precedes the Secure attribute in Set-Cookie","repro_type":"security","status":"published","severity":"low","description":"A horizontal tab immediately before a Set-Cookie Secure attribute is mishandled, so curl stores the cookie as non-Secure and can disclose it on a later plaintext HTTP request to the same host.","root_cause":"# Root Cause Analysis: CVE-2026-80255\n\n## Summary\n\nCVE-2026-80255 is a curl/libcurl HTTP cookie-parser vulnerability in which a response attribute encoded as `;\\tSecure` is not recognized as the `Secure` attribute. In the tested vulnerable fix parent, curl persisted `sess=SECRET` with its secure-only field set to `FALSE`; a later invocation of the real curl command-line product then transmitted `Cookie: sess=SECRET` to a plaintext HTTP peer. The exact fixed commit persisted the same cookie with `Secure=TRUE` and omitted it from the equivalent plaintext request.\n\n## Impact\n\n- **Package/component affected:** curl/libcurl, specifically `lib/cookie.c` HTTP `Set-Cookie` parsing as exercised by the curl command-line product.\n- **Affected versions:** The advisory reports curl 8.13.0 through 8.21.0, introduced by `1aea05a6c2699e80c75936d5`. Per the fixed-commit checkout rule, this run tested the exact fix parent `621e507300e6c83966567b6ae0352ca82544df13` as vulnerable and `4f6aa41a0145e930e766775dbe860883d350aa0a` as fixed.\n- **Risk level and consequences:** Low severity, but with a concrete confidentiality consequence. A server capable of returning the crafted `Set-Cookie` header can cause affected curl clients to disclose a cookie intended for encrypted transport in a later plaintext HTTP request to the same non-localhost origin. The cookie is visible to that HTTP peer and to network observers on the plaintext path.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Information disclosure of a cookie intended to have the `Secure` transport restriction.\n- **Reproduced impact from this run:** The literal secret `sess=SECRET` was captured in plaintext HTTP request bytes in two independent vulnerable product attempts after being set through two independent HTTPS TCP sessions.\n- **Parity:** `full`\n- **Not demonstrated:** No higher impact such as arbitrary memory disclosure or code execution was claimed or attempted. The demonstrated disclosure is bounded to the affected application's cookie data and workflow.\n\n## Root Cause\n\nThe parser in the vulnerable commit scans each cookie attribute name with:\n\n```c\nif(!curlx_str_cspn(&ptr, &name, \";\\t\\r\\n=\")) {\n```\n\nIncluding horizontal TAB in this delimiter set makes parsing of `Set-Cookie: sess=SECRET;\\tSecure` terminate in a way that prevents the following token from matching the recognized `Secure` attribute. curl therefore persists the cookie with the secure field set to `FALSE`. When that cookie jar is loaded for a later request to plaintext `http://tab-cookie.invalid`, curl considers it eligible and emits the secret in the `Cookie` request header.\n\nFix commit [`4f6aa41a0145e930e766775dbe860883d350aa0a`](https://github.com/curl/curl/commit/4f6aa41a0145e930e766775dbe860883d350aa0a) changes the scan delimiter to:\n\n```c\nif(!curlx_str_cspn(&ptr, &name, \";\\r\\n=\")) {\n```\n\nTAB is then handled as leading whitespace around the attribute name, allowing `Secure` to be recognized. The fixed cookie jar records `TRUE`, and curl omits the cookie from plaintext HTTP. The fix also adds upstream regression test 2885 for this exact TAB-before-Secure form.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh` from any directory; it derives portable paths from its own location and honors `PRUVA_ROOT`.\n2. The script reads `bundle/project_cache_context.json`, uses the required prepared repository path when available, resolves fixed commit `4f6aa41a0145e930e766775dbe860883d350aa0a` and its first parent, and verifies the vulnerable and fixed source hunks before building non-sanitized real curl command-line products.\n3. For each isolated attempt, it starts an HTTPS TCP peer, maps the non-localhost name `tab-cookie.invalid` to that peer with curl `--resolve`, and sends the literal wire header `Set-Cookie: sess=SECRET;\\x09Secure`. It then starts a fresh plaintext HTTP peer, loads the resulting cookie jar with the same product build, and captures the request bytes.\n4. It requires two vulnerable attempts to show `secure=FALSE` plus a captured plaintext `Cookie: sess=SECRET`, and two fixed attempts to show `secure=TRUE` with no Cookie header. All curl invocations are bounded by `timeout`.\n5. On success it writes strict, target-bound `bundle/repro/runtime_manifest.json` with SHA-256 hashes of finalized evidence files. Exit status 0 means the issue was confirmed; status 1 means the required divergence was not reproduced.\n\nExpected terminal output includes:\n\n```text\nPASS vulnerable attempt 1: secure=FALSE plaintext_cookie_seen=true\nPASS vulnerable attempt 2: secure=FALSE plaintext_cookie_seen=true\nPASS fixed attempt 1: secure=TRUE plaintext_cookie_seen=false\nPASS fixed attempt 2: secure=TRUE plaintext_cookie_seen=false\nCONFIRMED: vulnerable curl disclosed sess=SECRET over plaintext HTTP in two attempts; fixed curl blocked it in two attempts.\n```\n\n## Evidence\n\n- `bundle/repro/vulnerable-attempt-1.log` — vulnerable transcript, SHA-256 `e995fcb7f7e5422788e3fc188312333fc71aa3bfd1b01c9d57d0586a9bfdfafd`.\n- `bundle/repro/vulnerable-attempt-2.log` — second vulnerable transcript, SHA-256 `ba0967b31d8a4e8657df1bdb6b7fd788529a2524cb3d7cba0f5c2865a681edc8`.\n- `bundle/repro/fixed-attempt-1.log` — fixed negative control, SHA-256 `b30e5a532dc4390fb7b122b44d2a5aba4128e46a457e5e8870601f34a3ba6b29`.\n- `bundle/repro/fixed-attempt-2.log` — second fixed negative control, SHA-256 `c5afc0185862af91e1b0f899fcdea3cb4098635dae13fe95a886509477807201`.\n- `bundle/repro/target-identity.log` — exact commits, patch lines, binary hashes, and product versions, SHA-256 `9b2d64973b3600d6852c952279942e6e51c36e7a84f71e7a88f18269658c8488`.\n- `bundle/repro/runtime_manifest.json` — entrypoint state, vulnerable source identity, proof artifact list, and checked hashes.\n- `bundle/logs/reproduction_steps.log` — overall diagnostics (not hashed as immutable proof because shell-wide `tee` writes it through script exit).\n- `bundle/logs/cmake-vulnerable.log`, `build-vulnerable.log`, `cmake-fixed.log`, and `build-fixed.log` — build diagnostics.\n\nThe critical vulnerable excerpt is:\n\n```text\nCOMMIT=621e507300e6c83966567b6ae0352ca82544df13\nATTACKER_HEADER_HEX=5365742d436f6f6b69653a20736573733d5345435245543b09536563757265\nCOOKIE_JAR_SECURE_FIELD=FALSE\nPLAINTEXT_COOKIE_HEADER_SEEN=true\n...\nHost: tab-cookie.invalid:<dynamic-port>\nCookie: sess=SECRET\n```\n\nThe fixed negative control contains:\n\n```text\nCOMMIT=4f6aa41a0145e930e766775dbe860883d350aa0a\nCOOKIE_JAR_SECURE_FIELD=TRUE\nPLAINTEXT_COOKIE_HEADER_SEEN=false\n```\n\nIts captured plaintext request ends after `Accept: */*` and contains no `Cookie` header. Both peer transcripts record `LISTENING`, `ACCEPTED`, protocol request bytes, and the exact malicious header bytes. The tested environment was Linux x86-64, curl/libcurl `8.22.0-DEV` at the exact commits, OpenSSL 3.5.5, and nghttp2 1.68.0. The target digest is SHA-256 of `git:https://github.com/curl/curl@621e507300e6c83966567b6ae0352ca82544df13`: `99769c35eb8a8ef5d8c6f984733cb9a16c536698292048e4b4ef2b6fe755f495`.\n\n## Recommendations / Next Steps\n\n- Upgrade to curl 8.22.0 or later, or apply the complete change from fixed commit `4f6aa41a0145e930e766775dbe860883d350aa0a` to affected downstream branches.\n- Do not treat TAB as a terminal name delimiter before cookie attribute matching; trim accepted optional whitespace and then compare the complete attribute token.\n- Preserve an end-to-end regression test with the literal `0x09` byte before `Secure`, a non-localhost origin, a persisted cookie jar, and a later plaintext HTTP request. Testing only the jar is weaker than proving that the secret is not emitted.\n- Include both a vulnerable regression fixture and a fixed negative control to avoid false conclusions caused by curl's special secure-cookie treatment of localhost/loopback origins.\n\n## Additional Notes\n\n- **Idempotency confirmation:** The final script completed successfully twice consecutively. Each execution itself performed two clean vulnerable attempts and two clean fixed attempts.\n- **Real boundary:** The primary proof invokes the real curl command-line product. Attacker input crosses accepted HTTPS TCP connections, and disclosure crosses separate plaintext HTTP TCP connections; the vulnerable parser is not called directly by a unit harness.\n- **Sanitizers:** None were used. This is a protocol/confidentiality defect rather than a memory-safety crash.\n- **Edge case avoided:** The peer listens on loopback for containment, but requests use `tab-cookie.invalid` via `--resolve`. Using `127.0.0.1` as the URL host would be an invalid negative control because curl intentionally permits Secure cookies in localhost-like contexts.\n- **Version string nuance:** The exact fix parent and fix commit both report the development version string `8.22.0-DEV`; commit hashes and binary SHA-256 values, not that mutable prose label, bind the two tested builds.\n","cve_id":"CVE-2026-80255","cwe_id":"CWE-201: Information Exposure Through Sent Data","source_url":"https://curl.se/docs/CVE-2026-80255.html","package":{"name":"curl/curl","ecosystem":"github","affected_versions":"curl 8.13.0 through 8.21.0 inclusive; the machine-readable advisory lists 8.13.0, 8.14.0, 8.14.1, 8.15.0, 8.16.0, 8.17.0, 8.18.0, 8.19.0, 8.20.0, and 8.21.0.","fixed_version":"curl 8.22.0"},"reproduced_at":"2026-09-11T10:13:16.872144+00:00","duration_secs":1576.060424,"tool_calls":190,"handoffs":2,"total_cost_usd":6.486431,"agent_costs":{"claim_matcher":0.018067,"judge":0.667691,"learning_policy":0.011701,"repro":2.750214,"support":0.489675,"vuln_variant":2.549083},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.018067},"judge":{"gpt-5.6-sol":0.667691},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.011701},"repro":{"gpt-5.6-sol":2.750214},"support":{"gpt-5.6-sol":0.489675},"vuln_variant":{"gpt-5.6-sol":2.549083}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"not_found","variant_disclosure_state":"not_applicable","quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-09-11T10:13:17.511515+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8892,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":17563,"category":"reproduction_script"},{"path":"bundle/repro/fixed-attempt-1.log","filename":"fixed-attempt-1.log","size":2676,"category":"log"},{"path":"bundle/repro/fixed-attempt-2.log","filename":"fixed-attempt-2.log","size":2676,"category":"log"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1729,"category":"other"},{"path":"bundle/repro/target-identity.log","filename":"target-identity.log","size":650,"category":"log"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1219,"category":"other"},{"path":"bundle/repro/vulnerable-attempt-1.log","filename":"vulnerable-attempt-1.log","size":2751,"category":"log"},{"path":"bundle/repro/vulnerable-attempt-2.log","filename":"vulnerable-attempt-2.log","size":2751,"category":"log"}]}