# Root Cause Analysis: CVE-2026-80255

## Summary

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

## Impact

- **Package/component affected:** curl/libcurl, specifically `lib/cookie.c` HTTP `Set-Cookie` parsing as exercised by the curl command-line product.
- **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.
- **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.

## Impact Parity

- **Disclosed/claimed maximum impact:** Information disclosure of a cookie intended to have the `Secure` transport restriction.
- **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.
- **Parity:** `full`
- **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.

## Root Cause

The parser in the vulnerable commit scans each cookie attribute name with:

```c
if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {
```

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

Fix commit [`4f6aa41a0145e930e766775dbe860883d350aa0a`](https://github.com/curl/curl/commit/4f6aa41a0145e930e766775dbe860883d350aa0a) changes the scan delimiter to:

```c
if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) {
```

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

## Reproduction Steps

1. Run `bundle/repro/reproduction_steps.sh` from any directory; it derives portable paths from its own location and honors `PRUVA_ROOT`.
2. 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.
3. 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.
4. 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`.
5. 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.

Expected terminal output includes:

```text
PASS vulnerable attempt 1: secure=FALSE plaintext_cookie_seen=true
PASS vulnerable attempt 2: secure=FALSE plaintext_cookie_seen=true
PASS fixed attempt 1: secure=TRUE plaintext_cookie_seen=false
PASS fixed attempt 2: secure=TRUE plaintext_cookie_seen=false
CONFIRMED: vulnerable curl disclosed sess=SECRET over plaintext HTTP in two attempts; fixed curl blocked it in two attempts.
```

## Evidence

- `bundle/repro/vulnerable-attempt-1.log` — vulnerable transcript, SHA-256 `e995fcb7f7e5422788e3fc188312333fc71aa3bfd1b01c9d57d0586a9bfdfafd`.
- `bundle/repro/vulnerable-attempt-2.log` — second vulnerable transcript, SHA-256 `ba0967b31d8a4e8657df1bdb6b7fd788529a2524cb3d7cba0f5c2865a681edc8`.
- `bundle/repro/fixed-attempt-1.log` — fixed negative control, SHA-256 `b30e5a532dc4390fb7b122b44d2a5aba4128e46a457e5e8870601f34a3ba6b29`.
- `bundle/repro/fixed-attempt-2.log` — second fixed negative control, SHA-256 `c5afc0185862af91e1b0f899fcdea3cb4098635dae13fe95a886509477807201`.
- `bundle/repro/target-identity.log` — exact commits, patch lines, binary hashes, and product versions, SHA-256 `9b2d64973b3600d6852c952279942e6e51c36e7a84f71e7a88f18269658c8488`.
- `bundle/repro/runtime_manifest.json` — entrypoint state, vulnerable source identity, proof artifact list, and checked hashes.
- `bundle/logs/reproduction_steps.log` — overall diagnostics (not hashed as immutable proof because shell-wide `tee` writes it through script exit).
- `bundle/logs/cmake-vulnerable.log`, `build-vulnerable.log`, `cmake-fixed.log`, and `build-fixed.log` — build diagnostics.

The critical vulnerable excerpt is:

```text
COMMIT=621e507300e6c83966567b6ae0352ca82544df13
ATTACKER_HEADER_HEX=5365742d436f6f6b69653a20736573733d5345435245543b09536563757265
COOKIE_JAR_SECURE_FIELD=FALSE
PLAINTEXT_COOKIE_HEADER_SEEN=true
...
Host: tab-cookie.invalid:<dynamic-port>
Cookie: sess=SECRET
```

The fixed negative control contains:

```text
COMMIT=4f6aa41a0145e930e766775dbe860883d350aa0a
COOKIE_JAR_SECURE_FIELD=TRUE
PLAINTEXT_COOKIE_HEADER_SEEN=false
```

Its 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`.

## Recommendations / Next Steps

- Upgrade to curl 8.22.0 or later, or apply the complete change from fixed commit `4f6aa41a0145e930e766775dbe860883d350aa0a` to affected downstream branches.
- Do not treat TAB as a terminal name delimiter before cookie attribute matching; trim accepted optional whitespace and then compare the complete attribute token.
- 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.
- 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.

## Additional Notes

- **Idempotency confirmation:** The final script completed successfully twice consecutively. Each execution itself performed two clean vulnerable attempts and two clean fixed attempts.
- **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.
- **Sanitizers:** None were used. This is a protocol/confidentiality defect rather than a memory-safety crash.
- **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.
- **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.
