security: eliminate anonymous write contamination in pilot events and apply tracking#347
security: eliminate anonymous write contamination in pilot events and apply tracking#347ctol3r wants to merge 1 commit into
Conversation
… apply tracking - /api/pilot-ops/events (frontend): enforce session.userId -> 401 before proxy - /api/pilot-ops/events (backend): requireClerkUserId second-layer guard - /api/track/apply: new auth-enforcing Next.js proxy for APPLY_CLICKED - /api/learning/track (backend): require x-clerk-user-id header, attach actor_id to metadata - useTrackEvent: route APPLY_CLICKED through /api/track/apply with credentials:include - tests: assert 401 contract for anonymous pilot event writes No durable write may occur without attributable actor continuity.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82b6540c46
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!actorId) { | ||
| return void res.status(401).json({ | ||
| error: 'Unauthorized', | ||
| error_description: 'x-clerk-user-id header is required. Route all tracking calls through an authenticated proxy.', | ||
| }); |
There was a problem hiding this comment.
Keep non-APPLY tracking events routable after auth gate
POST /api/learning/track now unconditionally returns 401 when x-clerk-user-id is missing, but this commit only adds an authenticated proxy for APPLY_CLICKED; useTrackEvent still sends other frontend events (PROFILE_VIEWED, EMPLOYER_VIEWED, JOB_VIEWED, etc.) directly to /api/learning/track without that header, so those events are silently dropped in production and analytics/experiments lose data for all non-apply interactions.
Useful? React with 👍 / 👎.
Summary
Closes the brief "eliminate anonymous write contamination" against the two routes named in the request. After this PR, no durable write may occur on these surfaces without an attributable Clerk-authenticated actor.
POST /api/pilot-ops/events(web proxy){error:'unauthenticated', detail:'session.userId required …'}before any upstream callPOST /api/pilot-ops/events(backend)x-clerk-user-idheader verbatim from proxyrequireClerkUserIdsecond-layer guard rejects empty/missingPOST /api/track/apply(web proxy)APPLY_CLICKED; 401 on anonymous; emitsactor_idto the backendlearning/trackendpointPOST /api/learning/track(backend)x-clerk-user-idheader; rejects empty; attachesactor_idto event metadatauseTrackEvent(client)APPLY_CLICKEDanonymouslyAPPLY_CLICKEDthrough/api/track/applywithcredentials:includeso the Clerk session cookie is attachedAttribution continuity contract
After the patch:
apps/web/app/api/{pilot-ops/events,track/apply}/route.ts) — checkssession.userIdon every POST; 401 before any backend call.apps/api/backend/src/routes/{pilotOps,learningTrack}.ts) — second layer requires thex-clerk-user-idheader; rejects empty.actor_idequal to the Clerk user id. Replay attribution can reconstruct which actor wrote which event for any historical run.replay.runId/replay.lineageKeyderivation (feat(replay): canonical deterministic replay identity (Wave 10) #343) is orthogonal: the actor identity is additional attribution, not a replacement.actor_idlives alongside the existing snapshot ownership fields; no rename.Files
apps/web/app/api/pilot-ops/events/route.tsapps/web/app/api/track/apply/route.tsapps/web/lib/learning/useTrackEvent.tsapps/web/__tests__/pilot-ops-events-route.test.tsapps/api/backend/src/routes/pilotOps.tsrequireClerkUserIdsecond-layer guardapps/api/backend/src/routes/learningTrack.tsTruth rules
Validation
__tests__/pilot-ops-events-route.test.ts)pnpm turbo run build --filter @vitalcv/web→ 13/13 tasks, 35sAnonymous actor extinction verdict
✅ Both routes now reject anonymous writes at TWO independent layers (web proxy + backend handler). No durable event row can be written without a Clerk-authenticated session. Existing replay-identity infrastructure (#343) is preserved verbatim; this PR layers actor attribution on top.
Notes
/api/pilot/events; the canonical route is/api/pilot-ops/events. Both layers are hardened./api/track/apply; that exact path is created by this PR.