Skip to content

Compare scheme and host case-insensitively in isCloudWorkstation - #10235

Open
Zuhef wants to merge 1 commit into
firebase:mainfrom
Zuhef:fix/alpha-cloudworkstation-case
Open

Compare scheme and host case-insensitively in isCloudWorkstation#10235
Zuhef wants to merge 1 commit into
firebase:mainfrom
Zuhef:fix/alpha-cloudworkstation-case

Conversation

@Zuhef

@Zuhef Zuhef commented Jul 30, 2026

Copy link
Copy Markdown

Problem

isCloudWorkstation() compares both the scheme prefix and the host suffix case-sensitively, but URL schemes and host names are both case-insensitive (RFC 3986 §3.1, RFC 4343). That produces two wrong answers:

isCloudWorkstation('ABC.CloudWorkstations.dev')
// false — but 'abc.cloudworkstations.dev' is true, and DNS does not distinguish them

isCloudWorkstation('HTTPS://example.com/abc.cloudworkstations.dev')
// true — 'HTTPS://' does not match the lower-case prefixes, so the whole URL is
// treated as a host name and the *path* satisfies the suffix check.
// The same URL with a lower-case scheme correctly returns false.

The second one is the direction that matters more: it makes a non-cloud-workstation URL look like a cloud workstation host, and callers use that answer to decide cloud-workstation-specific behaviour (packages/util/src/url.ts also exposes pingServer, which sends credentials: 'include').

Note the mixed-case host only fails in the bare-hostname branch — new URL(url).hostname already lower-cases, so https://ABC.CloudWorkstations.dev happens to work while ABC.CloudWorkstations.dev does not. So the two documented call styles disagree with each other today.

Fix

Lower-case the value before both comparisons.

Negative cases are unaffected — the suffix check keeps its leading dot and stays anchored to the end, so these still return false:

  • evil-cloudworkstations.dev
  • abc.cloudworkstations.dev.example.com
  • https://example.com/abc.cloudworkstations.dev

Full matrix I verified, before vs after:

input before after
abc.cloudworkstations.dev true true
ABC.CloudWorkstations.dev false true
https://abc.cloudworkstations.dev true true
https://ABC.CloudWorkstations.dev true true
HTTPS://abc.cloudworkstations.dev true true
HTTPS://example.com/abc.cloudworkstations.dev true false
https://example.com/abc.cloudworkstations.dev false false
evil-cloudworkstations.dev false false
abc.cloudworkstations.dev.example.com false false
'' false false

Tests

packages/util/src/url.ts had no test file, so this adds packages/util/test/url.test.ts covering the accepted forms, the case-insensitivity of both scheme and host, and the negative cases above.

$ cd packages/util
$ mocha "test/**/*.test.ts" --config ../../config/mocharc.node.js
64 passing

Baseline on an unmodified checkout is 58 passing, so all 6 additions are new coverage. Reverting only src/url.ts fails exactly the two bug cases:

✖ ignores case in the hostname and the scheme
✖ rejects a url whose path ends with the suffix

prettier --check passes on both files, and tsc --noEmit reports nothing for either — the only diagnostic in my environment comes from a newer @types/node than TypeScript 5.5.4 expects, in url.d.ts.

I did not touch pingServer or any caller, and no existing test referenced isCloudWorkstation.

isCloudWorkstation matched both the scheme prefix and the host suffix with
case-sensitive comparisons, but URL schemes and host names are both
case-insensitive. That produced two wrong answers:

    isCloudWorkstation('ABC.CloudWorkstations.dev')
    // false, although 'abc.cloudworkstations.dev' is true and DNS does not
    // distinguish the two

    isCloudWorkstation('HTTPS://example.com/abc.cloudworkstations.dev')
    // true, because 'HTTPS://' did not match the lower-case prefixes, so the
    // whole URL was treated as a host name and the path matched the suffix.
    // The same URL with a lower-case scheme correctly returns false.

Lower-case the value before both comparisons. Negative cases are unaffected:
'evil-cloudworkstations.dev' and 'abc.cloudworkstations.dev.example.com'
still return false, since the suffix check keeps its leading dot and stays
anchored to the end.

packages/util/src/url.ts had no test file, so add one.
@Zuhef
Zuhef requested review from a team as code owners July 30, 2026 09:33
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d25efc9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
@firebase/util Patch
@firebase/ai Patch
@firebase/analytics-compat Patch
@firebase/analytics Patch
@firebase/app-check-compat Patch
@firebase/app-check Patch
@firebase/app-compat Patch
@firebase/app Patch
@firebase/auth-compat Patch
@firebase/auth Patch
@firebase/component Patch
@firebase/data-connect Patch
@firebase/database-compat Patch
@firebase/database-types Patch
@firebase/database Patch
firebase Patch
@firebase/firestore-compat Patch
@firebase/firestore Patch
@firebase/functions-compat Patch
@firebase/functions Patch
@firebase/installations-compat Patch
@firebase/installations Patch
@firebase/messaging-compat Patch
@firebase/messaging Patch
@firebase/performance-compat Patch
@firebase/performance Patch
@firebase/remote-config-compat Patch
@firebase/remote-config Patch
@firebase/storage-compat Patch
@firebase/storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the isCloudWorkstation() utility function to compare URL schemes and hostnames case-insensitively, preventing issues with mixed-case hosts or upper-case schemes. It also adds comprehensive unit tests to verify this behavior. The reviewer suggested an optimization to avoid a redundant .toLowerCase() call on the extracted hostname, leveraging the fact that the WHATWG URL specification guarantees new URL(url).hostname is already lowercase.

Comment thread packages/util/src/url.ts
Comment on lines +29 to +34
const lowerCaseUrl = url.toLowerCase();
const host =
url.startsWith('http://') || url.startsWith('https://')
lowerCaseUrl.startsWith('http://') || lowerCaseUrl.startsWith('https://')
? new URL(url).hostname
: url;
return host.endsWith('.cloudworkstations.dev');
return host.toLowerCase().endsWith('.cloudworkstations.dev');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since new URL(url).hostname is guaranteed to be lowercase by the WHATWG URL specification, and we already have lowerCaseUrl computed, we can use lowerCaseUrl as the fallback when no protocol is present. This allows us to avoid the redundant .toLowerCase() call on host before checking the suffix.

Suggested change
const lowerCaseUrl = url.toLowerCase();
const host =
url.startsWith('http://') || url.startsWith('https://')
lowerCaseUrl.startsWith('http://') || lowerCaseUrl.startsWith('https://')
? new URL(url).hostname
: url;
return host.endsWith('.cloudworkstations.dev');
return host.toLowerCase().endsWith('.cloudworkstations.dev');
const lowerCaseUrl = url.toLowerCase();
const host =
lowerCaseUrl.startsWith('http://') || lowerCaseUrl.startsWith('https://')
? new URL(url).hostname
: lowerCaseUrl;
return host.endsWith('.cloudworkstations.dev');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant