Skip to content

fix(oauth): request the scopes the extension and CLI actually need - #1128

Open
EhabY wants to merge 2 commits into
mainfrom
fix/oauth-scopes
Open

EhabY wants to merge 2 commits into
mainfrom
fix/oauth-scopes

Conversation

@EhabY

@EhabY EhabY commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes VSC-24

Problem

With scope enforcement (coder/coder, next release after 2.37), OAuth login failed right after the token exchange: GET /users/me needs user:read. An audit against a live server found more gaps with the same root cause:

  • Resolving a workspace by owner/name (user:read) breaks remote SSH, ping, speedtest, support bundle, coder start and coder update.
  • Start builds need user:read (owner lookup), and stop builds need workspace:stop.
  • Workspaces shared by another user need organization_member:read, which is only grantable through the coder:workspaces.* composites.
  • coder start dry-runs a build when a workspace must update first (workspace:create).

Fix

coder:workspaces.operate coder:workspaces.access workspace:create user:read user:read_personal

Verified with a real OAuth grant on v2.25.3, v2.27.11, v2.30.9, v2.34.11, v2.37.3 and current main. Released servers ignore requested scopes, so nothing changes there. Stored sessions with the old scopes fail the existing coverage check and re-authenticate.

Size

  • Production: 1 file, +8/−7. Only the scope list in src/oauth/constants.ts changes.
  • Tests: +656/−8. That's 649 lines of new tests (test/scopes/ and the unit metatest), plus 7 lines updating existing OAuth fixtures.
  • CI, tooling and docs: +65/−9 (workflow, Vitest project, test:scopes script, CONTRIBUTING.md).

Tests

  • test/unit/oauth/scopeProbes.test.ts (unit, every PR, ~2s): uses the TypeScript checker to find every CoderApi request or stream method referenced in src/, including through Pick<CoderApi, …> and structural interfaces. It fails if one has no probe, or if a probe/exemption is stale.
  • test/scopes/ (pnpm test:scopes): runs every probe in probes.ts with a token limited to DEFAULT_OAUTH_SCOPES.
    • deployment.ts sets up a fresh deployment: an owner, a member, a coder-provider-only template, the member's workspace, and an owner workspace shared with the member.
    • It refuses a deployment that already has users, so it can't touch a real server.
    • Builds are awaited by following the job's log stream until the server closes it, with no polling.
    • State-changing probes always leave the workspace running, so one failure can't cascade.
    • List probes require results, since unreadable rows are dropped silently rather than refused.
    • Streams open through the extension's CoderApi. Streams that authorize per message must deliver one.
    • Known gaps assert their exact failure, so a server fix shows up as a failing test.
  • test/scopes/compose.yaml: Postgres plus coder-preview, used by CI and locally (see CONTRIBUTING.md):
    docker compose -f test/scopes/compose.yaml up -d --wait
    CODER_URL=http://localhost:7080 pnpm test:scopes
  • .github/workflows/oauth-scopes.yaml: runs when the scopes or test/scopes/** change, and nightly to catch server-side changes before they ship.

With the old scopes, 9 of 25 probes fail, each with the server's reason:

Probe Old scopes Fixed by
getAuthenticatedUser GET /users/me: 404 user:read
getWorkspaceByOwnerAndName GET /users/member/workspace/own: 404 user:read
getWorkspaceByOwnerAndName (shared) GET /users/admin/workspace/shared: 404 composites (organization_member:read)
getWorkspaceBuildByNumber GET /users/member/workspace/own/builds/1: 404 user:read
stopWorkspace POST …/builds: 403 You do not have permission to stop this workspace workspace:stop
stopWorkspace (shared) same workspace:stop
startWorkspace POST …/builds: 400 Unable to validate parameters: Failed to fetch workspace owner user:read
startWorkspace (shared) same composites (organization_member:read)
coder start dry-run POST /templateversions/…/dry-run: 404 workspace:create

Known gap: inbox notifications need inbox_notification:read, which no public scope grants, so the server closes the socket on the first notification. Fixed server-side in coder/coder#30176.


🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Sep 29, 2026

Copy link
Copy Markdown

VSC-24

Servers that enforce OAuth2 scopes rejected GET /users/me right after the
token exchange, so OAuth login always failed. The same gap broke resolving a
workspace by owner and name, which `coder ssh`, start, update, ping,
speedtest and support bundle all do, and stop builds were refused outright.

Request coder:workspaces.operate and coder:workspaces.access (which also
grant organization_member:read, required for shared workspaces and not
requestable on its own), workspace:create for `coder start` dry-runs when a
workspace must update, and user:read. Verified against v2.25 through v2.37
and current main.

Fixes VSC-24
Calls every Coder API method and stream the extension uses with a
DEFAULT_OAUTH_SCOPES token against coder-preview, on changes to the scope
or probe list and nightly. A unit test finds those methods in src/ with the
type checker and fails when one has no probe. test/scopes/compose.yaml runs
the same deployment locally.

This branch has not been deployed

No deployments
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