Skip to content

fix(oauth): reject user-scoped clients on single-workspace hosts - #1894

Open
The-AarushiSingh wants to merge 4 commits into
UsefulSoftwareCo:mainfrom
The-AarushiSingh:fix/oauth-single-workspace
Open

fix(oauth): reject user-scoped clients on single-workspace hosts#1894
The-AarushiSingh wants to merge 4 commits into
UsefulSoftwareCo:mainfrom
The-AarushiSingh:fix/oauth-single-workspace

Conversation

@The-AarushiSingh

Copy link
Copy Markdown
Contributor

Fixes #1850

On local/desktop the executor subject is LOCAL_SUBJECT ("local") and every connection is org-scoped. createClient still accepted owner: "user", so those clients could never be used. The later mismatch error also used cloud wording (“A Workspace connection must use a Workspace app.”).

Change

  • In createClient, reject owner: "user" when deps.subject === "local".
  • Multi-workspace fixtures keep subjects like test-subject / subject-a / subject-b, so existing user-client tests are unchanged.
  • Mismatch error is now host-neutral: An org connection must use an org-owned OAuth client.

No new config. No silent remap of userorg.

Test

  • Existing oauth-remove / list / flow user-client tests still pass.
  • Added one case: subject "local" + owner: "user" returns the new error.

@The-AarushiSingh

Copy link
Copy Markdown
Contributor Author

cloud 13of16 is mcp-session-cap-eviction (no mcp-session-id on session-5), not this change. Rerunning the shard.

@devin-ai-integration

Copy link
Copy Markdown

Verdict: needs a decision from Rhys (mechanism question) — otherwise close but incomplete. Open 3 days; merges cleanly onto current main (the mismatch-message line moved to oauth-service.ts:1466 but auto-merges).

Ran on the PR merged onto main: bun run lint && bun run format:check — pass; bun run typecheck — pass (45/45); bun run --filter @executor-js/sdk test -- src/oauth-flow.test.ts src/oauth-remove-client.test.ts src/oauth-list-clients.test.ts — 57/57 pass. The failing CI shard is the cloud e2e mcp-session-cap-eviction scenario, not touched by this diff.

Decision needed

  • The guard keys on deps.subject === "local" (packages/core/sdk/src/oauth-service.ts:852). The SDK explicitly treats the subject as opaque — core-schema.ts:185 and executor.ts:537 both say it "carries host sentinels like "local", so nothing may parse it", and LOCAL_SUBJECT is a private constant in apps/local/src/executor.ts, not an SDK export. The React side solves the same host clamp with an explicit signal (organizationId === nullLOCAL_CONNECTION_OWNER in packages/react/src/plugins/connection-owner.tsx). If you're fine with the SDK reading the sentinel, this is mergeable with the fixes below; if not, this needs an explicit createExecutor option (e.g. a single-workspace flag set by apps/local) that the check reads instead. (Self-host binds real user ids as the subject — apps/host-selfhost/src/auth/identity.ts:71 — so it is correctly unaffected either way.)
  • Copy: the cloud mismatch message drops the product vocabulary ("Personal"/"Workspace" is what the cloud UI shows) for "org connection / org-owned OAuth client". The issue offered "reword per host" as the alternative; this rewords for everyone. Your call whether that's acceptable for cloud users.

Blocking regardless of the decision

  • Description says "Added one case: subject "local" + owner: "user" returns the new error" — there is no such test in the diff. The only test change is the string assertion update in oauth-flow.test.ts:877. The new branch is untested.
  • registerDynamicClient (oauth-service.ts:1273) calls createClient after the remote DCR registration succeeds, so on local a owner: "user" DCR request registers a client at the authorization server and then fails. The check should run before the network call (or DCR should pass through the same validation up front).
  • The rejection is a StorageError, which is consistent with the existing reserved-namespace checks in createClient, so fine — but the message says "single-workspace hosts", which is a concept the SDK has no name for; align the wording with whatever signal you pick above.
  • @executor-js/sdk is a published package and this changes a user-facing error string; needs a changeset (.changeset/*.md, "executor": patch per the existing ones).

Nothing pushed — the mechanism decision should come first so the contributor isn't reworked twice.

- Reject owner: 'user' when deps.subject === 'local'
- Keep user-owned OAuth clients working for other subjects
- Update mismatch error to avoid 'Workspace' terminology

Closes UsefulSoftwareCo#1850
@The-AarushiSingh
The-AarushiSingh force-pushed the fix/oauth-single-workspace branch from 46b3736 to 26e10d2 Compare September 4, 2026 15:09
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.

Single-workspace hosts allow creating user-scoped OAuth clients that can never be used

1 participant