✓ProveIt
Examples / Report #3584903 Saved analysis

CVE-2026-3784: wrong proxy connection reuse with credentials

curl · 2026-03-0411.7s analysis · $0.014 in model calls

What the report claims

libcurl may reuse an existing HTTP proxy CONNECT tunnel without matching proxy credentials when selecting a reusable connection.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-3784 wrong proxy connection reuse with credentials

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

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • proxy_info_matches() at lib/url.c:589-595 compares only proxytype, port, and host.name — it omits user and passwd fields
  • url_match_proxy_use() at lib/url.c:934 calls proxy_info_matches for HTTP proxy connections, so credential differences are ignored during connection reuse
  • socks_proxy_info_matches() at lib/url.c:600-616 explicitly compares user and passwd, demonstrating the intended pattern is missing for HTTP proxy
  • Shared connection cache (CURLSH + CURL_LOCK_DATA_CONNECT) is a documented feature; when used, this flaw allows a transfer with invalid proxy credentials to reuse a CONNECT tunnel authenticated with different credentials
  • This is a security issue: it bypasses proxy authentication policy in multi-tenant or shared-pool architectures
How this analysis ran 6 claims · 12 citations verified

Checked against curl master on 2026-03-04 (38ee35353c1d). Test 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 · 10 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 1913 tokens · $0.0002
3 · Evidence
2 code snippets
real curl source at 38ee35353c1d
4 · Judgment
Nemotron-3-Ultra-550b-a55b
8417 tokens · 9.0s · $0.0136
5 · Receipts
12 of 14 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. The code confirms the flaw: proxy_info_matches() (lib/url.c:589-595) compares only proxytype, port, and host for HTTP proxy, while socks_proxy_info_matches() (lib/url.c:600-616) correctly compares user and passwd. When a shared connection cache (CURLSH + CURL_LOCK_DATA_CONNECT) is used, url_match_proxy_use() reuses HTTP CONNECT tunnels without checking credentials. This is a valid security issue allowing proxy authentication bypass. We will work on a fix.

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

Report claims

C1flaw supported

proxy_info_matches() compares proxy type, host, and port but does not compare proxy username or password

Read the reasoning

The proxy_info_matches function at lines 589-595 explicitly compares only proxytype, port, and host.name. It does not compare the user or passwd fields of the proxy_info struct.

C2location supported

url_match_proxy_use() calls proxy_info_matches()

Read the reasoning

At line 934, url_match_proxy_use calls proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy) when the httpproxy bit is set.

C3trigger supported

shared connection cache (CURLSH + CURL_LOCK_DATA_CONNECT)

Read the reasoning

The deterministic checks confirm both CURL_LOCK_DATA_CONNECT and CURLSHOPT_SHARE exist in the codebase, which are the mechanisms for shared connection caches.

lib/url.c:0 lib/url.c:0
C4impact supported

a transfer using different proxy credentials can reuse a previously authenticated tunnel

Read the reasoning

Since url_match_proxy_use uses proxy_info_matches (which lacks credential checks) for HTTP proxy connections, and the shared connection cache (CURLSH + CURL_LOCK_DATA_CONNECT) enables connection reuse across transfers, a transfer with different proxy credentials can reuse a previously authenticated HTTP CONNECT tunnel. Notably, socks_proxy_info_matches (lines 600-616) does compare credentials, showing the pattern exists but is missing for HTTP proxy.

C5reproduction not verifiable

second transfer configured with bad:bad succeeds through the existing tunnel without issuing a new CONNECT

Read the reasoning

The reproduction steps and PoC script are not in the provided code. The code supports the mechanism described, but the specific reproduction cannot be verified from source alone.

C6reproduction not verifiable

wrapper/daemon model where multiple clients submit jobs to a process holding the shared connection cache

Read the reasoning

The wrapper/daemon model (curl_wrap_daemon.c) is not in the provided code. The code supports the shared connection cache mechanism that would enable this scenario, but the specific daemon code cannot be verified.

Read the original report (excerpt)

Full report on HackerOne ↗

Summary libcurl may reuse an existing HTTP proxy CONNECT tunnel without matching proxy credentials when selecting a reusable connection. In lib/url.c, url_match_proxy_use() calls proxy_info_matches() (lib/url.c:930-935 → lib/url.c:589-595), and that matcher compares proxy type, host, and port but does not compare proxy username or password. When a shared connection cache is used (CURLSH + CURL_LOCK_DATA_CONNECT), a transfer using different proxy credentials can reuse a previously authenticated tunnel. In my reproduction the proxy receives only one CONNECT request with good:good, while a second transfer configured with bad:bad succeeds through the existing tunnel without issuing a new CONNECT. This was reproduced both with sequential libcurl transfers and with a wrapper/daemon model where multiple clients submit jobs to a process holding the shared connection cache. Control tests show the issue does not occur when connections are not shared (noshare) or when the destination host differs. Affected version Reproduced on: curl 8.18.0 (x86_64-pc-linux-gnu) libcurl/8.18.0 OpenSSL/3.5.5 zlib/1.3.1 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.2 libssh2/1.11.1 nghttp2/1.64.0 ngtcp2/1.16.0 nghttp3/1.12.0 librtmp/2.3 mit-krb5/1.22.1 OpenLDAP/2.6.10 Release information: Release-Date: 2026-01-07 security patched: 8.18.0-2 Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd Steps To Reproduce 1. Build the PoC program using libcurl. 2. Start the local proxy reproducer script. python3 /tmp/run_share_connect_poc.py 3. The script performs two transfers via a proxy. Transfer 1: proxy credentials: good:good Transfer 2: proxy credentials: bad:bad MODE noshare - first request succeeds - second request fails with proxy authentication error - proxy observes two CONNECT requests MODE share - both requests succeed - proxy observes only one CONNECT request - CONNECT uses good:good - second request (bad:bad) is sent through existing tunnel Run the wrapper daemon scenario (curl_wrap_daemon.c) to simulate a service using a shared connection pool. The second client using bad:bad credentials still succeeds while …

