Skip to content

feat(geo): track multiple domains per project for AI traffic - #1033

Merged
janburzinski merged 22 commits into
mainfrom
cursor/geo-project-domains-a3c9
Sep 13, 2026
Merged

janburzinski merged 22 commits into
mainfrom
cursor/geo-project-domains-a3c9

Conversation

@janburzinski

@janburzinski janburzinski commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

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
  • I ran a self-review before opening this PR
  • I ran formatting/linting/type checks locally
  • I updated docs when behavior or setup changed
  • I only added comments where the logic is not obvious
  • I have used conventional commits for the PR title and commit messages
  • I did not use AI to write the code in this PR or have disclosed that I did
Open in Web Open in Cursor 

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

  • Stores normalized domains; the brand website remains read-only, every tracked host uses the same token, and settings or project changes invalidate cached allowlists.
  • Seeds sample traffic across www, docs, and app, includes subdomains for tracked hosts, and canonicalizes Unicode domains to punycode.
  • Rejects hostless URLs such as data: and file://; allowlist lookup failures fail open.
  • Applies host filters before limits, preserves selections missing from the top-500 results, keeps the host selector when that selection is empty, groups pages by host and path, binds the selector to canonical host values so www URLs match collapsed options, resets log pagination on host changes, and clears selections when switching projects.
  • Splits assistant-browse crawlers into a Cited band, gives Instagram its own source icon, and attributes meta.ai referrals to Meta.
  • Fixes narrow-pane metric wrapping and tag remove-button hover shapes.
  • Local development authentication requires NODE_ENV=development, DEV_AUTH_ENABLED, and DEV_AUTH_EMAIL; it only works on loopback requests, rejects tunneled or forwarded hosts, and sends banned DEV_AUTH_EMAIL users to /auth/banned.

Rollout

  • The pages endpoint remains on raw events until the host-aware rollup backfill runs.

Written for commit 5473341. Summary will update on new commits.

Review in cubic

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>
@vercel

vercel Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
notra Ready Ready Preview Sep 13, 2026 6:57am UTC
4 Skipped Deployments
Project Deployment Actions Updated
notra-agent Skipped Skipped Sep 13, 2026 6:57am UTC
notra-onboarding-agent Skipped Skipped Sep 13, 2026 6:57am UTC
notra-ui Skipped Skipped Sep 13, 2026 6:57am UTC
notra-web Skipped Skipped Sep 13, 2026 6:57am UTC

Request Review

@CLAassistant

CLAassistant commented Sep 11, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added apps/api Changes files in apps/api apps/dashboard Changes files in apps/dashboard apps/docs Changes files in apps/docs packages/analytics Changes files in packages/analytics packages/db Changes files in packages/db packages/geo-core Changes files in packages/geo-core packages/schemas Changes files in packages/schemas priority/normal Issue priority or pull request review urgency; maintainers decide type/feature PR change classification labels Sep 11, 2026
@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

React Doctor found 1 new issue in 1 file · 1 warning · score 91 / 100 (Great) · 0 fixed · vs main

1 warning

src/utils/local-dev-auth.test.ts

  • ⚠️ L12 Secret in client code no-secrets-in-client-code

Reviewed by React Doctor for commit 5473341. See inline comments for fixes.

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>

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread packages/geo-core/src/geo/ingest.ts Outdated
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>
@vercel
vercel Bot temporarily deployed to Preview – notra-web September 12, 2026 12:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-ui September 12, 2026 12:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-agent September 12, 2026 12:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-onboarding-agent September 12, 2026 12:24 Inactive
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>
@vercel
vercel Bot temporarily deployed to Preview – notra-ui September 12, 2026 12:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-web September 12, 2026 12:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-agent September 12, 2026 12:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-onboarding-agent September 12, 2026 12:46 Inactive
janburzinski and others added 4 commits September 12, 2026 13:43
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>
Comment thread apps/dashboard/src/components/providers/geo-project-provider.tsx Outdated
janburzinski and others added 4 commits September 12, 2026 15:09
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

Copy link
Copy Markdown
Collaborator Author

@cubic

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

@cubic

@janburzinski I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

4 issues found across 79 files

Confidence score: 2/5

  • apps/dashboard/src/utils/local-dev-auth.ts accepts a spoofed Host: localhost:3000 when the development server is exposed beyond localhost, allowing the proxy and getAuthIdentity path to bypass intended local-development restrictions; require trusted forwarding or bind/restrict the server to loopback.
  • apps/dashboard/src/lib/auth/server.ts turns a banned DEV_AUTH_EMAIL identity failure into an unauthenticated result, while isSessionBanned treats that result as allowed, potentially bypassing the ban; preserve and enforce the banned state explicitly.
  • packages/geo-core/src/types/geo.ts can 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.ts does not normalize a selected www. 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)) {

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.

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>

Comment thread apps/dashboard/src/components/geo/traffic-pages-card.tsx
| "mistral"
| "deepseek"
| "meta"
| "instagram"

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.

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") {

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.

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>

Comment thread packages/geo-core/src/utils/geo-project-domains.ts Outdated
Comment thread packages/geo-core/src/geo/sample-data.ts
selected: string,
hosts: readonly string[]
): boolean {
const needle = selected.trim();

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.

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>

Comment thread .env.example Outdated
Comment thread apps/dashboard/src/utils/ai-traffic-pages.ts Outdated
Comment thread packages/schemas/src/schemas/api/geo-traffic.ts Outdated
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>

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread apps/dashboard/src/utils/ai-traffic-pages.ts
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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Docs

This branch was successfully deployed

1 active and 4 inactive deployments
Preview – notra — 54733418 Deployed Sep 13, 2026 by vercel[bot]
Preview – notra-web — 54733418 Deployed Sep 13, 2026 by vercel[bot]
Preview – notra-ui — 54733418 Deployed Sep 13, 2026 by vercel[bot]
Preview – notra-agent — 54733418 Deployed Sep 13, 2026 by vercel[bot]
Preview – notra-onboarding-agent — 54733418 Deployed Sep 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apps/api Changes files in apps/api apps/dashboard Changes files in apps/dashboard apps/docs Changes files in apps/docs packages/analytics Changes files in packages/analytics packages/db Changes files in packages/db packages/geo-core Changes files in packages/geo-core packages/schemas Changes files in packages/schemas priority/normal Issue priority or pull request review urgency; maintainers decide type/feature PR change classification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants