Skip to content

feat(desktop): re-land explicit OAuth scopes now ceilings are seeded - #76812

Open
charlesvien wants to merge 2 commits into
masterfrom
matt/oauth-reland-explicit-scopes
Open

feat(desktop): re-land explicit OAuth scopes now ceilings are seeded#76812
charlesvien wants to merge 2 commits into
masterfrom
matt/oauth-reland-explicit-scopes

Conversation

@charlesvien

@charlesvien charlesvien commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problem

Ports PostHog/code#3974. PostHog Code still requests scope=* at sign-in, which blocks retiring the wildcard server-side and keeps the desktop token full-privilege.

PostHog/code#3411 shipped an explicit scope list and bumped OAUTH_SCOPE_VERSION, but the US/EU Code OAuth app ceilings were not seeded yet, so fresh sign-ins hit invalid_scope and PostHog/code#3668 reverted to *. Seeding and the *-to-ceiling narrowing (#70538) are now live in both regions, so the client change is unblocked.

Changes

All under products/desktop/packages/shared/:

  • OAUTH_SCOPES: 206 scopes — the 205 advertised in scopes_supported on us.posthog.com and eu.posthog.com (both regions publish identical sets), plus privileged llm_gateway:read last.
  • OAUTH_SCOPE_VERSION 6 to 7 so existing installs re-authorize onto the narrower set.
  • Comments document two guardrails: seed the ceiling in US and EU before shipping a non-* client, and never copy OAUTH_SCOPES_HIDDEN out of the generated MCP scope file.
  • Restores structural tests (no dups, llm_gateway:read last, well-formed strings) plus a fingerprint guard.

Why the hidden scopes are excluded

services/mcp/src/lib/oauth-scopes.generated.ts exports two lists: OAUTH_SCOPES_SUPPORTED and OAUTH_SCOPES_HIDDEN (batch_import_support, query_performance, wizard_session). Only the first belongs here. The hidden ones are staff-only, is_staff-gated, and subtracted out of UNPRIVILEGED_SCOPES in posthog/scopes.py, so a ["@default", "llm_gateway:read"] ceiling can never cover them. OAuthValidator.validate_scopes treats an explicit scope list as all-or-nothing, so including even one would fail every sign-in with invalid_scope.

Verified by running the branch's scope list against posthog/scopes.py on master:

Requested list vs ["@default", "llm_gateway:read"] vs the local dev app ceiling
212 (with hidden scopes) rejected rejected
206 (this PR) accepted accepted

Preconditions (already done): #70538 deployed in US and EU, Code app ceilings seeded to ["@default", "llm_gateway:read"] in both regions, real * sign-in verified to narrow correctly in US.

Follow-up worth tracking separately

Pinning an explicit list trades one failure mode for another. A * request is rewritten to the app's ceiling by validate_scopes, so it can never go stale. An explicit list cannot: if a scope is ever renamed or retired server-side, every already-installed build that still pins it fails /authorize with invalid_scope until users update. That is not a regression in this PR, but it does mean scope removals become a breaking change for shipped desktop builds, and there is currently no tolerance path at /authorize.

How did you test this code?

  • pnpm --filter @posthog/shared test from products/desktop/: 782 passed, including the oauth fingerprint and structural guards.
  • pnpm --filter @posthog/shared typecheck clean.
  • Scope list checked against posthog/scopes.py on master; its get_oauth_scopes_supported() reproduces exactly the 205 scopes live on both regions.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

N/A

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude ported this from PostHog/code#3974 at @charlesvien's direction using /porting-code-prs, applying the source patch with git am --directory=products/desktop/ so the commit keeps @MattBro's authorship. A follow-up commit removed the six hidden scopes after checking the list against the server's ceiling resolution.

@trunk-io

trunk-io Bot commented Aug 3, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
products/desktop/packages/shared/src/oauth.ts:230
**Development OAuth rejects privileged scope**

When signing in against the documented local development OAuth application with an empty scope ceiling, the shared scope list requests privileged `llm_gateway:read`, causing `/oauth/authorize` to return `invalid_scope` and preventing developer sign-in. The development application's ceiling needs to be seeded for this scope as part of this change, or the development flow needs a compatible scope list.

---

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

Reviews (1): Last reviewed commit: "feat(oauth): re-land explicit scopes now..." | Re-trigger Greptile

// Privileged: embedded agent model calls go through PostHog's LLM gateway
// (gateway.{region}.posthog.com), which requires this scope. Not in the
// advertised set above; granted via this app's seeded ceiling.
"llm_gateway:read",

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.

P1 Development OAuth rejects privileged scope

When signing in against the documented local development OAuth application with an empty scope ceiling, the shared scope list requests privileged llm_gateway:read, causing /oauth/authorize to return invalid_scope and preventing developer sign-in. The development application's ceiling needs to be seeded for this scope as part of this change, or the development flow needs a compatible scope list.