Source code

lib/url.clines 905–960 · lines 930-935 cited by the report
905 if(!Curl_conn_is_ssl(conn, FIRSTSOCKET))
906 return FALSE;
907 }
908 else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
909 /* If the protocol does not allow reuse of SSL connections OR
910 is of another protocol family, not a match. */
911 if(!(m->needle->scheme->flags & PROTOPT_SSL_REUSE) ||
912 (get_protocol_family(conn->scheme) != m->needle->scheme->protocol))
913 return FALSE;
914 }
915 return TRUE;
916}
917
918#ifndef CURL_DISABLE_PROXY
919static bool url_match_proxy_use(struct connectdata *conn,
920 struct url_conn_match *m)
921{
922 if(m->needle->bits.httpproxy != conn->bits.httpproxy ||
923 m->needle->bits.socksproxy != conn->bits.socksproxy)
924 return FALSE;
925
926 if(m->needle->bits.socksproxy &&
927 !socks_proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy))
928 return FALSE;
929
930 if(m->needle->bits.httpproxy) {
C2: url_match_proxy_use checks httpproxy bit
931 if(m->needle->bits.tunnel_proxy != conn->bits.tunnel_proxy)
932 return FALSE;
933
934 if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy))
C2: calls proxy_info_matches for http_proxy
C4: url_match_proxy_use uses proxy_info_matches for HTTP proxy
935 return FALSE;
936
937 if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) {
938 /* https proxies come in different types, http/1.1, h2, ... */
939 if(m->needle->http_proxy.proxytype != conn->http_proxy.proxytype)
940 return FALSE;
941 /* match SSL config to proxy */
942 if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) {
943 DEBUGF(infof(m->data,
944 "Connection #%" FMT_OFF_T
945 " has different SSL proxy parameters, cannot reuse",
946 conn->connection_id));
947 return FALSE;
948 }
949 /* the SSL config to the server, which may apply here is checked
950 * further below */
951 }
952 }
953 return TRUE;
954}
955#else
956#define url_match_proxy_use(c, m) ((void)(c), (void)(m), TRUE)
957#endif
958
959#ifndef CURL_DISABLE_HTTP
960static bool url_match_http_multiplex(struct connectdata *conn,
lib/url.clines 564–620 · lines 589-595 cited by the report
564 *
565 * Return a TRUE, iff the transfer can be done over an (appropriate)
566 * multiplexed connection.
567 */
568static bool xfer_may_multiplex(const struct Curl_easy *data,
569 const struct connectdata *conn)
570{
571#ifndef CURL_DISABLE_HTTP
572 /* If an HTTP protocol and multiplexing is enabled */
573 if((conn->scheme->protocol & PROTO_FAMILY_HTTP) &&
574 (!conn->bits.protoconnstart || !conn->bits.close)) {
575
576 if(Curl_multiplex_wanted(data->multi) &&
577 (data->state.http_neg.allowed & (CURL_HTTP_V2x | CURL_HTTP_V3x)))
578 /* allows HTTP/2 or newer */
579 return TRUE;
580 }
581#else
582 (void)data;
583 (void)conn;
584#endif
585 return FALSE;
586}
587
588#ifndef CURL_DISABLE_PROXY
589static bool proxy_info_matches(const struct proxy_info *data,
C1: proxy_info_matches function definition starts
590 const struct proxy_info *needle)
591{
592 if((data->proxytype == needle->proxytype) &&
C1: compares proxytype, port, and host.name only
C4: proxy_info_matches lacks credential comparison
593 (data->port == needle->port) &&
C1: compares port
594 curl_strequal(data->host.name, needle->host.name))
C1: compares host.name via curl_strequal
595 return TRUE;
C1: returns TRUE if those three match; no user/passwd comparison
596
597 return FALSE;
598}
599
600static bool socks_proxy_info_matches(const struct proxy_info *data,
601 const struct proxy_info *needle)
602{
603 if(!proxy_info_matches(data, needle))
C4: socks_proxy_info_matches DOES compare user/passwd (contrast)
604 return FALSE;
605
606 /* the user information is case-sensitive
607 or at least it is not defined as case-insensitive
608 see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */
609
610 /* curl_strequal does a case insensitive comparison,
611 so do not use it here! */
612 if(Curl_timestrcmp(data->user, needle->user) ||
C4: explicit user comparison for SOCKS
613 Curl_timestrcmp(data->passwd, needle->passwd))
C4: explicit passwd comparison for SOCKS
614 return FALSE;
615 return TRUE;
616}
617#endif
618
619/* A connection has to have been idle for less than 'conn_max_idle_ms'
620 (the success rate is too low after this), or created less than