Skip to content

Fix OAuth redirect URI mismatch for org-scoped client-id metadata#1197

Merged
RhysSullivan merged 1 commit into
mainfrom
fix/cimd-org-redirect-uri
Jun 29, 2026
Merged

Fix OAuth redirect URI mismatch for org-scoped client-id metadata#1197
RhysSullivan merged 1 commit into
mainfrom
fix/cimd-org-redirect-uri

Conversation

@RhysSullivan

Copy link
Copy Markdown
Owner

When connecting an OAuth integration while an org is active, the authorize request failed at exact-match providers (e.g. PostHog) with:

{"error":"invalid_request","error_description":"Mismatching redirect URI."}

Cause

The org selector was moved into the OAuth state in #1147 (the cloud callback reads it back from state), but #1148 (the CIMD registry) re-added an executor_org query param onto the redirect_uris of org-scoped client-id metadata documents. So the two sides disagreed:

  • Registered in .../client-id-metadata/<org>.json: https://executor.sh/api/oauth/callback?executor_org=<org>
  • Sent by the client: https://executor.sh/api/oauth/callback (bare; oauthCallbackUrl)

Providers compare redirect_uri as an exact string, so the extra query param was rejected. The executor_org param was also inert: nothing reads it on the callback (org comes from state).

Fix

Org targets keep their distinct client_id URL, but all targets now register the same bare callback redirect_uri, matching what the client sends and the design intent recorded in e2e/cloud/oauth-callback-org-scope.test.ts ("the provider state must carry the org selector without adding provider-facing query params to redirect_uri").

Removed the now-unused executor_org append (and the orphaned orgSlugFromMetadataTarget helper).

Tests

  • Updated oauth-client-metadata.test.ts: org target now expects a bare callback; a stray legacy executor_org query param is asserted inert (stays on client_id, never leaks into redirect_uri).
  • vitest run (scoped) green, typecheck, scoped lint + format:check clean.

Note: takes effect once executor.sh is deployed (the metadata doc is production-served).

Org-scoped client-id metadata documents registered their callback with an
executor_org query param on redirect_uri, but the client sends the bare
callback and the org is carried in the OAuth state (#1147). Providers that
exact-match redirect_uri (PostHog) rejected the authorize request with
"Mismatching redirect URI". Org targets keep their distinct client_id URL
but now register the same bare callback redirect_uri as every other target.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 9eb8632 Commit Preview URL

Branch Preview URL
Jun 28 2026, 11:59 PM

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown

Greptile Summary

Removes a stale executor_org query parameter that was being appended to redirect_uris in org-scoped client-id metadata documents, causing exact-match OAuth providers (e.g. PostHog) to reject the authorize request with "Mismatching redirect URI."

  • oauth-client-metadata.ts: Deletes OAUTH_CALLBACK_ORG_QUERY_PARAM, the now-dead orgSlugFromMetadataTarget helper, and the two lines that appended the param to redirectUri. All targets — including org-scoped ones — now register the same bare callback URL. Org identity continues to travel in the OAuth state, read back by decodeOAuthCallbackState in apps/cloud/src/start.ts.
  • oauth-client-metadata.test.ts: Test assertions updated to expect the bare callback in both the path-scoped and legacy query-param cases.
  • .changeset/cimd-org-redirect-uri.md: Patch-level changeset entry for @executor-js/api.

Confidence Score: 5/5

Safe to merge — the change is a targeted deletion of dead code that was causing a production OAuth failure, with no behavioral impact on non-org targets.

The org resolver in apps/cloud/src/start.ts has always read the org from the OAuth state via decodeOAuthCallbackState, never from a URL query parameter, so removing the executor_org append is purely a correction of a metadata document that disagreed with the actual authorize request. No other production code reads executor_org from a callback URL; remaining references are test-only negative assertions. The fix is small, well-commented, and covered by updated unit tests and an existing e2e scenario that already asserted the bare redirect URI contract.

No files require special attention.

Important Files Changed

Filename Overview
packages/core/api/src/server/oauth-client-metadata.ts Removes OAUTH_CALLBACK_ORG_QUERY_PARAM, orgSlugFromMetadataTarget, and the redirect-URI mutation; all targets now emit a bare callback URL. The callback-state path in start.ts correctly handles org resolution from state.
packages/core/api/src/server/oauth-client-metadata.test.ts Tests updated to assert bare redirect_uri for org-scoped and legacy query-param cases; legacy test renamed to accurately reflect the inert nature of a stray executor_org param.
.changeset/cimd-org-redirect-uri.md Patch-level changeset entry for @executor-js/api; description accurately reflects the fix.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as Browser Client
    participant CIMD as Client-ID Metadata<br/>(executor.sh/api/oauth/client-id-metadata/acme.json)
    participant Provider as OAuth Provider<br/>(e.g. PostHog)
    participant Callback as Callback Handler<br/>(executor.sh/api/oauth/callback)

    Client->>CIMD: GET /api/oauth/client-id-metadata/acme.json
    Note over CIMD: client_id = .../acme.json (org-scoped)<br/>redirect_uris = [.../oauth/callback] (bare, no executor_org)
    CIMD-->>Client: "{ client_id, redirect_uris: [bare callback] }"

    Client->>Provider: "GET /authorize?client_id=...&redirect_uri=.../oauth/callback&state={orgSlug,...}"
    Note over Provider: Exact-match check:<br/>redirect_uri == registered redirect_uri ✓

    Provider-->>Client: "302 → /oauth/callback?code=...&state={orgSlug,...}"

    Client->>Callback: "GET /oauth/callback?code=...&state={orgSlug,...}"
    Note over Callback: decodeOAuthCallbackState(state)<br/>→ reads orgSlug from state, not from URL params
    Callback-->>Client: 200 Connected
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as Browser Client
    participant CIMD as Client-ID Metadata<br/>(executor.sh/api/oauth/client-id-metadata/acme.json)
    participant Provider as OAuth Provider<br/>(e.g. PostHog)
    participant Callback as Callback Handler<br/>(executor.sh/api/oauth/callback)

    Client->>CIMD: GET /api/oauth/client-id-metadata/acme.json
    Note over CIMD: client_id = .../acme.json (org-scoped)<br/>redirect_uris = [.../oauth/callback] (bare, no executor_org)
    CIMD-->>Client: "{ client_id, redirect_uris: [bare callback] }"

    Client->>Provider: "GET /authorize?client_id=...&redirect_uri=.../oauth/callback&state={orgSlug,...}"
    Note over Provider: Exact-match check:<br/>redirect_uri == registered redirect_uri ✓

    Provider-->>Client: "302 → /oauth/callback?code=...&state={orgSlug,...}"

    Client->>Callback: "GET /oauth/callback?code=...&state={orgSlug,...}"
    Note over Callback: decodeOAuthCallbackState(state)<br/>→ reads orgSlug from state, not from URL params
    Callback-->>Client: 200 Connected
Loading

Reviews (1): Last reviewed commit: "Fix OAuth redirect URI mismatch for org-..." | Re-trigger Greptile

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 9eb8632 Jun 29 2026, 12:00 AM

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1197

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1197

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1197

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1197

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1197

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1197

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1197

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1197

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1197

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1197

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1197

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1197

executor

npm i https://pkg.pr.new/executor@1197

commit: 9eb8632

@RhysSullivan RhysSullivan merged commit fd29abe into main Jun 29, 2026
15 of 16 checks passed
@RhysSullivan RhysSullivan mentioned this pull request Jun 29, 2026
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.

1 participant