GeezerVison is showing github.com — fix(analytics): GTM/GA4 missing on /demo and other landing pages by waleedlatif1 · Pull Request #5437 · simstudioai/sim
Conversation
Summary
- Andy (marketing) reported GTM/GA4 key-event tracking not firing on
/demo. Confirmed live via curl against sim.ai that script-src/connect-src on /demo and /pricing are missing googletagmanager.com/google-analytics.com/analytics.ahrefs.com, while / has them.
- Root cause: those GTM/GA CSP entries are gated on
isHosted, which next.config.ts's build-time getMainCSPPolicy() bakes in once at build/boot. apps/sim/proxy.ts only overrides the response CSP with the request-time generateRuntimeCSP() for /, /login, /signup, and /workspace/* — every other route (all marketing/landing pages, including /demo) fell back to the stale build-time policy, which silently dropped the GTM allowlist.
- Fix: apply
generateRuntimeCSP() to every remaining matched route in proxy.ts instead of gating it to pathname === '/'.
Test plan
-
bun run vitest run proxy.test.ts — 11/11 pass
-
tsc --noEmit clean on proxy.ts
- Verify post-deploy:
curl -sL -D - -o /dev/null https://www.sim.ai/demo | grep -i content-security-policy includes googletagmanager.com
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
GTM/GA4 script-src and connect-src allowlist entries are gated behind
isHosted, which next.config.ts's build-time CSP bakes in from whatever
NEXT_PUBLIC_APP_URL was resolved at build/boot time. Only the homepage
route was overridden with the request-time CSP in proxy.ts, so every
other landing page (including /demo) silently lost the
googletagmanager.com/google-analytics.com allowlist and GTM never fired.
vercel
Bot
commented
Jul 6, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
| Project |
Deployment |
Actions |
Updated (UTC) |
| docs |
Skipped |
|
Jul 6, 2026 6:14pm |

