From 07de147e77fbc07e666edd549f3b79996b25517d Mon Sep 17 00:00:00 2001 From: Hugues Pouillot Date: Thu, 13 Aug 2026 16:38:15 +0200 Subject: [PATCH] feat(audit): validate frontend-backend session linking Teach the comprehensive and identify audits to verify both tracing_headers and server request-context binding. Strengthen integration guidance so full-stack setups wire the same person and session across runtimes. Generated-By: PostHog Desktop Task-Id: affb0421-c754-4167-8998-6a25bd80c58a --- context/skills/audit-identify/config.yaml | 2 + context/skills/audit-identify/description.md | 2 +- .../references/2-identify-fix.md | 45 +++++++++++++++---- .../audit/references/3-identification.md | 35 +++++++++++++-- .../integration-v2/identify/description.md | 17 ++++--- 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/context/skills/audit-identify/config.yaml b/context/skills/audit-identify/config.yaml index f99c87e9..ecaa765d 100644 --- a/context/skills/audit-identify/config.yaml +++ b/context/skills/audit-identify/config.yaml @@ -12,6 +12,8 @@ shared_docs: - https://posthog.com/docs/getting-started/identify-users.md - https://posthog.com/docs/product-analytics/cutting-costs.md - https://posthog.com/docs/libraries/js/config.md + - https://posthog.com/docs/references/posthog-node.md + - https://posthog.com/docs/product-analytics/best-practices.md - https://posthog.com/docs/data/anonymous-vs-identified-events.md variants: - id: all diff --git a/context/skills/audit-identify/description.md b/context/skills/audit-identify/description.md index 23811759..137990eb 100644 --- a/context/skills/audit-identify/description.md +++ b/context/skills/audit-identify/description.md @@ -4,7 +4,7 @@ This skill audits an existing PostHog integration's **$identify implementation** The audit covers three lenses: -- **Fix** — correctness checks on `posthog.identify()` placement, distinct_id stability, cross-runtime identity, and `reset()` on logout. These are the existing data-integrity checks already in the broader PostHog audit, scoped to identification. +- **Fix** — correctness checks on `posthog.identify()` placement, distinct_id stability, cross-runtime person and session correlation, and `reset()` on logout. These are the existing data-integrity checks already in the broader PostHog audit, scoped to identification. - **Lifecycle quality** — how `$set` / `$set_once`, `alias()`, and `groupIdentify()` are used over time. Code-only checks. - **Optimize** — cost-side checks on `person_profiles` mode, identified-vs-anonymous traffic ratio, `_isIdentified()` guarding, and duplicate `$identify` / `$groupidentify` events. These use PostHog MCP to read the operator's tenant; they gracefully skip if MCP is unavailable. diff --git a/context/skills/audit-identify/references/2-identify-fix.md b/context/skills/audit-identify/references/2-identify-fix.md index 673faef4..df15e508 100644 --- a/context/skills/audit-identify/references/2-identify-fix.md +++ b/context/skills/audit-identify/references/2-identify-fix.md @@ -4,17 +4,18 @@ next_step: 3-identify-lifecycle.md # Step 2 — Identify (fix) -This step resolves five correctness checks **in parallel**, one subagent per check. The first four ids are reused by the broader PostHog audit so a fix once made here is observable from either entry point. The fifth (`identify-sequential-calls`) is audit-identify-specific. +This step resolves six correctness checks **in parallel**, one subagent per check. The first five ids are reused by the broader PostHog audit so a fix once made here is observable from either entry point. The sixth (`identify-sequential-calls`) is audit-identify-specific. - `identify-stable-distinct-id` - `identify-not-late` - `cross-runtime-distinct-id` +- `cross-runtime-session-id` - `identify-reset-on-logout` - `identify-sequential-calls` -## Skip case — no `posthog.identify` calls found +## Reduced case — no `posthog.identify` calls found -If Step 1's identify grep returned **zero** hits, resolve all five checks in a single `audit_resolve_checks` call with `status: "pass"` and `details: "skip: no posthog.identify call sites detected"`. Then continue to **`3-identify-optimize.md`**. Do not dispatch subagents. +If Step 1's identify grep returned **zero** hits, resolve these five identify-dependent checks in one `audit_resolve_checks` call with `status: "pass"` and `details: "skip: no posthog.identify call sites detected"`: `identify-stable-distinct-id`, `identify-not-late`, `cross-runtime-distinct-id`, `identify-reset-on-logout`, and `identify-sequential-calls`. Then dispatch **only Task D** (`cross-runtime-session-id`), because anonymous browser and server events still need the same session correlation. When Task D returns, continue to **`3-identify-lifecycle.md`**. ## Status @@ -24,9 +25,9 @@ Emit before dispatching: [STATUS] Auditing identify correctness ``` -## Action — dispatch five subagents in one message +## Action — dispatch six subagents in one message -Make **five `Agent` tool calls in a single message** so they run concurrently. Wait for all five to return, then continue to `3-identify-optimize.md`. Do not run any other tools between dispatch and the next step. +Make **six `Agent` tool calls in a single message** so they run concurrently. Wait for all six to return, then continue to `3-identify-lifecycle.md`. Do not run any other tools between dispatch and the next step. The bundled `identify-users.md` reference holds PostHog's authoritative guidance on `distinct_id`, `identify()` ordering, and cross-runtime identity. It's typically at `.claude/skills/audit-identify/references/identify-users.md`; if that path doesn't exist, discover it with `Glob` `**/skills/audit-identify/references/identify-users.md`. Each subagent reads it once before judging. @@ -96,7 +97,33 @@ Rule: Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for id `cross-runtime-distinct-id`, including `file` (path:line of the most relevant init or capture site) and `details` (one-line explanation). Return when the call completes. Do not write the audit report. ``` -### Task D — `identify-reset-on-logout` +### Task D — `cross-runtime-session-id` + +`description`: `Audit cross-runtime-session-id` + +`prompt`: +``` +You are an audit subagent. Resolve exactly one rule and return: cross-runtime-session-id. + +Read this skill's bundled `best-practices.md` reference once (typically `.claude/skills/audit-identify/references/best-practices.md`; otherwise discover it with `Glob` `**/skills/audit-identify/references/best-practices.md`). Focus on the browser-and-server runtime guidance. Do not treat tracing headers as authentication. + +Run **two** Greps in parallel: +- `posthog\.init\(|new PostHog\(|posthog\.Posthog\(|Posthog\(` — locate PostHog initialization across runtimes. +- `tracing_headers|setupExpressRequestContext|PosthogContextMiddleware|X-POSTHOG-SESSION-ID|x-posthog-session-id|withContext\(` — locate the client-to-server correlation wiring. + +Read each file that contains a hit, once. Determine whether the project initializes both a browser SDK and a server SDK. If it does, verify both halves of the hand-off: the browser sends the PostHog tracing headers to the actual backend hostname, and the server binds the incoming session id to request-scoped PostHog context. + +Rule: +- pass: `posthog-js` configures `tracing_headers` for the backend hostname and the server consumes the incoming session id through supported middleware (for example `setupExpressRequestContext` or `PosthogContextMiddleware`) or passes it as `sessionId` to request-scoped context. +- error: both browser and server runtimes initialize PostHog, but either the browser does not send tracing headers or the server does not bind `X-POSTHOG-SESSION-ID` to PostHog context. +- warning: both halves appear present, but the configured hostname or request-context flow cannot be matched to the backend from source. +- Skip (`pass` with details: "single runtime"): only a browser or only a server runtime initializes PostHog. +- Never accept a hand-written constant session id. The value must come from the browser SDK's current session through the request header. + +Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for id `cross-runtime-session-id`, including `file` (path:line of the missing or most relevant wiring) and `details` (one-line explanation of both the client and server evidence). Return when the call completes. Do not write the audit report. +``` + +### Task E — `identify-reset-on-logout` `description`: `Audit identify-reset-on-logout` @@ -118,7 +145,7 @@ Rule: Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for id `identify-reset-on-logout`, including `file` (path:line of the most relevant logout or reset site) and `details` (one-line explanation). Return when the call completes. Do not write the audit report. ``` -### Task E — `identify-sequential-calls` +### Task F — `identify-sequential-calls` `description`: `Audit identify-sequential-calls` @@ -156,6 +183,6 @@ Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for Return when the call completes. Do not write the audit report. ```` -## After all five return +## After all six return -Continue to **`3-identify-lifecycle.md`**. Do not write the report yet — that's Step 4's job after Step 3 finishes. +Continue to **`3-identify-lifecycle.md`**. Do not write the report yet — Step 6 writes it after the remaining checks finish. diff --git a/context/skills/audit/references/3-identification.md b/context/skills/audit/references/3-identification.md index 7ef51818..e94533bc 100644 --- a/context/skills/audit/references/3-identification.md +++ b/context/skills/audit/references/3-identification.md @@ -4,11 +4,12 @@ next_step: 4-event-capture.md # Step 3 — Identification -This step resolves four identification checks **in parallel**, one subagent per check: +This step resolves five identification checks **in parallel**, one subagent per check: - `identify-stable-distinct-id` - `identify-not-late` - `cross-runtime-distinct-id` +- `cross-runtime-session-id` - `identify-reset-on-logout` Each subagent owns its own grep, reads, evaluates its single rule, and emits one `audit_resolve_checks` call with one update. The ledger's mutex serializes concurrent writes — there's no race. @@ -21,9 +22,9 @@ Emit before dispatching: [STATUS] Auditing identification ``` -## Action — dispatch four subagents in one message +## Action — dispatch five subagents in one message -Make **four `Agent` tool calls in a single message** so they run concurrently. Wait for all four to return, then continue to `4-event-capture.md`. Do not run any other tools between dispatch and the next step. +Make **five `Agent` tool calls in a single message** so they run concurrently. Wait for all five to return, then continue to `4-event-capture.md`. Do not run any other tools between dispatch and the next step. The bundled `identify-users.md` reference holds PostHog's authoritative guidance on `distinct_id`, `identify()` ordering, and cross-runtime identity. It's typically at `.claude/skills/audit/references/identify-users.md`; if that path doesn't exist, discover it with `Glob` `**/skills/audit/references/identify-users.md`. Each subagent reads it once before judging. @@ -93,7 +94,33 @@ Rule: Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for id `cross-runtime-distinct-id`, including `file` (path:line of the most relevant init or capture site) and `details` (one-line explanation). Return when the call completes. Do not write the audit report. ``` -### Task D — `identify-reset-on-logout` +### Task D — `cross-runtime-session-id` + +`description`: `Audit cross-runtime-session-id` + +`prompt`: +``` +You are an audit subagent. Resolve exactly one rule and return: cross-runtime-session-id. + +Read this skill's bundled `best-practices.md` reference once (typically `.claude/skills/audit/references/best-practices.md`; otherwise discover it with `Glob` `**/skills/audit/references/best-practices.md`). Focus on the browser-and-server runtime guidance. Do not treat tracing headers as authentication. + +Run **two** Greps in parallel: +- `posthog\.init\(|new PostHog\(|posthog\.Posthog\(|Posthog\(` — locate PostHog initialization across runtimes. +- `tracing_headers|setupExpressRequestContext|PosthogContextMiddleware|X-POSTHOG-SESSION-ID|x-posthog-session-id|withContext\(` — locate the client-to-server correlation wiring. + +Read each file that contains a hit, once. Determine whether the project initializes both a browser SDK and a server SDK. If it does, verify both halves of the hand-off: the browser sends the PostHog tracing headers to the actual backend hostname, and the server binds the incoming session id to request-scoped PostHog context. + +Rule: +- pass: `posthog-js` configures `tracing_headers` for the backend hostname and the server consumes the incoming session id through supported middleware (for example `setupExpressRequestContext` or `PosthogContextMiddleware`) or passes it as `sessionId` to request-scoped context. +- error: both browser and server runtimes initialize PostHog, but either the browser does not send tracing headers or the server does not bind `X-POSTHOG-SESSION-ID` to PostHog context. +- warning: both halves appear present, but the configured hostname or request-context flow cannot be matched to the backend from source. +- Skip (`pass` with details: "single runtime"): only a browser or only a server runtime initializes PostHog. +- Never accept a hand-written constant session id. The value must come from the browser SDK's current session through the request header. + +Emit one `mcp__wizard-tools__audit_resolve_checks` call with a single update for id `cross-runtime-session-id`, including `file` (path:line of the missing or most relevant wiring) and `details` (one-line explanation of both the client and server evidence). Return when the call completes. Do not write the audit report. +``` + +### Task E — `identify-reset-on-logout` `description`: `Audit identify-reset-on-logout` diff --git a/context/skills/integration-v2/identify/description.md b/context/skills/integration-v2/identify/description.md index 35263ed5..ee9ce52d 100644 --- a/context/skills/integration-v2/identify/description.md +++ b/context/skills/integration-v2/identify/description.md @@ -30,12 +30,17 @@ capture buys nothing. Find the auth flow first: login and signup handlers, session callbacks. If the app has no concept of a user, there is nothing to identify — report that and stop. -If the app has both a client and a server, keep them on the same person. Set the -client SDK's `tracing_headers` to the backend's hostname (hostnames only) and it -adds the `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` request headers on its -own — do not hand-roll a fetch wrapper for it. The server reads those headers, but -they are client-controlled: prefer the authenticated user id for anything -security-sensitive. +If the app has both a client and a server, keep them in the same person and +session. Set the client SDK's `tracing_headers` to each backend hostname +(hostnames only). It adds `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` to +matching requests on its own, so do not hand-roll a fetch wrapper. Then bind both +incoming values to request-scoped PostHog context on the server. Use the SDK's +framework middleware where one exists (for example `setupExpressRequestContext` +or `PosthogContextMiddleware`); otherwise read the headers and pass the session as +`sessionId` to the server SDK's context API. Verify that the configured hostname +is the one the browser actually calls. These headers are client-controlled +analytics context: prefer the authenticated user id for security-sensitive +identity and authorization decisions. ## Reference