Files
OmniRoute/open-sse/utils/cookieDomain.ts
Diego Rodrigues de Sa e Souza 1d0c5a36db fix(security): match cookie domains by suffix, not substring (#11429)
CodeQL js/incomplete-url-substring-sanitization, alerts #860 and #861:
volcengineConsoleAutoLogin accepted any cookie whose `domain` merely *contained*
"volcengine.com".

That check is an authorization decision, not a string test. The console
auto-login harvests `digest`, `AccountID`, `csrfToken` and `userInfo` out of the
Playwright context and persists them as the operator's Volcengine credentials,
so a cookie set by `volcengine.com.attacker.tld` — or `notvolcengine.com` — was
captured and stored as a provider connection.

Add `matchesCookieDomain()` (open-sse/utils/cookieDomain.ts): exact host or
dot-boundary suffix, leading dots and case normalized on both sides, failing
closed on an empty expected domain. Same shape as the existing
`isAdobeCookieDomain` in adobeFireflyBrowserLogin.ts, which already got this
right.

While sweeping the class, inAppLoginService's cookie capture had the identical
weakness — `c.domain.includes(domain.replace(/^\./, ""))` — with the identical
consequence: a look-alike host's cookie stored as the operator's credential.
CodeQL did not flag it because the expected domain comes from
TOKEN_EXTRACTION_CONFIGS rather than a literal. Both callsites now share the
helper.

tests/unit/volcengine-cookie-domain-suffix.test.ts — 5 tests, red before the
fix, covering the real domains, seven look-alikes, empty/missing input, and the
config-supplied path.

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-24 19:44:12 -03:00

35 lines
1.3 KiB
TypeScript

/**
* Cookie-domain matching for browser-driven credential capture.
*
* Every in-app / console login flow harvests cookies out of a Playwright
* context and persists them as operator credentials, so "is this cookie from
* the site I sent the browser to?" is an authorization decision. A substring
* test is not one: `domain.includes("example.com")` also accepts
* `example.com.attacker.tld` and `notexample.com`, which lets a look-alike host
* hand us cookies we then store as the operator's real credentials
* (CodeQL js/incomplete-url-substring-sanitization).
*
* A cookie domain is matched by exact host or dot-boundary suffix — nothing
* else. Leading dots (the RFC 6265 "domain-matches any subdomain" spelling) and
* case are normalized away on both sides.
*/
export function matchesCookieDomain(
cookieDomain: string | undefined,
expectedDomain: string | undefined
): boolean {
const expected = normalizeCookieDomain(expectedDomain);
if (!expected) return false;
const actual = normalizeCookieDomain(cookieDomain);
if (!actual) return false;
return actual === expected || actual.endsWith(`.${expected}`);
}
function normalizeCookieDomain(domain: string | undefined): string {
return String(domain || "")
.trim()
.replace(/^\.+/, "")
.toLowerCase();
}