Skip to content

fix(activity-log): attribute bearer-token API writes to the key's user - #73120

Merged
jurajmajerik merged 8 commits into
masterfrom
activity-log/bearer-auth-attribution
Jul 24, 2026
Merged

fix(activity-log): attribute bearer-token API writes to the key's user#73120
jurajmajerik merged 8 commits into
masterfrom
activity-log/bearer-auth-attribution

Conversation

@jurajmajerik

Copy link
Copy Markdown
Contributor

Problem

API writes authenticated with a personal API key or OAuth token (e.g. via MCP) show up in activity logs as "PostHog" system entries. ActivityLoggingMiddleware captures the acting user before DRF auth runs, so bearer-token requests never get one. Affects models with signal-driven activity logging (experiments, holdouts, shared metrics, ...) — viewsets that log explicitly with request.user were fine.

Changes

  • Set the activity-log user after successful personal API key / OAuth authentication.
  • Show "via MCP" next to the timestamp on activity rows when the request came from an API client (x-posthog-client header, already captured and serialized, just never rendered).

How did you test this code?

Two regression tests: an experiment created via personal API key and via OAuth token gets an activity entry attributed to the key's user instead of system. Both fail without the auth change. No existing test covered bearer-auth attribution. Also ran the activityLogLogic jest tests, frontend typescript check, and mypy on posthog/auth.py.

@jurajmajerik
jurajmajerik requested a review from a team as a code owner July 23, 2026 10:41
@jurajmajerik jurajmajerik self-assigned this Jul 23, 2026
@trunk-io

trunk-io Bot commented Jul 23, 2026

Copy link
Copy Markdown

