Skip to content

fix(server): older clients get split permissions when they pair - #17341

Open
teoaliano wants to merge 1 commit into
pingdotgg:mainfrom
teoaliano:fix/legacy-scope-token-exchange
Open

teoaliano wants to merge 1 commit into
pingdotgg:mainfrom
teoaliano:fix/legacy-scope-token-exchange

Conversation

@teoaliano

Copy link
Copy Markdown

Problem

Released clients such as iOS 2.0.0 (TestFlight build 110) still request the pre-split scope names at token exchange (orchestration:read orchestration:operate terminal:operate relay:read, with review:write dropped as retired). Token exchange keeps only requested scopes that the grant holds, and nothing translates the old names. So every new session from those clients is stored with four scopes, even when the pairing link granted all of them. Folder browsing and host media fail with missing required scope: filesystem:read, cloning with source-control:write, provider sign-in with providers:manage, remote updates with environment:maintain, and Usage with diagnostics:read. Re-pairing does not help.

Reproduction on a macOS host running 0.0.46-nightly.20261007.2787, over Tailscale:

  1. t3 pair --tailscale. The stored link holds the full standard grant.
  2. Redeem the link in iOS 2.0.0 (110).
  3. The new auth_sessions row holds only ["orchestration:read","orchestration:operate","terminal:operate","relay:read"].

Change

Token exchange now expands a request made only of pre-split scopes by every permission split out of a requested parent, using the existing legacyParents map. It then intersects the result with the bootstrap grant as before. This follows the compatibility rule proposed in #16804, extended to every split permission per the maintainer follow-up about filesystem:read and the report about diagnostics:read.

  • Requests that name any granular scope are unchanged, so current clients can still narrow on purpose.
  • Requests without a scope are unchanged, since current clients already get the full grant.
  • The pairing grant still caps the result. A link granting orchestration:operate without providers:manage does not gain it.
  • Stored credentials are not touched. This only affects sessions minted after the server updates, so it stays within the fix(auth): keep old clients connected across scope changes #10298 policy. Already-paired clients still need to pair again once.

expandLegacyScopeRequest lives next to legacyParents in packages/contracts/src/auth.ts, and the comment on that map now says servers read old requests with it. One sentence in docs/internals/environment-auth.md describing token exchange is updated.

Scope and approval

Fixes #16804, which maintainers have triaged and confirmed as a bug, with this server-side compatibility rule proposed as the fix (maintainer comment). #17256 and #16856 were closed as duplicates of it. This PR covers only proposal item 1, the token-exchange rule. The mobile "missing permission" state (item 3) and the T3 Connect session refresh in #17308 are separate problems.

Verification

  • vp test run apps/server/src/auth/EnvironmentAuth.test.ts packages/contracts/src/auth.test.ts apps/server/src/auth/http.test.ts apps/server/src/auth/PairingGrantStore.test.ts: 64 passed.
  • Two new tests in EnvironmentAuth.test.ts:
    • The exact iOS 2.0.0 request against a standard pairing link now yields AuthStandardClientScopes, checked through bearer authentication.
    • A pre-split request against a link holding only orchestration:read, orchestration:operate and filesystem:read yields exactly those three, so the grant still caps the expansion.
  • With the server change reverted, both new tests fail (2 failed | 18 passed).
  • Three existing tests narrowed sessions by requesting orchestration:read alone, which is now a pre-split request. They request filesystem:read instead, so they still cover narrowing by a granular request. Their rejection cases are unchanged: ["access:write"], ["review:write"] and [] still fail with ServerAuthScopeNotGrantedError.
  • Related suites also pass: McpOAuth, CliTokenManager, publicConfig, RpcAuthorization and SessionStore (74 passed).
  • tsc --noEmit for apps/server and packages/contracts shows no errors, and vp lint and vp fmt --check are clean on the changed files.

Not checked: I did not build this server and pair a real iOS 2.0.0 client against it. Instead, I confirmed the client's request on a live host: a stored link with the full grant was consumed into a session holding exactly the four legacy scopes. As a stopgap on that host, I set the session's scopes in auth_sessions to the full standard list without a restart, and the phone's WebSocket requests then passed the filesystem:read check. The new test reproduces that request.

Done with Claude Code (Claude Opus 5.5) running in T3 Code.

🤖 Generated with Claude Code

Released clients such as iOS 2.0.0 request the pre-split scope names at
token exchange. The exchange kept only those names, so new sessions lost
filesystem:read, providers:manage, source-control:write and the other
permissions split out of them, even when the pairing grant included them.

