new file mode 100644
@@ -0,0 +1,183 @@
+From 0bfc0eca3c3858f3a3d838b9a2fff258d4b4d088 Mon Sep 17 00:00:00 2001
+From: Stefan Eissing <stefan@eissing.org>
+Date: Fri, 6 Mar 2026 14:54:09 +0100
+Subject: [PATCH] proxy-auth: additional tests
+
+Also eliminate the special handling for socks proxy match.
+
+Closes #20837
+
+CVE: CVE-2026-3784
+Upstream-Status: Backport [https://github.com/curl/curl/commit/5f13a7645e565c5c1a06f3ef86e97afb856fb364]
+
+Backport Changes:
+- In lib/url.c, within proxy_info_matches(), replace strcasecompare()
+ with the upstream fixed curl_strequal().
+ the use of strcasecompare(data->host.name, needle->host.name) was introduced
+ in curl-7_87_0 by this commit.
+ https://github.com/curl/curl/commit/3f039dfd6f0a1d806b94cc2d5548926182485b7e
+- In lib/url.c, a manual modification was made in ConnectionExists()
+ by replacing socks_proxy_info_matches(&m->needle->socks_proxy, &conn->http_proxy)
+ with proxy_info_matches(&needle->socks_proxy, &check->socks_proxy).
+ The socks_proxy_info_matches check in the if block was originally introduced
+ in curl-8_14_0 via the following commit:
+ https://github.com/curl/curl/commit/35e1e7be22df543ead520392af1d8f022e77203c
+- In tests/http/testenv/curl.py file, updated the http_download() method
+ with parameter url_options: Optional[Dict[str,List[str]]] of upstream fixed.
+ The use of Optional was introduced curl-7_88_0 by this commit
+ https://github.com/curl/curl/commit/33ac97e1cb99e04f4833a2fa70636a5bbc23c4ef
+
+(cherry picked from commit 5f13a7645e565c5c1a06f3ef86e97afb856fb364)
+Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
+---
+ lib/url.c | 28 +++++++---------------------
+ tests/http/test_13_proxy_auth.py | 20 ++++++++++++++++++++
+ tests/http/testenv/curl.py | 18 +++++++++++++++---
+ 3 files changed, 42 insertions(+), 24 deletions(-)
+
+diff --git a/lib/url.c b/lib/url.c
+index 6d212680dc..528f9a3b23 100644
+--- a/lib/url.c
++++ b/lib/url.c
+@@ -703,30 +703,16 @@ proxy_info_matches(const struct proxy_info *data,
+ {
+ if((data->proxytype == needle->proxytype) &&
+ (data->port == needle->port) &&
+- strcasecompare(data->host.name, needle->host.name))
+- return TRUE;
++ curl_strequal(data->host.name, needle->host.name)) {
+
++ if(Curl_timestrcmp(data->user, needle->user) ||
++ Curl_timestrcmp(data->passwd, needle->passwd))
++ return FALSE;
++ return TRUE;
++ }
+ return FALSE;
+ }
+
+-static bool
+-socks_proxy_info_matches(const struct proxy_info *data,
+- const struct proxy_info *needle)
+-{
+- if(!proxy_info_matches(data, needle))
+- return FALSE;
+-
+- /* the user information is case-sensitive
+- or at least it is not defined as case-insensitive
+- see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */
+-
+- /* curl_strequal does a case insensitive comparison,
+- so do not use it here! */
+- if(Curl_timestrcmp(data->user, needle->user) ||
+- Curl_timestrcmp(data->passwd, needle->passwd))
+- return FALSE;
+- return TRUE;
+-}
+ #else
+ /* disabled, won't get called */
+ #define proxy_info_matches(x,y) FALSE
+@@ -1085,7 +1071,7 @@ ConnectionExists(struct Curl_easy *data,
+ continue;
+
+ if(needle->bits.socksproxy &&
+- !socks_proxy_info_matches(&needle->socks_proxy,
++ !proxy_info_matches(&needle->socks_proxy,
+ &check->socks_proxy))
+ continue;
+
+diff --git a/tests/http/test_13_proxy_auth.py b/tests/http/test_13_proxy_auth.py
+index abeae0100e..1bf8429a3f 100644
+--- a/tests/http/test_13_proxy_auth.py
++++ b/tests/http/test_13_proxy_auth.py
+@@ -155,3 +155,23 @@ class TestProxyAuth:
+ protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
+ assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
+ if tunnel == 'h2' else 'HTTP/1.1'
++
++ def test_13_10_tunnels_mixed_auth(self, env: Env, httpd, configures_httpd):
++ self.httpd_configure(env, httpd)
++ curl = CurlClient(env=env)
++ url1 = f'http://localhost:{env.http_port}/data.json?1'
++ url2 = f'http://localhost:{env.http_port}/data.json?2'
++ url3 = f'http://localhost:{env.http_port}/data.json?3'
++ xargs1 = curl.get_proxy_args(proxys=False, tunnel=True)
++ xargs1.extend(['--proxy-user', 'proxy:proxy']) # good auth
++ xargs2 = curl.get_proxy_args(proxys=False, tunnel=True)
++ xargs2.extend(['--proxy-user', 'ungood:ungood']) # bad auth
++ xargs3 = curl.get_proxy_args(proxys=False, tunnel=True)
++ # no auth
++ r = curl.http_download(urls=[url1, url2, url3], alpn_proto='http/1.1', with_stats=True,
++ url_options={url1: xargs1, url2: xargs2, url3: xargs3})
++ # only url1 succeeds, others fail, no connection reuse
++ assert r.stats[0]['http_code'] == 200, f'{r.dump_logs()}'
++ assert r.stats[1]['http_code'] == 0, f'{r.dump_logs()}'
++ assert r.stats[2]['http_code'] == 0, f'{r.dump_logs()}'
++ assert r.total_connects == 3, f'{r.dump_logs()}'
+diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py
+index bfd6fdefc7..4de0082c4f 100644
+--- a/tests/http/testenv/curl.py
++++ b/tests/http/testenv/curl.py
+@@ -425,7 +425,8 @@ class CurlClient:
+ with_headers: bool = False,
+ with_profile: bool = False,
+ no_save: bool = False,
+- extra_args: List[str] = None):
++ extra_args: List[str] = None,
++ url_options: Optional[Dict[str,List[str]]] = None):
+ if extra_args is None:
+ extra_args = []
+ if no_save:
+@@ -445,6 +446,7 @@ class CurlClient:
+ ])
+ return self._raw(urls, alpn_proto=alpn_proto, options=extra_args,
+ with_stats=with_stats,
++ url_options=url_options,
+ with_headers=with_headers,
+ with_profile=with_profile)
+
+@@ -582,6 +584,7 @@ class CurlClient:
+
+ def _raw(self, urls, intext='', timeout=None, options=None, insecure=False,
+ alpn_proto: Optional[str] = None,
++ url_options=None,
+ force_resolve=True,
+ with_stats=False,
+ with_headers=True,
+@@ -590,7 +593,8 @@ class CurlClient:
+ args = self._complete_args(
+ urls=urls, timeout=timeout, options=options, insecure=insecure,
+ alpn_proto=alpn_proto, force_resolve=force_resolve,
+- with_headers=with_headers, def_tracing=def_tracing)
++ with_headers=with_headers, def_tracing=def_tracing,
++ url_options=url_options)
+ r = self._run(args, intext=intext, with_stats=with_stats,
+ with_profile=with_profile)
+ if r.exit_code == 0 and with_headers:
+@@ -602,8 +606,10 @@ class CurlClient:
+ def _complete_args(self, urls, timeout=None, options=None,
+ insecure=False, force_resolve=True,
+ alpn_proto: Optional[str] = None,
++ url_options=None,
+ with_headers: bool = True,
+ def_tracing: bool = True):
++ url_sep = []
+ if not isinstance(urls, list):
+ urls = [urls]
+
+@@ -621,7 +627,13 @@ class CurlClient:
+ active_options = options[options.index('--next') + 1:]
+
+ for url in urls:
+- u = urlparse(urls[0])
++ args.extend(url_sep)
++ if url_options is not None:
++ url_sep = ['--next']
++
++ u = urlparse(url)
++ if url_options is not None and url in url_options:
++ args.extend(url_options[url])
+ if options:
+ args.extend(options)
+ if alpn_proto is not None:
+--
+2.44.1
@@ -35,6 +35,7 @@ SRC_URI = " \
file://CVE-2026-1965_p1.patch \
file://CVE-2026-1965_p2.patch \
file://CVE-2026-3783.patch \
+ file://CVE-2026-3784.patch \
"
SRC_URI:append:class-nativesdk = " \