Files
orca/tests/e2e/relay-region-compatibility.unit.test.ts
Neil 56fcb544e0 fix(browser): move cookie scoping off psl's stale suffix list (#20421)
* fix(browser): move cookie scoping off psl's stale suffix list

psl@1.15.0 is its latest release and ships a Dec-2024 snapshot of the
public suffix list. Measured against the current upstream list, it fails
to recognise 600 of 10,030 suffixes; tldts misses 2.

That gap is a cookie-isolation bug. psl does not know `api.br` is a
suffix, so it falls back to the `br` rule and maps foo.api.br, bar.api.br
and example.api.br all onto the single family `api.br`. Unrelated
registrants then share a removal scope, and a replace-mode import for one
clears the others' cookies. The same holds for seg.ar, co.az, gov.cz and
~597 more.

tldts is called with allowPrivateDomains, without which the PSL's PRIVATE
section is ignored and every *.github.io / *.s3.amazonaws.com / *.vercel.app
tenant collapses into one family — 21 of 49 probed hosts changed family
under the default. The new test pins that boundary.

One deliberate behaviour change: hosts under `.local` (not in the PSL)
were their own family under psl, which returned an all-null parse for
them; they now resolve to the two-label boundary (app.orca.local ->
orca.local), matching what Chromium treats as the registrable domain.

* fix(build): bundle tldts into the main process like psl was

psl sat in BUNDLED_MAIN_DEPENDENCIES, so it was inlined into the main
bundle rather than externalized and copied into resources/node_modules.
Swapping the dependency without moving that entry left a bare tldts
import that afterPack's runtime-closure check rejects.

* fix(build): point the output contract at tldts and drop the psl shim

The contract test still asserted psl was in BUNDLED_MAIN_DEPENDENCIES, so
it failed once the entry became tldts. src/types/psl.ts declared a module
that no longer resolves; tldts ships its own types.

* test(browser): pin the suffix boundaries the tldts swap moved

Three semantic changes shipped untested:

- `.local` is unlisted, and the libraries disagreed on what that means. psl
  returned an all-null parse so every `*.orca.local` host was its own family;
  tldts stops at `orca.local`. The consequence is wider than the family name —
  importDomainAncestors now yields the shared parent, so a replace-mode import
  of one host clears non-host-only cookies every sibling shares.
- psl's snapshot had `compute.amazonaws.com` as a literal PRIVATE suffix; the
  current list only carries the wildcard, so the bare host is ICANN now.
- The renderer's `psl.isValid` gate had no direct test at all — nothing imported
  the module from a test.

Also drops comments that explained a boundary in terms of psl's internals. One
was wrong under tldts: bracketed IPv6 does not reach an error branch, it parses
with the brackets stripped and falls through the unlisted path.
2026-09-12 16:00:54 -07:00

119 lines
4.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import {
AssignmentRequestSchema as BaselineRequest,
AssignmentResponseSchema as BaselineResponse
} from '../../cloud/apps/relay/src/test-fixtures/relay-contract-baseline/director-messages'
import {
DrainSchema as BaselineDrain,
HostHelloSchema as BaselineHello
} from '../../cloud/apps/relay/src/test-fixtures/relay-contract-baseline/control-messages'
import { AssignmentRequestSchema } from '../../cloud/packages/relay-contract/src/director-messages'
import { HostHelloSchema } from '../../cloud/packages/relay-contract/src/control-messages'
import { requestRelayAssignment } from '../../src/main/runtime/relay/relay-http-client'
import { RelayAssignRateGate } from '../../src/main/runtime/relay/relay-assign-rate-gate'
const assignment = {
v: 1,
cellUrl: 'https://asia.example.test',
assignmentEpoch: 3,
lease: 'synthetic-assignment'
}
const window = {
generation: 1,
assignmentEpoch: 3,
incumbentRegion: 'asia-east2',
expiresAt: 100_000_000,
policyVersion: 1
}
function request(fetch: typeof globalThis.fetch) {
return requestRelayAssignment({
directorUrl: 'https://director.example.test',
relayHostId: 'abcdefghijklmnop',
relayToken: 'synthetic-authorization',
preferredRegion: 'asia-east2',
reconnect: true,
regionCorrection: { v: 1, action: 'issue-window' },
fetch,
assignRateGate: new RelayAssignRateGate()
})
}
describe('relay correction mixed-version wire contracts', () => {
it('new desktop falls back against the actual pinned old director parser', async () => {
const bodies: unknown[] = []
const fetch = vi.fn<typeof globalThis.fetch>(async (_url, init) => {
const body: unknown = JSON.parse(String(init?.body))
bodies.push(body)
return BaselineRequest.safeParse(body).success
? Response.json(BaselineResponse.parse(assignment))
: new Response(null, { status: 400 })
})
expect(await request(fetch)).toEqual(assignment)
expect(bodies).toHaveLength(2)
expect(AssignmentRequestSchema.safeParse(bodies[0]).success).toBe(true)
expect(BaselineRequest.safeParse(bodies[0]).success).toBe(false)
expect(bodies[1]).toEqual({
v: 1,
relayHostId: 'abcdefghijklmnop',
preferredRegion: 'asia-east2',
reconnect: true
})
})
it('the old desktop assignment shape remains accepted by the new director', () => {
const request = BaselineRequest.parse({ v: 1, relayHostId: 'abcdefghijklmnop' })
expect(AssignmentRequestSchema.parse(request)).toEqual(request)
expect(BaselineResponse.parse(assignment)).toEqual(assignment)
})
it('the negotiated capability requires no change to the strict old host hello', () => {
const hello = {
v: 1,
relayHostId: 'abcdefghijklmnop',
assignmentEpoch: 3,
hostPublicKeyB64: Buffer.alloc(32).toString('base64'),
appVersion: 'test'
}
expect(BaselineHello.parse(HostHelloSchema.parse(hello))).toEqual(hello)
expect(BaselineHello.safeParse({ ...hello, idleRegionalRehome: true }).success).toBe(false)
})
it('the idle cutover uses a drain frame understood by the pinned old desktop', () => {
const drain = { recovery: 'resolve-director', graceMs: 0 }
expect(BaselineDrain.parse(drain)).toEqual(drain)
})
it.each([
{ v: 1, window: { ...window, policyVersion: 2 } },
{ v: 2, window },
{ v: 1, window: { ...window, expiresAt: -1 } },
{ v: 1, window: { ...window, unexpectedField: true } }
])(
'defers unsupported or malformed optional correction without losing placement: %j',
async (regionCorrection) => {
const result = await request(async () => Response.json({ ...assignment, regionCorrection }))
expect(result).toMatchObject(assignment)
expect(result.regionCorrection).toBeUndefined()
}
)
it('still accepts supported correction metadata', async () => {
const regionCorrection = { v: 1, window }
expect(await request(async () => Response.json({ ...assignment, regionCorrection }))).toEqual({
...assignment,
regionCorrection
})
})
it.each([
{ cellUrl: 'http://untrusted.example.test' },
{ assignmentEpoch: -1 },
{ lease: '' },
{ unexpectedField: true }
])('keeps the core assignment strict: %j', async (invalid) => {
await expect(request(async () => Response.json({ ...assignment, ...invalid }))).rejects.toThrow(
'relay_assignment_failed_502'
)
})
})