From 38bd8090660e4e1d564b282f26cb323189fd6538 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Fri, 8 May 2026 17:54:35 -0300 Subject: [PATCH 1/2] warn and fallback when cookieOptions.domain is a Public Suffix List entry --- packages/script/src/base.js | 22 ++++++++++++++++++++++ packages/web/README.md | 2 ++ packages/web/src/types.ts | 8 ++++++++ 3 files changed, 32 insertions(+) diff --git a/packages/script/src/base.js b/packages/script/src/base.js index 138fa36..2fc24ab 100644 --- a/packages/script/src/base.js +++ b/packages/script/src/base.js @@ -10,6 +10,15 @@ // Common script attributes const API_HOST = script.getAttribute('data-api-host') || 'https://api.dub.co'; const PUBLISHABLE_KEY = script.getAttribute('data-publishable-key'); + // Domains on the Public Suffix List that browsers treat as TLDs. + // Cookies cannot be set on these domains — the browser silently drops them. + const KNOWN_PSL_DOMAINS = [ + 'vercel.app', 'vercel.dev', 'vercel.run', 'now.sh', 'v0.build', + 'vusercontent.net', 'netlify.app', 'pages.dev', 'github.io', 'fly.dev', + 'railway.app', 'onrender.com', 'web.app', 'firebaseapp.com', + 'surge.sh', 'azurewebsites.net', 'amplifyapp.com', + ]; + const COOKIE_OPTIONS = (() => { const defaultOptions = { domain: @@ -32,6 +41,19 @@ delete parsedOpts.expiresInDays; } + if (parsedOpts.domain) { + const bare = parsedOpts.domain.replace(/^\./, ''); + if (KNOWN_PSL_DOMAINS.includes(bare)) { + console.warn( + `[dubAnalytics] cookieOptions.domain "${parsedOpts.domain}" is on the Public Suffix List. ` + + `Browsers silently reject cookies set on PSL domains. ` + + `Falling back to the default domain "${defaultOptions.domain}". ` + + `Cross-subdomain cookie sharing under a PSL domain is not possible via client-side cookies.`, + ); + delete parsedOpts.domain; + } + } + return { ...defaultOptions, ...parsedOpts }; })(); diff --git a/packages/web/README.md b/packages/web/README.md index 0402709..235c251 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -70,6 +70,8 @@ The `cookieOptions` prop accepts the following keys: | `expiresInDays` | `90` | Specifies the number (in days) to be the value for the `Expires` Set-Cookie attribute. | `90` | | `path` | `/` | Specifies the value for the `Path` Set-Cookie attribute. By default, the path is considered the "default path". | `/` | +> **Note:** Domains on the [Public Suffix List](https://publicsuffix.org/) (PSL) — such as `.vercel.app`, `.netlify.app`, `.github.io`, or `.pages.dev` — **cannot** be used as cookie domains. Browsers silently reject cookies set on PSL entries because these suffixes are treated like top-level domains (`.com`, `.io`). If you pass a PSL domain, the script will log a `console.warn` and fall back to the current hostname. Cross-subdomain cookie sharing under a PSL domain is not achievable via client-side cookies. + For example, to set a 60-day cookie window, you can use the following code: ```tsx diff --git a/packages/web/src/types.ts b/packages/web/src/types.ts index 939c820..5d320ad 100644 --- a/packages/web/src/types.ts +++ b/packages/web/src/types.ts @@ -59,6 +59,14 @@ export interface AnalyticsProps { * By default, the domain is set to the current hostname (including all subdomains). * * @default `.` + window.location.hostname + * + * **Warning:** Domains listed on the {@link https://publicsuffix.org/|Public Suffix List} (PSL) — + * such as `.vercel.app`, `.netlify.app`, `.github.io`, or `.pages.dev` — cannot be used as + * cookie domains. Browsers silently reject `document.cookie` writes that target a PSL entry, + * because these suffixes are treated equivalently to top-level domains like `.com`. + * If you provide a PSL domain, the script will log a warning and fall back to the default + * hostname-derived domain. Cross-subdomain cookie sharing under a PSL domain is not achievable + * via client-side cookies. */ domain?: string | undefined; From b1177dd7e791151353f0404de7161f2e18ea4b2b Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Tue, 19 May 2026 18:19:51 -0300 Subject: [PATCH 2/2] code improvements --- packages/script/src/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/script/src/base.js b/packages/script/src/base.js index 2fc24ab..2574161 100644 --- a/packages/script/src/base.js +++ b/packages/script/src/base.js @@ -42,7 +42,7 @@ } if (parsedOpts.domain) { - const bare = parsedOpts.domain.replace(/^\./, ''); + const bare = parsedOpts.domain.trim().replace(/^\./, '').toLowerCase(); if (KNOWN_PSL_DOMAINS.includes(bare)) { console.warn( `[dubAnalytics] cookieOptions.domain "${parsedOpts.domain}" is on the Public Suffix List. ` +