😎 This pull request was merged.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 0ae4be3 · box box-694cc8b3d98c · ready in 814s (push → usable) · build log · rebuilds on every push, torn down on close

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 23, 2026 10:42
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(activity-log): attribute bearer-toke..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Bundle size — 🔺 +254 B (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.38 MiB · 🔺 +254 B (+0.0%)

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files no change ███░░░░░░░ 27.5% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.07 MiB · 3,010 files 🔺 +271 B (+0.0%) ████████░░ 83.0% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.5 KiB ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
236.0 KiB src/taxonomy/core-filter-definitions-by-group.json
226.1 KiB ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.8 KiB src/lib/api.ts
94.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.18 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.18 MiB · 17 files no change ████░░░░░░ 38.1% of 5.72 MiB
Deferred (lazy) 2.07 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
713.8 KiB dist/toolbar/toolbar-app-HLTHZTTF.css
545.0 KiB dist/toolbar/chunk-chunk-JGF7ZYC3.js
484.2 KiB dist/toolbar/chunk-chunk-YMFODIKP.js
133.6 KiB dist/toolbar/chunk-chunk-YSVTUFEA.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-Y3ZKEAFE.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-ALQOU6WO.js
20.9 KiB dist/toolbar/chunk-chunk-VMBXRLPQ.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +3.4 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1352.21 MiB · 🔺 +3.4 KiB (+0.0%)

Playwright — all passed

All tests passed.

View test results →

⚠️ Backend snapshots — 11 updated (11 modified, 0 added, 0 deleted)

Query snapshots: Backend query snapshots updated

Changes: 11 snapshots (11 modified, 0 added, 0 deleted)

What this means:

  • Query snapshots have been automatically updated to match current output
  • These changes reflect modifications to database queries or schema

Next steps:

  • Review the query changes to ensure they're intentional
  • If unexpected, investigate what caused the query to change

Review snapshot changes →

⚠️ Backend coverage — 96.0% of changed backend lines covered — 3 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 96.0% (79 / 82)

File Patch Uncovered changed lines
posthog/api/test/test_team.py 50.0% 78–79
posthog/models/activity_logging/model_activity.py 80.0% 188

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30077258277 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.6% 8,414 / 21,249
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks ██████████████░░░░░░ 69.4% 30,050 / 43,291
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
signals ████████████████░░░░ 80.0% 22,084 / 27,597
cdp ████████████████░░░░ 81.0% 3,146 / 3,883
data_modeling █████████████████░░░ 82.7% 5,547 / 6,708
wizard █████████████████░░░ 84.8% 935 / 1,102
notebooks █████████████████░░░ 85.3% 7,275 / 8,531
agent_platform █████████████████░░░ 86.4% 3,807 / 4,405
actions █████████████████░░░ 86.6% 717 / 828
cohorts ██████████████████░░ 87.8% 4,488 / 5,114
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
exports ██████████████████░░ 88.4% 6,951 / 7,863
data_warehouse ██████████████████░░ 88.9% 11,887 / 13,378
business_knowledge ██████████████████░░ 89.0% 4,391 / 4,936
conversations ██████████████████░░ 89.3% 16,869 / 18,883
engineering_analytics ██████████████████░░ 89.4% 6,386 / 7,145
visual_review ██████████████████░░ 89.4% 5,818 / 6,505
dashboards ██████████████████░░ 89.5% 5,989 / 6,693
error_tracking ██████████████████░░ 89.7% 10,168 / 11,336
alerts ██████████████████░░ 90.0% 4,056 / 4,508
early_access_features ██████████████████░░ 90.1% 1,031 / 1,144
mcp_analytics ██████████████████░░ 90.1% 2,763 / 3,065
streamlit_apps ██████████████████░░ 90.4% 2,501 / 2,767
links ██████████████████░░ 90.6% 183 / 202
slack_app ██████████████████░░ 90.7% 9,028 / 9,951
marketing_analytics ██████████████████░░ 91.0% 11,792 / 12,964
stamphog ██████████████████░░ 91.1% 4,056 / 4,450
mcp_store ██████████████████░░ 91.9% 4,257 / 4,634
product_analytics ███████████████████░ 92.6% 5,852 / 6,322
notifications ███████████████████░ 92.6% 1,011 / 1,092
ai_observability ███████████████████░ 92.8% 15,041 / 16,214
surveys ███████████████████░ 93.0% 5,734 / 6,167
managed_migrations ███████████████████░ 93.1% 1,424 / 1,530
web_analytics ███████████████████░ 93.1% 14,426 / 15,492
posthog_ai ███████████████████░ 93.2% 1,326 / 1,422
approvals ███████████████████░ 93.3% 3,437 / 3,682
reminders ███████████████████░ 93.4% 468 / 501
workflows ███████████████████░ 93.6% 6,390 / 6,826
legal_documents ███████████████████░ 94.1% 1,568 / 1,667
endpoints ███████████████████░ 94.1% 8,640 / 9,177
messaging ███████████████████░ 94.3% 2,682 / 2,845
tracing ███████████████████░ 94.5% 2,670 / 2,826
review_hog ███████████████████░ 94.7% 6,814 / 7,199
skills ███████████████████░ 94.7% 3,300 / 3,483
logs ███████████████████░ 95.4% 10,012 / 10,498
experiments ███████████████████░ 95.7% 25,036 / 26,159
growth ███████████████████░ 96.1% 3,245 / 3,376
annotations ███████████████████░ 96.2% 732 / 761
replay_vision ███████████████████░ 96.2% 15,260 / 15,864
revenue_analytics ███████████████████░ 96.3% 1,887 / 1,960
feature_flags ███████████████████░ 96.5% 17,112 / 17,736
user_interviews ███████████████████░ 96.5% 2,638 / 2,734
access_control ███████████████████░ 96.9% 870 / 898
customer_analytics ███████████████████░ 97.1% 9,047 / 9,317
warehouse_sources ███████████████████░ 97.1% 330,460 / 340,224
data_catalog ███████████████████░ 97.5% 2,368 / 2,429
analytics_platform ████████████████████ 98.0% 2,153 / 2,197
metrics ████████████████████ 98.2% 2,491 / 2,536
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

@jurajmajerik jurajmajerik added the stamphog Request AI approval (no full review) label Jul 23, 2026
@stamphog

stamphog Bot commented Jul 23, 2026

Copy link
Copy Markdown

Note

🤖 stamphog reviewed 60283f0e250444e339ed58b161771882d6c80bae — verdict: REFUSED

Gates denied (auth deny-list hit) and the change genuinely touches authentication attribution logic in posthog/auth.py — real risky territory, not an incidental keyword. No substantive independent review exists (only a bot 👍 reaction), so this needs a human auth-focused review before merge.

  • 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
  • Gates denied: PR matches the auth deny-list and was classified as a cross-cutting T2 change (203 lines, 7 files) that requires human review, not auto-approval.
  • Modifies posthog/auth.py to change how the acting user is attributed after personal API key / OAuth authentication — genuine auth-surface behavior change, not an incidental keyword match.
  • No human or agent review with substantive content exists on the current head — only a bot reaction (👍 from greptile-apps) and a merge-queue bot comment, which is not independent assurance for auth-sensitive code.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list matches: auth
size 80L, 5F substantive, 203L/7F incl. docs/generated/snapshots — within ceiling
tier classified as T2-never: T2-never (203L, 7F, cross-cutting, fix)
stamphog 2.0.0b3 .stamphog/policy.yml @ d16551d · reviewed head 60283f0

@stamphog stamphog Bot removed the stamphog Request AI approval (no full review) label Jul 23, 2026
@posthog

posthog Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Visual changes approved by @jurajmajerik — baseline updated in cc6257b.

View this run in PostHog

10 changed.

return ids


def has_uncalculated_cohorts(team: Team, *filter_sources: Any) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This cohort-gating change (and the exposures-runner + test changes that go with it) is unrelated to bearer-token activity-log attribution.

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.

Yes, rebased

<div className="text-secondary">
<div className="text-secondary flex items-center gap-1.5">
<TZLabel time={logItem.created_at} />
{logItem.client && (

@a-lider a-lider Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The via MCP tag only renders on this Advanced Activity Logs page, not on the entity History tabs because the per-entity ActivityLogSerializer doesn't emit client.

image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image

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.

Added client to the serializer. We have two serializers for the same rows, hence the inconsistency, but I'd rather fix that in a follow-up.

@github-actions
github-actions Bot requested a deployment to preview-pr-73120 July 23, 2026 11:42 In progress
@veria-ai

veria-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 2 · PR risk: 0/10

@jurajmajerik
jurajmajerik force-pushed the activity-log/bearer-auth-attribution branch from 00970ca to 82e5706 Compare July 23, 2026 11:48
@github-actions
github-actions Bot requested a deployment to preview-pr-73120 July 23, 2026 11:48 In progress
Comment thread posthog/auth.py Outdated
Comment thread frontend/src/lib/components/ActivityLog/ActivityLog.tsx Outdated
10 updated
Run: 109e2fce-60de-40fe-8c3d-e367195e5002

Co-authored-by: jurajmajerik <22996112+jurajmajerik@users.noreply.github.com>

@a-lider a-lider left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, thanks for fixing impersonation!

Guard activity_storage writes in bearer auth behind a middleware-owned
request-scoped marker so direct authenticate() calls can't leak a stale
user into the thread-local. Update remaining activity payload assertions
for the new envelope fields.
@github-actions
github-actions Bot requested a deployment to preview-pr-73120 July 24, 2026 07:57 In progress
@jurajmajerik
jurajmajerik enabled auto-merge (squash) July 24, 2026 08:54
@jurajmajerik
jurajmajerik merged commit 83d4363 into master Jul 24, 2026
356 of 358 checks passed
@jurajmajerik
jurajmajerik deleted the activity-log/bearer-auth-attribution branch July 24, 2026 08:55
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-24 09:21 UTC Run
prod-us ✅ Deployed 2026-07-24 09:42 UTC Run
prod-eu ✅ Deployed 2026-07-24 09:44 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants