Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 13 additions & 0 deletions src/components/XPixel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ import { Suspense, useEffect, useRef } from "react";

const X_PIXEL_ID = "re2t6";

/* Conversion events configured in the X Ads UI, keyed by what they mean here.
Reported with twq('event', <event id>, {...}); the call queues if uwt.js
hasn't loaded yet, and is a no-op in dev where the tag never loads. */
const X_EVENTS = {
pledgedToVote: "tw-re2t6-rekr4",
} as const;

/** Report an X conversion. `email` is optional and used by X for identity
matching — uwt.js hashes it before it leaves the browser. */
export function trackXEvent(event: keyof typeof X_EVENTS, email?: string) {
window.twq?.("event", X_EVENTS[event], { email_address: email ?? null });
}

declare global {
interface Window {
twq?: (...args: unknown[]) => void;
Expand Down
2 changes: 2 additions & 0 deletions src/components/elections/PledgeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { hubspotPageContext } from "@/lib/hubspot-context";
import { pledgeSharePath } from "@/lib/elections/pledge-share";
import { DEFAULT_ELECTION_SLUG, getElection } from "@/lib/elections/registry";
import { trackXEvent } from "@/components/XPixel";

/* "Pledge to vote" CTA — opens the same modal treatment as the navbar
Subscribe button. Submitting records the pledge (via /api/elections/pledge
Expand Down Expand Up @@ -108,6 +109,7 @@ export function PledgeButton({
}

posthog.capture("pledged_to_vote", { source, election: config.slug });
trackXEvent("pledgedToVote", email);

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 identity bypasses consent

When an in-region pledge succeeds in production, this call sends the pledger's email and pledge-conversion signal to X's advertising tag without an advertising-use disclosure or consent gate, exposing personal information to a third-party advertising platform outside the application's stated consent boundary.

How this was verified: The submitted email flows directly through trackXEvent to the globally mounted twq tag, and the pledge form and runtime contain no advertising-consent control.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/elections/PledgeButton.tsx
Line: 112

Comment:
**Advertising identity bypasses consent**

When an in-region pledge succeeds in production, this call sends the pledger's email and pledge-conversion signal to X's advertising tag without an advertising-use disclosure or consent gate, exposing personal information to a third-party advertising platform outside the application's stated consent boundary.

**How this was verified:** The submitted email flows directly through `trackXEvent` to the globally mounted `twq` tag, and the pledge form and runtime contain no advertising-consent control.

---

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

// keep the button disabled while we navigate to the shared page;
// prefer the server's record (canonical name + unguessable token)
router.push(pledgeSharePath(config, data.name || name, data.shareToken));
Expand Down
Loading