✓ProveIt
Examples / Report #3750295 Saved analysis

CVE-2026-9079: stale proxy password leak

curl · 2026-05-2017.3s analysis · $0.021 in model calls

What the report claims

curl's CURLOPT_PROXYUSERPWD setter fails to clear stale proxy password when overwritten or cleared, leading to credential leakage across requests.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-9079 stale proxy password leak

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

Why this finding?
  • The report is detailed and plausible with a working PoC demonstrating the vulnerability on affected versions.
  • Deterministic checks confirm relevant symbols (CURLOPT_PROXYUSERPWD, STRING_PROXYUSERNAME, STRING_PROXYPASSWORD, setstropt_userpwd) exist in the codebase at the checked revision.
  • However, the critical CURLOPT_PROXYUSERPWD case handler code in lib/setopt.c is not included in the provided source snippets (only the setstropt_userpwd helper is shown).
  • Without the actual setter implementation, the core claims about partial overwrite and missing NULL clearing cannot be verified against the code.
  • A human reviewer should examine the CURLOPT_PROXYUSERPWD case in lib/setopt.c at the relevant revision to confirm the reported logic matches the quoted snippet.
What still needs checking
  • The CURLOPT_PROXYUSERPWD case handler code from lib/setopt.c (the switch case that calls setstropt_userpwd and conditionally updates STRING_PROXYUSERNAME/STRING_PROXYPASSWORD)
How this analysis ran 6 claims · 0 citations verified

Checked against curl master on 2026-05-20 (edfc80c7c473). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 12 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 4360 tokens · $0.0004
3 · Evidence
3 code snippets
real curl source at edfc80c7c473 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
12909 tokens · 14.6s · $0.0207
5 · Receipts
0 of 0 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report and PoC. The issue is plausible and the PoC demonstrates a real credential leak. However, the provided source snippets do not include the CURLOPT_PROXYUSERPWD case handler in lib/setopt.c (only the setstropt_userpwd helper is shown). Could you confirm the exact lines of that case handler in the affected versions, or should we fetch the full lib/setopt.c from the repository to verify the setter logic against your quoted snippet?

Inspect all claims and source code 6 claims · 3 code excerpts

Report claims

C1flaw not verifiable

When CURLOPT_PROXYUSERPWD is set to a username-only value, the old password is not cleared and remains stored.

Read the reasoning

The claim describes the CURLOPT_PROXYUSERPWD setter logic when a username-only value is provided. The actual case handler code for CURLOPT_PROXYUSERPWD in lib/setopt.c is not included in the provided source snippets (only setstropt_userpwd helper function is shown at lines 132-161). Without the setter code, I cannot verify whether the old password is cleared or retained.

C2flaw not verifiable

When CURLOPT_PROXYUSERPWD is set to NULL, the old username and password are not freed, leaving stale credentials.

Read the reasoning

The claim describes behavior when CURLOPT_PROXYUSERPWD is set to NULL. The setter code that would handle the NULL case and free STRING_PROXYUSERNAME/STRING_PROXYPASSWORD is not in the provided source. The provided snippets only show curlx_safefree macro definition and setstropt_userpwd, not the CURLOPT_PROXYUSERPWD case logic.

C3flaw not verifiable

The setter only replaces internal fields when parsed components are non-null, causing partial overwrites.

Read the reasoning

The claim references variables u, p and the string fields, but the actual setter implementation that conditionally writes only non-null components is not in the provided source. The quoted code in the report cannot be verified against the repository at the checked revision.

C4flaw not verifiable

The documented behavior that setting the option to NULL disables its use is violated by the implementation.

Read the reasoning

The claim compares documented behavior to implementation. The external source E2 mentions historical NULL-clearing behavior for CURLOPT_USERPWD but does not address current CURLOPT_PROXYUSERPWD documentation. The current documentation at https://curl.se/libcurl/c/CURLOPT_PROXYUSERPWD.html was not retrieved. The implementation code is also not provided.

C5flaw not verifiable

The implementation parses the input into temporary variables u and p before writing back only existing components.

Read the reasoning

The claim describes the parsing approach using temporary u/p variables. This logic resides in the CURLOPT_PROXYUSERPWD case handler, which is not in the provided source. setstropt_userpwd is shown but it is a helper; the caller's use of its output is not visible.

C6flaw not verifiable

Stale proxy password is sent in Proxy-Authorization header after being overwritten or cleared.

Read the reasoning

The claim describes the runtime effect (stale password sent in Proxy-Authorization). The code_refs are symbol names only; the actual authentication header generation code that reads s->str[STRING_PROXYUSERNAME] and s->str[STRING_PROXYPASSWORD] is not provided.

What the report says about other projects

not addressed When this option is set multiple times, the last set string overrides the previous ones.

not addressed Setting this option to NULL disables its use.

Read the original report (excerpt)

Full report on HackerOne ↗