Expand a request made only of pre-split scopes by the permissions split
out of each requested parent, then intersect with the grant as before.
Granular requests and requests without a scope are unchanged, and stored
credentials are still never widened.

Fixes pingdotgg#16804

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 8, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a focused compatibility fix that changes which permissions are issued during production pairing-token exchange, while retaining the pairing grant as the cap. Because it modifies authentication code and affects authorization scope issuance, human review is required.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d01411c5-362c-469d-af4e-340b367eb784
📥 Commits

Reviewing files that changed from the base of the PR and between 48b71f0 and c5644b2.

📒 Files selected for processing (4)
  • apps/server/src/auth/EnvironmentAuth.test.ts
  • apps/server/src/auth/EnvironmentAuth.ts
  • docs/internals/environment-auth.md
  • packages/contracts/src/auth.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Bootstrap token exchange now expands explicitly supplied requests that contain only legacy scopes before resolving the grant. The expansion remains limited to granted scopes. Tests and internal documentation cover this behavior.

Changes

Bootstrap scope compatibility

Layer / File(s) Summary
Define legacy scope expansion
packages/contracts/src/auth.ts
Adds expandLegacyScopeRequest for requests that contain only legacy scopes. Clarifies how clients and servers use parent-scope checks.
Apply and verify expansion
apps/server/src/auth/EnvironmentAuth.ts, apps/server/src/auth/EnvironmentAuth.test.ts, docs/internals/environment-auth.md
Bootstrap exchange expands explicit scope requests before grant resolution and filtering. Tests cover legacy requests, pairing-grant limits, and filesystem:read expectations. Documentation describes the expansion and the failure when no requested scopes are granted.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to c5644

Older clients that request only pre-split scopes now receive the permissions split out of them, up to what the pairing grant allows. Nothing in the supplied change shows a regression or a broader grant, so it looks safe to merge. A real iOS pairing against an updated server was not tested.

Architecture Summary

Architecture risk: 🟡 Medium · up to c5644

The change affects 3 systems.

Changed systems: packages/contracts, apps/server, docs

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — packages/contracts (library) was modified; 1 changed file maps to changed impact.
  • observed — apps/server (service) was modified; 2 changed files map to changed impact.
  • observed — docs (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in apps/server/src/auth/EnvironmentAuth.test.ts: The reusable dev-token exchange assertions now request and expect filesystem:read for bearer and DPoP access tokens instead of orchestration:read; the exchanges and token-type checks remain.
  • observed — Modified behavior in apps/server/src/auth/EnvironmentAuth.test.ts: The pairing-credential exchange test now requests and verifies filesystem:read instead of orchestration:read, including the requested scope on the follow-up reuse attempt.
  • observed — Modified behavior in apps/server/src/auth/EnvironmentAuth.test.ts: Adds coverage that a request using the pre-split scope vocabulary receives all standard client scopes after exchange.
  • observed — Modified behavior in apps/server/src/auth/EnvironmentAuth.test.ts: Adds coverage that a pairing grant containing orchestration:read, orchestration:operate, and filesystem:read limits a request for the two orchestration scopes to those granted scopes.

Reliability and maintainability

  • inferred — Risk-relevant change factors for packages/contracts: blast_radius_1; direct_dependents_1
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check Warning Issue #16804 requires provider-management clients to receive providers:manage and use provider-auth subscription, while restricted clients must see an explicit missing-permission UI state. The chang… Implement the remaining #16804 coding requirements, including the restricted-client missing-permission UI behavior and provider-auth subscription coverage, or separate this server compatibility change from the full #16804 fix and track the …
✅ Passed checks (3 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: older clients receive split permissions during pairing. It uses an appropriate conventional commit format.
Description check Passed The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the reproduction, implementation, scope limits, linked issue and maintainer approval, test…
Out of Scope Changes check Passed The changed server code, contract helper, documentation, and tests directly support the legacy-scope token-exchange compatibility rule from #16804. The expansion remains limited to legacy-only request…
Full details: Linked Issues check

Explanation

Issue #16804 requires provider-management clients to receive providers:manage and use provider-auth subscription, while restricted clients must see an explicit missing-permission UI state. The change expands legacy requests and preserves the pairing-grant cap. The reported tests cover token exchange and bearer authorization, but the change does not implement the restricted-client UI state or establish provider-auth subscription coverage. The issue's full coding requirements are therefore not met.

Resolution

Implement the remaining #16804 coding requirements, including the restricted-client missing-permission UI behavior and provider-auth subscription coverage, or separate this server compatibility change from the full #16804 fix and track the remaining work in a linked issue.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

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

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: iOS Provider accounts fails with missing providers:manage on fresh T3 Connect tokens

2 participants