Prompt To Fix With AI
This is a comment left during a code review.
Path: products/desktop/packages/shared/src/oauth.ts
Line: 230

Comment:
**Development OAuth rejects privileged scope**

When signing in against the documented local development OAuth application with an empty scope ceiling, the shared scope list requests privileged `llm_gateway:read`, causing `/oauth/authorize` to return `invalid_scope` and preventing developer sign-in. The development application's ceiling needs to be seeded for this scope as part of this change, or the development flow needs a compatible scope list.

---

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

@MattBro MattBro Aug 4, 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.

Right that privileged scopes fail under an empty ceiling, but the premise doesn't hold for the documented dev app. generate_demo_data creates it with an explicit ceiling of UNPRIVILEGED_SCOPES | {llm_gateway:read, llm_gateway:write, wizard_session:read, wizard_session:write} (products/demo/backend/logic/products/hedgebox/matrix.py:1823), not an empty one, so llm_gateway:read resolves fine locally.

It did surface a real bug one line up though. The list also carried OAUTH_SCOPES_HIDDEN from the generated MCP scope file — batch_import_support:*, query_performance:*, wizard_session:*. Those are subtracted out of UNPRIVILEGED_SCOPES, so @default can never cover them, and validate_scopes rejects the whole request rather than dropping the offenders. Checked against posthog/scopes.py on master:

  • 212 scopes vs ["@default", "llm_gateway:read"]: rejected on all six
  • 212 scopes vs the dev app ceiling: rejected on batch_import_support:* and query_performance:*
  • 206 scopes (hidden ones removed): accepted against both

Removed in a432d28.

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.

Thanks for the correction — you're right, the dev app ceiling is explicitly seeded in generate_demo_data, not empty, so llm_gateway:read resolves fine there. My premise was wrong.

The hidden scope analysis is solid: batch_import_support, query_performance, and wizard_session are subtracted from UNPRIVILEGED_SCOPES, so @default can never cover them, and OAuthValidator.validate_scopes treats an explicit request as all-or-nothing — one unresolvable scope rejects the entire grant. The table in the PR description confirms it cleanly. The fix in a432d28 is correct.

@trunk-io

trunk-io Bot commented Aug 3, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@charlesvien
charlesvien requested a review from MattBro August 3, 2026 21:45

@dmarticus dmarticus 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.

greptile comment seems legit so just get that before you merge.

Request the live scopes_supported set plus llm_gateway:read instead of
"*", and bump OAUTH_SCOPE_VERSION 5 → 6 so installs re-authorize.

The Jul 22 revert (#3668) was required because #3411 shipped the explicit
list before US/EU OAuth app ceilings were seeded. Both Code apps are now
seeded to [@default, llm_gateway:read] and *→ceiling narrowing is live,
so the client change is unblocked.

Regenerated from us.posthog.com scopes_supported (203 scopes, includes
loop:* and post-#3411 additions); drop agents:* which left the advertised
set. Comment documents the seed-before-release guardrail so it doesn't
get bundled again without the ceiling.
@charlesvien
charlesvien force-pushed the matt/oauth-reland-explicit-scopes branch from 5d9625e to 3e1ef67 Compare August 4, 2026 03:13
batch_import_support, query_performance, and wizard_session are
OAUTH_SCOPES_HIDDEN server-side: subtracted out of UNPRIVILEGED_SCOPES,
so the app's "@default" ceiling can never cover them. /authorize treats
an explicit scope list as all-or-nothing, so requesting them would fail
every sign-in with invalid_scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAPcQaAZNzcWNhCh1nyBPi
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

React Doctor found 50 issues in 13 files · 8 errors & 42 warnings.

Errors

42 warnings

apps/code/src/main/platform-adapters/electron-updater.ts

packages/ui/src/features/folder-picker/GitHubRepoPicker.tsx

packages/ui/src/features/loops/components/LoopDetailView.tsx

packages/ui/src/features/loops/components/LoopForm.tsx

packages/ui/src/features/message-editor/components/PromptInput.tsx

packages/ui/src/features/message-editor/tiptap/useTiptapEditor.ts

packages/ui/src/features/sessions/components/SessionView.tsx

packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx

packages/ui/src/features/sessions/components/chat-thread/ToolGroup.tsx

packages/ui/src/features/sessions/components/session-update/ToolRow.tsx

packages/ui/src/features/sessions/components/session-update/toolCallUtils.tsx

packages/ui/src/features/skills/SkillCard.tsx

packages/ui/src/features/skills/SkillDetailPanel.tsx

Reviewed by React Doctor for commit a432d28.

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

Labels

feature/desktop Feature Tag: Desktop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants