fix: do not forward Authorization/Cookie to subdomains on redirect

Closes https://github.com/dart-lang/sdk/pull/63256

GitOrigin-RevId: 1cf4d94690343b4df087e7c583154a36580fff71
Change-Id: I186e9338e36ee72714d385444e6fbf3b217ec4ff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498424
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
mohammadmseet-hue
2026-04-29 00:54:05 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 1e46f62954
commit 890f7e8aa9
2 changed files with 64 additions and 13 deletions
+7 -11
View File
@@ -3059,11 +3059,8 @@ class _HttpClient implements HttpClient {
);
}
static bool _isSubdomain(Uri subdomain, Uri domain) {
return (subdomain.isScheme(domain.scheme) &&
subdomain.port == domain.port &&
(subdomain.host == domain.host ||
subdomain.host.endsWith("." + domain.host)));
static bool _isSameOrigin(Uri a, Uri b) {
return a.isScheme(b.scheme) && a.host == b.host && a.port == b.port;
}
// Only visible for testing.
@@ -3072,17 +3069,16 @@ class _HttpClient implements HttpClient {
required Uri originalUrl,
required Uri redirectUrl,
}) {
if (_isSubdomain(redirectUrl, originalUrl)) {
return true;
}
const nonRedirectHeaders = [
// It is only safe to copy sensitive headers when redirecting to the
// same origin (RFC 6454 section 4).
const sensitiveHeaders = [
"authorization",
"www-authenticate",
"cookie",
"cookie2",
];
return !nonRedirectHeaders.contains(headerKey.toLowerCase());
return !sensitiveHeaders.contains(headerKey.toLowerCase()) ||
_isSameOrigin(redirectUrl, originalUrl);
}
Future<_HttpClientRequest> _openUrlFromRequest(
+57 -2
View File
@@ -582,15 +582,70 @@ void testShouldCopyHeadersOnRedirect() {
);
checkShouldCopyHeader("cat", "http://foo.com", "http://foo.com:80/foo", true);
// Redirect to subdomain.
// Redirect to subdomain. Credential-carrying headers MUST NOT be forwarded
// to a subdomain (the subdomain can be attacker-controlled on shared apex
// hosts, and RFC 6265 host-only cookies must not be sent to subdomains).
// Non-sensitive headers continue to follow the redirect.
checkShouldCopyHeader(
"authorization",
"https://foo.com",
"https://www.foo.com",
true,
false,
);
checkShouldCopyHeader(
"cookie",
"https://foo.com",
"https://www.foo.com",
false,
);
checkShouldCopyHeader(
"cookie2",
"https://foo.com",
"https://www.foo.com",
false,
);
checkShouldCopyHeader(
"www-authenticate",
"https://foo.com",
"https://www.foo.com",
false,
);
checkShouldCopyHeader("cat", "https://foo.com", "https://www.foo.com", true);
// Redirect to same host: all sensitive headers are still forwarded.
checkShouldCopyHeader(
"cookie",
"https://foo.com",
"https://foo.com/path",
true,
);
checkShouldCopyHeader(
"cookie2",
"https://foo.com",
"https://foo.com/path",
true,
);
checkShouldCopyHeader(
"www-authenticate",
"https://foo.com",
"https://foo.com/path",
true,
);
// Redirect to a deeper subdomain (e.g. attacker-uploads.example.com).
checkShouldCopyHeader(
"authorization",
"https://example.com",
"https://attacker-uploads.example.com",
false,
);
checkShouldCopyHeader(
"cookie",
"https://example.com",
"https://attacker-uploads.example.com",
false,
);
// Redirect to different domain.
checkShouldCopyHeader(
"authorization",