feat(geo): track multiple domains per project for AI traffic - #1033
Conversation
Projects can list extra hostnames besides the brand website so docs, app, and regional sites share one ingest token. Pages roll up by host and path. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
4 Skipped Deployments
|
|
React Doctor found 1 new issue in 1 file · 1 warning · score 91 / 100 (Great) · 0 fixed · vs Reviewed by React Doctor for commit |
The citations log queried the latest 200 events across every domain and filtered in the browser, so a domain picker often showed an empty log. Query Tinybird with the selected host, and drop a stale host param when switching projects. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
da76c7a to
15c3192
Compare
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
A failed project lookup after a brand URL save aborted before any Redis delete and failed the already-committed settings write. The org key is now cleared first; project keys still drop when the lookup succeeds. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Badge chips are rounded-4xl. The X used rounded-sm, so hover sat as a square inside the capsule. Match the concentric inner radius (and the onboarding competitor chips) with rounded-full. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The funnel metrics used viewport lg:grid-cols-4, so a desktop window with a squeezed content pane (sidebar, agent panel) still forced four text-4xl columns and the numbers overlapped. Size the grid from the card container instead, and let the pages column flex instead of a 32rem min width. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The Brand settings description was four sentences. Keep the same facts in two: extra hosts besides the brand website, same ingest token, listed domains and subdomains only. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…mains-a3c9 # Conflicts: # apps/api/src/routes/brand-identities.ts # packages/db/migrations/meta/0085_snapshot.json # packages/db/migrations/meta/_journal.json Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Keep the identity website read-only in GEO Brand settings and strip it from extra domains. Drop the host-query effect setState React Doctor flagged. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Split assistant-browse crawlers into their own Cited sources band so they are not mixed into training/index crawlers. Treat Instagram as its own source and icon family instead of folding it into Meta, and attribute meta.ai click-throughs as Meta referrals. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The marketing site build fails styles:check when EngineIcon grows a new SVG that is not listed in ui-sources.css. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Spread sample ingest across www, docs, and app example.com so the pages table and domain filter can be demoed. Add the extras to GEO settings and a third host on the directions pages fixture. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Local-dev impersonation now needs DEV_AUTH_ENABLED plus DEV_AUTH_EMAIL and is rejected on tunneled or forwarded hosts. Traffic host filters no longer drop selections missing from the top-500 ranking, Unicode domains are canonicalized to punycode, and ingest allowlists are invalidated when projects are created, relinked, or deleted. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
@janburzinski I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
4 issues found across 79 files
Confidence score: 2/5
apps/dashboard/src/utils/local-dev-auth.tsaccepts a spoofedHost: localhost:3000when the development server is exposed beyond localhost, allowing the proxy andgetAuthIdentitypath to bypass intended local-development restrictions; require trusted forwarding or bind/restrict the server to loopback.apps/dashboard/src/lib/auth/server.tsturns a bannedDEV_AUTH_EMAILidentity failure into an unauthenticated result, whileisSessionBannedtreats that result as allowed, potentially bypassing the ban; preserve and enforce the banned state explicitly.packages/geo-core/src/types/geo.tscan resolve an Instagram source to an unregistered dashboard icon key, causing the UI to show the Microsoft Copilot fallback; add the Instagram icon mapping or registry entry.packages/geo-core/src/utils/geo-project-domains.tsdoes not normalize a selectedwww.host, so valid traffic-host selections can be rejected; normalize the selected hostname before comparison.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/geo-core/src/types/geo.ts">
<violation number="1" location="packages/geo-core/src/types/geo.ts:1144">
P2: When an Instagram source reaches the dashboard `EngineIcon`, `resolveEngineIconKey` returns `"instagram"` but the dashboard icon registry lacks it, so the component renders the Microsoft Copilot fallback. Add Instagram to the dashboard icon registry.</violation>
</file>
<file name="apps/dashboard/src/lib/auth/server.ts">
<violation number="1" location="apps/dashboard/src/lib/auth/server.ts:156">
P2: When `DEV_AUTH_EMAIL` belongs to a banned user, `loadLocalDevIdentity` fails and this branch converts that failure to `null`. Auth guards then call `isSessionBanned`, which returns `false` for an allowed local request. Banned developers are therefore sent to `/login` instead of `/auth/banned`; preserve the banned result or query the pinned user in that helper.</violation>
</file>
<file name="packages/geo-core/src/utils/geo-project-domains.ts">
<violation number="1" location="packages/geo-core/src/utils/geo-project-domains.ts:169">
P2: When the selected host is `www.example.com` and only `docs.example.com` is present, `isKnownTrafficHost` fails to recognize the selection because it does not normalize `www.`. Normalize `selected` before the host comparisons.</violation>
</file>
<file name="apps/dashboard/src/utils/local-dev-auth.ts">
<violation number="1" location="apps/dashboard/src/utils/local-dev-auth.ts:135">
P1: When the development server is reachable on a LAN or public interface, a client can send `Host: localhost:3000` without forwarding headers, and this condition returns `allowed`. The proxy and `getAuthIdentity` then bypass AuthKit and load `DEV_AUTH_EMAIL`; validate the actual peer address at the network boundary instead of trusting `Host` or spoofable forwarding headers.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return { kind: "blocked", reason: "missing_email" }; | ||
| } | ||
| const host = headers?.get("host") ?? null; | ||
| if (!requestHostIsLoopback(host) || requestLooksPubliclyExposed(headers)) { |
There was a problem hiding this comment.
P1: When the development server is reachable on a LAN or public interface, a client can send Host: localhost:3000 without forwarding headers, and this condition returns allowed. The proxy and getAuthIdentity then bypass AuthKit and load DEV_AUTH_EMAIL; validate the actual peer address at the network boundary instead of trusting Host or spoofable forwarding headers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/dashboard/src/utils/local-dev-auth.ts, line 135:
<comment>When the development server is reachable on a LAN or public interface, a client can send `Host: localhost:3000` without forwarding headers, and this condition returns `allowed`. The proxy and `getAuthIdentity` then bypass AuthKit and load `DEV_AUTH_EMAIL`; validate the actual peer address at the network boundary instead of trusting `Host` or spoofable forwarding headers.</comment>
<file context>
@@ -0,0 +1,148 @@
+ return { kind: "blocked", reason: "missing_email" };
+ }
+ const host = headers?.get("host") ?? null;
+ if (!requestHostIsLoopback(host) || requestLooksPubliclyExposed(headers)) {
+ return { kind: "blocked", reason: "non_loopback" };
+ }
</file context>
| | "mistral" | ||
| | "deepseek" | ||
| | "meta" | ||
| | "instagram" |
There was a problem hiding this comment.
P2: When an Instagram source reaches the dashboard EngineIcon, resolveEngineIconKey returns "instagram" but the dashboard icon registry lacks it, so the component renders the Microsoft Copilot fallback. Add Instagram to the dashboard icon registry.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/geo-core/src/types/geo.ts, line 1144:
<comment>When an Instagram source reaches the dashboard `EngineIcon`, `resolveEngineIconKey` returns `"instagram"` but the dashboard icon registry lacks it, so the component renders the Microsoft Copilot fallback. Add Instagram to the dashboard icon registry.</comment>
<file context>
@@ -1137,6 +1141,7 @@ export type EngineIconKey =
| "mistral"
| "deepseek"
| "meta"
+ | "instagram"
| "grok"
| "qwen"
</file context>
| headerList = null; | ||
| } | ||
| const gate = evaluateLocalDevAuth(headerList); | ||
| if (gate.kind === "allowed") { |
There was a problem hiding this comment.
P2: When DEV_AUTH_EMAIL belongs to a banned user, loadLocalDevIdentity fails and this branch converts that failure to null. Auth guards then call isSessionBanned, which returns false for an allowed local request. Banned developers are therefore sent to /login instead of /auth/banned; preserve the banned result or query the pinned user in that helper.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/dashboard/src/lib/auth/server.ts, line 156:
<comment>When `DEV_AUTH_EMAIL` belongs to a banned user, `loadLocalDevIdentity` fails and this branch converts that failure to `null`. Auth guards then call `isSessionBanned`, which returns `false` for an allowed local request. Banned developers are therefore sent to `/login` instead of `/auth/banned`; preserve the banned result or query the pinned user in that helper.</comment>
<file context>
@@ -99,10 +103,71 @@ const buildAuthIdentity = Effect.fn("auth.identity.build")(function* (
+ headerList = null;
+ }
+ const gate = evaluateLocalDevAuth(headerList);
+ if (gate.kind === "allowed") {
+ return Effect.runPromise(
+ loadLocalDevIdentity().pipe(
</file context>
| selected: string, | ||
| hosts: readonly string[] | ||
| ): boolean { | ||
| const needle = selected.trim(); |
There was a problem hiding this comment.
P2: When the selected host is www.example.com and only docs.example.com is present, isKnownTrafficHost fails to recognize the selection because it does not normalize www.. Normalize selected before the host comparisons.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/geo-core/src/utils/geo-project-domains.ts, line 169:
<comment>When the selected host is `www.example.com` and only `docs.example.com` is present, `isKnownTrafficHost` fails to recognize the selection because it does not normalize `www.`. Normalize `selected` before the host comparisons.</comment>
<file context>
@@ -0,0 +1,187 @@
+ selected: string,
+ hosts: readonly string[]
+): boolean {
+ const needle = selected.trim();
+ if (needle.length === 0 || needle === "all") {
+ return true;
</file context>
Bind next dev to loopback so Host cannot be spoofed from the LAN. Keep the pages host selector when a domain filter is empty, collapse www/apex hosts, register Instagram in the dashboard icon map, and send banned DEV_AUTH_EMAIL users to /auth/banned. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
There was a problem hiding this comment.
All reported issues were addressed across 15 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
A www URL host no longer mismatches the collapsed selector options, so the filter stays visible and the Select has a matching item. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
| requestLooksPubliclyExposed, | ||
| } from "./local-dev-auth"; | ||
|
|
||
| const LIVE_KEY = "sk_test_abcdefghijklmnopqrstuvwxyz"; |
There was a problem hiding this comment.
React Doctor · react-doctor/no-secrets-in-client-code (warning)
This hardcoded secret is a security vulnerability: it ships to the browser where anyone can read it.
Fix → Move secrets to server-only code. In Next.js, only NEXT_PUBLIC_* env vars are exposed to the browser, and they must not contain secrets
Description
Give a short summary of what this PR does and why it's needed.
Screenshot/Recording (if applicable)
Attach a screenshot or recording of the change. This is optional, but can help reviewers understand the change. You can use Cap to record a video.
Checklist
Summary by cubic
Projects can track up to 20 extra hostnames under one project and ingest token. AI traffic pages and citations are grouped and filtered by host, while ingest drops events from hosts outside the brand website or tracked domains so leaked tokens cannot register other hosts.
Behavior
domains; the brand website remains read-only, every tracked host uses the same token, and settings or project changes invalidate cached allowlists.www,docs, andapp, includes subdomains for tracked hosts, and canonicalizes Unicode domains to punycode.data:andfile://; allowlist lookup failures fail open.wwwURLs match collapsed options, resets log pagination on host changes, and clears selections when switching projects.meta.aireferrals to Meta.NODE_ENV=development,DEV_AUTH_ENABLED, andDEV_AUTH_EMAIL; it only works on loopback requests, rejects tunneled or forwarded hosts, and sends bannedDEV_AUTH_EMAILusers to/auth/banned.Rollout
Written for commit 5473341. Summary will update on new commits.