Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Toaster } from "sonner";
import { SubscribeModal } from "@/components/subscribe";
import { IdentifyUser } from "@/components/auth/IdentifyUser";
import { HubspotTracking } from "@/components/HubspotTracking";
import { XPixel } from "@/components/XPixel";

const GA_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;

Expand Down Expand Up @@ -77,6 +78,7 @@ export default function RootLayout({
<SubscribeModal />
<IdentifyUser />
<HubspotTracking />
<XPixel />
Comment thread
mikaalnaik marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Advertising pixel bypasses consent

When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. How this was verified: The root layout mounts XPixel unconditionally, its only guard checks NODE_ENV, and the repository contains no consent or opt-out mechanism.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/layout.tsx
Line: 81

Comment:
**Advertising pixel bypasses consent**

When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. **How this was verified:** The root layout mounts `XPixel` unconditionally, its only guard checks `NODE_ENV`, and the repository contains no consent or opt-out mechanism.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Real gap, but pre-existing and out of scope for this PR.

You are right that the privacy notice says "We rely on your consent, which you can withdraw" (src/app/privacy-notice/page.tsx:32) and there is no consent mechanism in the repo. But that is not something this PR introduces: GA, HubSpot, and PostHog all already load unconditionally on every production page view, gated only on NODE_ENV. The X pixel follows the exact same existing pattern.

Gating only the X pixel would be incoherent — it would leave three other trackers firing pre-consent while implying the problem was solved. A consent banner plus a preference store that gates all four belongs in its own PR. Flagged to the team rather than fixed here.

</body>
</html>
);
Expand Down
23 changes: 23 additions & 0 deletions src/components/XPixel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Script from "next/script";

/* X (Twitter) Ads universal website tag. `twq('config', <pixel id>)` loads the
tag and reports the initial page load; conversion events are then reported
with twq('event', 'tw-<pixel>-<event>', {...}) from wherever they happen.

Production only, so localhost and preview deployments stay out of the ad
data. To check the tag locally, temporarily drop the NODE_ENV guard. */

const X_PIXEL_ID = "re2t6";

export function XPixel() {
if (process.env.NODE_ENV !== "production") return null;

return (
<Script id="x-pixel" strategy="afterInteractive">
{`
!function(e,t,n,s,u,a){e.twq||(s=e.twq=function(){s.exe?s.exe.apply(s,arguments):s.queue.push(arguments);},s.version='1.1',s.queue=[],u=t.createElement(n),u.async=!0,u.src='https://static.ads-twitter.com/uwt.js',a=t.getElementsByTagName(n)[0],a.parentNode.insertBefore(u,a))}(window,document,'script');
Comment thread
mikaalnaik marked this conversation as resolved.
twq('config','${X_PIXEL_ID}');
`}
</Script>
);
}
Loading