|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
commented
Jul 6, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
PR Summary
Low Risk
Broadens security headers to more HTML routes using the same runtime CSP helper already used on / and auth pages; no auth or data-path changes.
Overview
Fixes GTM/GA4 and other hosted analytics not loading on marketing pages (e.g. /demo, /pricing) by ensuring every non-API page that falls through proxy.ts gets request-time CSP instead of only /.
Previously, generateRuntimeCSP() (which includes googletagmanager.com / google-analytics.com when isHosted) was set only when pathname === '/'. Other matched routes kept the build-time CSP from next.config.ts, which could omit those domains and block tags.
The change always sets Content-Security-Policy, X-Content-Type-Options, and X-Frame-Options on the default NextResponse.next() path. Invite routes that continue without a login redirect now get the same runtime CSP headers.
Routes that already set runtime CSP (/login, /signup, /workspace/*) and /chat/* (CSP from config) are unchanged in intent; /api/* still only gets CORS.
Reviewed by Cursor Bugbot for commit 7cac9f2. Configure here.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 363b18d. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps
Bot
commented
Jul 6, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Greptile Summary
This PR fixes missing GTM/GA4 analytics tracking on marketing/landing pages like /demo and /pricing by broadening where generateRuntimeCSP() is applied in the middleware. Previously, the runtime CSP (which includes googletagmanager.com and google-analytics.com) was only set when pathname === '/'; now it is applied to every matched route that reaches the fall-through block, and /invite/* pages with an active session also receive it.
- Removes the
if (url.pathname === '/') guard in proxy(), so all non-API, non-chat routes that fall through receive the runtime CSP with GTM/GA4 allowlists.
- Updates
handleInvitationRedirects to attach CSP/security headers to the NextResponse.next() it returns for authenticated /invite/ visitors, closing the one remaining gap for those pages.
Confidence Score: 5/5
Safe to merge — the change correctly broadens runtime CSP coverage to all matched landing/marketing routes without affecting API paths, chat embeds, or auth redirects.
The two-line guard removal is minimal and mechanically correct. Every early-return branch (API, /login, /signup, /workspace, /invite/ redirect, suspicious-UA block) is untouched; only the fall-through path that previously left /demo and other landing pages without a runtime CSP is affected. The /invite/ authenticated-session fix closes the one remaining gap that existed before this PR. No new code paths are introduced that could regress existing behaviour.
No files require special attention.
Important Files Changed
| Filename |
Overview |
| apps/sim/proxy.ts |
Broadens runtime CSP application from / only to all matched non-API, non-chat routes; also fixes /invite/ pages with an active session that previously returned a bare NextResponse.next(). Logic is correct and no new gaps are introduced. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming Request] --> B{pathname starts with /api/?}
B -->|Yes| C[Apply CORS headers & return]
B -->|No| D[Check session cookie]
D --> E[handleRootPathRedirects]
E -->|Redirect| F[track & return redirect]
E -->|null| G{pathname /login or /signup?}
G -->|Yes, has session| H[Redirect to /workspace]
G -->|Yes, no session| I[Apply runtime CSP & return]
G -->|No| J{pathname starts with /chat/?}
J -->|Yes| K[Return bare NextResponse — CSP from next.config.ts]
J -->|No| L{pathname starts with /workspace?}
L -->|Yes, no session| M[Redirect to /login]
L -->|Yes, has session| N[Apply runtime CSP & return]
L -->|No| O[handleInvitationRedirects]
O -->|Unauthenticated redirect| P[track & return redirect to login]
O -->|Authenticated — NEW| Q[Apply runtime CSP & return]
O -->|null| R[handleSecurityFiltering]
R -->|Suspicious UA| S[Return 403 Forbidden]
R -->|null| T[Apply runtime CSP + Vary: User-Agent — FIXED]
T --> U[track & return]
style T fill:#d4edda,stroke:#28a745
style Q fill:#d4edda,stroke:#28a745
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Incoming Request] --> B{pathname starts with /api/?}
B -->|Yes| C[Apply CORS headers & return]
B -->|No| D[Check session cookie]
D --> E[handleRootPathRedirects]
E -->|Redirect| F[track & return redirect]
E -->|null| G{pathname /login or /signup?}
G -->|Yes, has session| H[Redirect to /workspace]
G -->|Yes, no session| I[Apply runtime CSP & return]
G -->|No| J{pathname starts with /chat/?}
J -->|Yes| K[Return bare NextResponse — CSP from next.config.ts]
J -->|No| L{pathname starts with /workspace?}
L -->|Yes, no session| M[Redirect to /login]
L -->|Yes, has session| N[Apply runtime CSP & return]
L -->|No| O[handleInvitationRedirects]
O -->|Unauthenticated redirect| P[track & return redirect to login]
O -->|Authenticated — NEW| Q[Apply runtime CSP & return]
O -->|null| R[handleSecurityFiltering]
R -->|Suspicious UA| S[Return 403 Forbidden]
R -->|null| T[Apply runtime CSP + Vary: User-Agent — FIXED]
T --> U[track & return]
style T fill:#d4edda,stroke:#28a745
style Q fill:#d4edda,stroke:#28a745
Loading
Reviews (4): Last reviewed commit: "fix(analytics): apply runtime CSP to aut..." | Re-trigger Greptile
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps
Bot
commented
Jul 6, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Greptile Summary
This PR fixes GTM/GA4 analytics not firing on /demo, /pricing, and other landing pages by extending runtime CSP header application from only the root path to all catch-all routes in proxy.ts. The root cause was that generateRuntimeCSP() (which includes isHosted-gated GTM domains) was only called when pathname === '/', so every other marketing page fell back to the stale build-time CSP that silently omitted those domains.
- Root path guard removed: The
if (url.pathname === '/') block is replaced with unconditional calls to generateRuntimeCSP(), X-Content-Type-Options, and X-Frame-Options for all routes that reach the catch-all block.
- Scope is correct: Routes with early returns (
/api/*, /login, /signup, /workspace, /chat/*, /invite/*) are unaffected; only the true catch-all paths (landing pages, marketing routes) gain the runtime CSP.
Confidence Score: 4/5
The change is safe to merge — it broadens CSP header coverage from one route to all landing pages, and the security posture of those pages improves or stays the same.
The fix is minimal, targeted, and correct for its stated goal. The one notable gap is that authenticated users on /invite/ pages still receive no runtime CSP because handleInvitationRedirects returns NextResponse.next() before the new header-setting code is reached. This is pre-existing behaviour but falls within the same class of issue the PR is addressing. All other routes are handled correctly, and X-Content-Type-Options / X-Frame-Options being applied more broadly is an improvement.
apps/sim/proxy.ts — specifically the handleInvitationRedirects early-return path at line 278, which exits without setting runtime CSP headers.
Important Files Changed
| Filename |
Overview |
| apps/sim/proxy.ts |
Removes the pathname === '/' guard so generateRuntimeCSP() and security headers are applied to all catch-all routes; fix is correct but authenticated /invite/ pages still escape CSP via an early return in handleInvitationRedirects. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming Request] --> B{/api/*?}
B -- Yes --> C[CORS headers + early return]
B -- No --> D{/login or /signup?}
D -- Yes, with session --> E[Redirect to /workspace]
D -- Yes, no session --> F[generateRuntimeCSP + return]
D -- No --> G{/chat/*?}
G -- Yes --> H[Early return, CSP from next.config.ts]
G -- No --> I{/workspace/*?}
I -- No session --> J[Redirect to /login]
I -- With session --> K[generateRuntimeCSP + return]
I -- No --> L{/invite/* redirect?}
L -- Redirect --> M[Redirect to /login with callbackUrl]
L -- NextResponse.next --> N[Early return WITHOUT CSP headers]
L -- No --> O{Security block?}
O -- Yes --> P[403 Forbidden, strict CSP]
O -- No --> Q[Vary: User-Agent]
Q --> R[generateRuntimeCSP - BEFORE: only / - AFTER: all catch-all routes]
R --> S[track + return]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Incoming Request] --> B{/api/*?}
B -- Yes --> C[CORS headers + early return]
B -- No --> D{/login or /signup?}
D -- Yes, with session --> E[Redirect to /workspace]
D -- Yes, no session --> F[generateRuntimeCSP + return]
D -- No --> G{/chat/*?}
G -- Yes --> H[Early return, CSP from next.config.ts]
G -- No --> I{/workspace/*?}
I -- No session --> J[Redirect to /login]
I -- With session --> K[generateRuntimeCSP + return]
I -- No --> L{/invite/* redirect?}
L -- Redirect --> M[Redirect to /login with callbackUrl]
L -- NextResponse.next --> N[Early return WITHOUT CSP headers]
L -- No --> O{Security block?}
O -- Yes --> P[403 Forbidden, strict CSP]
O -- No --> Q[Vary: User-Agent]
Q --> R[generateRuntimeCSP - BEFORE: only / - AFTER: all catch-all routes]
R --> S[track + return]
Loading
Comments Outside Diff (1)
-
apps/sim/proxy.ts, line 277-278 (link)
Authenticated /invite/ pages still bypass runtime CSP
handleInvitationRedirects returns NextResponse.next() at line 186 when the visitor already has a session, and the proxy immediately returns that bare response at line 278 — before the new generateRuntimeCSP() call at line 286. Authenticated users landing on /invite/[id] therefore receive no runtime CSP, which means the GTM/GA4 domains are still absent on those pages. Since the PR's goal is to fix missing GTM tracking across all marketing-style routes, invite pages with a session are the one remaining gap.
Reviews (2): Last reviewed commit: "style: drop inline comment from proxy CS..." | Re-trigger Greptile
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
handleInvitationRedirects returns NextResponse.next() for authenticated
users, bypassing the catch-all block entirely, so it never picked up
the runtime CSP fix in the same way. Same class of gap flagged by review.
greptile-apps[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7cac9f2. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This branch was previously deployed
1 inactive
deployment
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|