From eb6c4dc0fe9b89b73041ed14734443011222db67 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Tue, 21 Jul 2026 19:30:58 -0700 Subject: [PATCH] revert oauth scopes to wildcard --- packages/shared/src/oauth.test.ts | 31 +---- packages/shared/src/oauth.ts | 217 +----------------------------- 2 files changed, 11 insertions(+), 237 deletions(-) diff --git a/packages/shared/src/oauth.test.ts b/packages/shared/src/oauth.test.ts index c42ec992d0..5e73af4447 100644 --- a/packages/shared/src/oauth.test.ts +++ b/packages/shared/src/oauth.test.ts @@ -20,35 +20,14 @@ describe("OAUTH_SCOPES guard", () => { fingerprint, }).toMatchInlineSnapshot(` { - "fingerprint": -1043925073, - "scopeCount": 198, - "scopeVersion": 6, + "fingerprint": 42, + "scopeCount": 1, + "scopeVersion": 5, } `); }); - // Structural guards that catch drift cheaply, independent of the exact list. - // OAUTH_SCOPES must mirror OAUTH_SCOPES_SUPPORTED in the API's generated - // services/mcp/src/lib/oauth-scopes.generated.ts (plus llm_gateway:read); this - // repo can't assert cross-repo equality, so these check the shape invariants. - it("contains no duplicate scopes", () => { - expect(OAUTH_SCOPES.length).toBe(new Set(OAUTH_SCOPES).size); - }); - - it("includes the privileged llm_gateway:read scope exactly once, kept last", () => { - expect( - OAUTH_SCOPES.filter((scope) => scope === "llm_gateway:read"), - ).toHaveLength(1); - expect(OAUTH_SCOPES.at(-1)).toBe("llm_gateway:read"); - }); - - it("only contains well-formed scope strings", () => { - const openidConnectScopes = new Set(["openid", "profile", "email"]); - const malformed = OAUTH_SCOPES.filter( - (scope) => - !openidConnectScopes.has(scope) && - !/^[a-z_]+:(read|write)$/.test(scope), - ); - expect(malformed).toEqual([]); + it("requests the grandfathered wildcard grant", () => { + expect(OAUTH_SCOPES).toEqual(["*"]); }); }); diff --git a/packages/shared/src/oauth.ts b/packages/shared/src/oauth.ts index f6cc78eb75..bda398cb77 100644 --- a/packages/shared/src/oauth.ts +++ b/packages/shared/src/oauth.ts @@ -4,218 +4,13 @@ export const POSTHOG_US_CLIENT_ID = "HCWoE0aRFMYxIxFNTTwkOORn5LBjOt2GVDzwSw5W"; export const POSTHOG_EU_CLIENT_ID = "AIvijgMS0dxKEmr5z6odvRd8Pkh5vts3nPTzgzU9"; export const POSTHOG_DEV_CLIENT_ID = "DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ"; -// Mirrors the scopes PostHog advertises as grantable: OAUTH_SCOPES_SUPPORTED in the API's -// services/mcp/src/lib/oauth-scopes.generated.ts, published as scopes_supported at -// /.well-known/oauth-authorization-server, plus the one privileged scope (llm_gateway:read) -// the LLM gateway requires, which the advertised set excludes. Requesting this explicit set -// instead of "*" keeps the token least-privilege; the privileged extra is granted via this -// app's seeded scope ceiling. Keep in sync with that generated list; bump OAUTH_SCOPE_VERSION -// below whenever the set changes. -export const OAUTH_SCOPES = [ - "access_control:read", - "access_control:write", - "account:read", - "account:write", - "action:read", - "action:write", - "activity_log:read", - "activity_log:write", - "agent_approvals:read", - "agent_approvals:write", - "agents:read", - "agents:write", - "alert:read", - "alert:write", - "annotation:read", - "annotation:write", - "approvals:read", - "approvals:write", - "batch_export:read", - "batch_export:write", - "batch_import:read", - "batch_import:write", - "business_knowledge:read", - "business_knowledge:write", - "cohort:read", - "cohort:write", - "comment:read", - "comment:write", - "conversation:read", - "conversation:write", - "customer_analytics:read", - "customer_analytics:write", - "customer_journey:read", - "customer_journey:write", - "customer_profile_config:read", - "customer_profile_config:write", - "dashboard:read", - "dashboard:write", - "dashboard_template:read", - "dashboard_template:write", - "data_catalog:read", - "data_catalog:write", - "data_catalog_approval:read", - "data_catalog_approval:write", - "dataset:read", - "dataset:write", - "early_access_feature:read", - "early_access_feature:write", - "element:read", - "element:write", - "email", - "endpoint:read", - "endpoint:write", - "engineering_analytics:read", - "engineering_analytics:write", - "error_tracking:read", - "error_tracking:write", - "evaluation:read", - "evaluation:write", - "event_definition:read", - "event_definition:write", - "event_filter:read", - "event_filter:write", - "experiment:read", - "experiment:write", - "experiment_holdout:read", - "experiment_holdout:write", - "experiment_saved_metric:read", - "experiment_saved_metric:write", - "export:read", - "export:write", - "external_data_schema:read", - "external_data_schema:write", - "external_data_source:read", - "external_data_source:write", - "feature_flag:read", - "feature_flag:write", - "field_note:read", - "field_note:write", - "file_system:read", - "file_system:write", - "file_system_shortcut:read", - "file_system_shortcut:write", - "group:read", - "group:write", - "health_issue:read", - "health_issue:write", - "heatmap:read", - "heatmap:write", - "hog_flow:read", - "hog_flow:write", - "hog_function:read", - "hog_function:write", - "ingestion_warning:read", - "ingestion_warning:write", - "insight:read", - "insight:write", - "insight_variable:read", - "insight_variable:write", - "integration:read", - "integration:write", - "legal_document:read", - "legal_document:write", - "link:read", - "link:write", - "live_debugger:read", - "live_debugger:write", - "llm_analytics:read", - "llm_analytics:write", - "llm_prompt:read", - "llm_prompt:write", - "llm_provider_key:read", - "llm_provider_key:write", - "llm_skill:read", - "llm_skill:write", - "logs:read", - "logs:write", - "loop:read", - "loop:write", - "marketing_analytics:read", - "marketing_analytics:write", - "mcp_analytics:read", - "mcp_analytics:write", - "metrics:read", - "metrics:write", - "notebook:read", - "notebook:write", - "openid", - "organization:read", - "organization:write", - "organization_integration:read", - "organization_integration:write", - "organization_member:read", - "organization_member:write", - "person:read", - "person:write", - "plugin:read", - "plugin:write", - "product_enablement:read", - "product_enablement:write", - "product_tour:read", - "product_tour:write", - "profile", - "project:read", - "project:write", - "property_definition:read", - "property_definition:write", - "query:read", - "query:write", - "replay_scanner:read", - "replay_scanner:write", - "revenue_analytics:read", - "revenue_analytics:write", - "session_recording:read", - "session_recording:write", - "session_recording_playlist:read", - "session_recording_playlist:write", - "sharing_configuration:read", - "sharing_configuration:write", - "signal_scout:read", - "signal_scout:write", - "streamlit_app:read", - "streamlit_app:write", - "subscription:read", - "subscription:write", - "survey:read", - "survey:write", - "tagger:read", - "tagger:write", - "task:read", - "task:write", - "ticket:read", - "ticket:write", - "tracing:read", - "tracing:write", - "uploaded_media:read", - "uploaded_media:write", - "usage_metric:read", - "usage_metric:write", - "user:read", - "user:write", - "user_interview:read", - "user_interview:write", - "vision_action:read", - "vision_action:write", - "visual_review:read", - "visual_review:write", - "warehouse_objects:read", - "warehouse_objects:write", - "warehouse_table:read", - "warehouse_table:write", - "warehouse_view:read", - "warehouse_view:write", - "web_analytics:read", - "web_analytics:write", - "webhook:read", - "webhook:write", - // Privileged: the embedded agent's 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. Without it the gateway 403s every call. - "llm_gateway:read", -]; +// Wildcard, not the explicit scope list: the prod OAuth apps have no seeded scope ceiling, +// so /oauth/authorize rejects the privileged llm_gateway:read with invalid_scope while "*" +// is grandfathered. Re-land the explicit list only after the US and EU app ceilings are +// seeded with ["@default", "llm_gateway:read"]. Bump OAUTH_SCOPE_VERSION on any change. +export const OAUTH_SCOPES = ["*"]; -export const OAUTH_SCOPE_VERSION = 6; +export const OAUTH_SCOPE_VERSION = 5; // Token refresh settings export const TOKEN_REFRESH_BUFFER_MS = 30 * 60 * 1000; // 30 minutes before expiry