#### Product Product name: curl / libcurl Product link: https://github.com/curl/curl Suggested CWE: CWE-226: Sensitive Information in Resource Not Removed Before Reuse (https://cwe.mitre.org/data/definitions/226.html); alternative CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (https://cwe.mitre.org/data/definitions/200.html) Affected versions: `8.8.0 <= libcurl <= 8.20.0` are confirmed affected. curl/libcurl `8.21.0-DEV`, commit `b2476a07128fc1e83a0b322fe6eb9dfa761db53d`, is also affected. Unaffected versions: `libcurl <= 8.7.1` do not contain the `CURLOPT_PROXYUSERPWD` setter change that introduced this issue. Introduced in: this issue was introduced by commit `d5e83eb745762f48d8fafadc5df5dd3ae8d8941e` (`url: do not URL decode proxy credentials`) in curl 8.8.0. This commit changed `CURLOPT_PROXYUSERPWD` from writing directly to the internal proxy username/password fields to first parsing the input into temporary `u` / `p` variables and then writing back only the components that exist. Reference: https://github.com/curl/curl/commit/d5e83eb745762f48d8fafadc5df5dd3ae8d8941e #### Summary curl is a widely used command-line network transfer tool, and libcurl is the transfer library it provides for integration into other applications. libcurl provides the `CURLOPT_PROXYUSERPWD` option to set the `username:password` used for HTTP proxy authentication. The official documentation states that when this option is set multiple times, the last set string overrides the previous one, and setting this option to `NULL` disables its use. However, the implementation of `CURLOPT_PROXYUSERPWD` has a stale-state issue. When the same easy handle is first configured with proxy credentials such as `victim:secret`, and later `CURLOPT_PROXYUSERPWD` is set to a username-only value such as `attacker`, or set to `NULL` in an attempt to clear the credentials, the old proxy password `secret` remains stored inside the handle. When a later request is sent through a proxy and proxy authentication is triggered, libcurl sends the old password to the proxy server, leaking a previous request's or previous task's proxy password to a later proxy. This is a credential disclosure vulnerability. It primarily affects applications that reuse libcurl easy handles and allow tasks with different trust levels to configure proxy parameters, such as proxy pools, download serv …

Source code

lib/setopt.clines 132–161 · definition of setstropt_userpwd
132static CURLcode setstropt_userpwd(const char *option, char **userp,
133 char **passwdp)
134{
135 char *user = NULL;
136 char *passwd = NULL;
137
138 DEBUGASSERT(userp);
139 DEBUGASSERT(passwdp);
140
141 /* Parse the login details if specified. If not, then we treat NULL as a
142 hint to clear the existing data */
143 if(option) {
144 size_t len = strlen(option);
145 CURLcode result;
146 if(len > CURL_MAX_INPUT_LENGTH)
147 return CURLE_BAD_FUNCTION_ARGUMENT;
148
149 result = Curl_parse_login_details(option, len, &user, &passwd, NULL);
150 if(result)
151 return result;
152 }
153
154 curlx_free(*userp);
155 *userp = user;
156
157 curlx_free(*passwdp);
158 *passwdp = passwd;
159
160 return CURLE_OK;
161}
lib/escape.clines 105–154 · definition of Curl_urldecode
105CURLcode Curl_urldecode(const char *string, size_t length,
106 char **ostring, size_t *olen,
107 enum urlreject ctrl)
108{
109 size_t alloc;
110 char *ns;
111
112 DEBUGASSERT(string);
113 DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
114
115 alloc = (length ? length : strlen(string));
116 ns = curlx_malloc(alloc + 1);
117
118 if(!ns)
119 return CURLE_OUT_OF_MEMORY;
120
121 /* store output string */
122 *ostring = ns;
123
124 while(alloc) {
125 unsigned char in = (unsigned char)*string;
126 if(('%' == in) && (alloc > 2) &&
127 ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
128 /* this is two hexadecimal digits following a '%' */
129 in = (unsigned char)((curlx_hexval(string[1]) << 4) |
130 curlx_hexval(string[2]));
131 string += 3;
132 alloc -= 3;
133 }
134 else {
135 string++;
136 alloc--;
137 }
138
139 if(((ctrl == REJECT_CTRL) && (in < 0x20)) ||
140 ((ctrl == REJECT_ZERO) && (in == 0))) {
141 curlx_safefree(*ostring);
142 return CURLE_URL_MALFORMAT;
143 }
144
145 *ns++ = (char)in;
146 }
147 *ns = 0; /* terminate it */
148
149 if(olen)
150 /* store output size */
151 *olen = ns - *ostring;
152
153 return CURLE_OK;
154}
lib/curl_setup.hlines 1326–1330 · definition of curlx_safefree
Show 5 lines (not cited by the judge)
1326#define curlx_safefree(ptr) \
1327 do { \
1328 curlx_free(ptr); \
1329 (ptr) = NULL; \
1330 } while(0)