Repository navigation
Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — Pairing codes now accept hyphens and whitespace without changing the underlying secret. Expiration, single-use consumption, proof-key binding and scope checks remain unchanged. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
# Conflicts: # apps/server/src/auth/PairingGrantStore.ts
|
Note Written by Merged current |
Problem
One-time pairing codes typed with ASCII hyphens or spaces are rejected by the server with
401 auth_invalid, while the same code without separators works. This addresses the separator-rejection part of #4889. The case-only fix in #22089 is already merged; the separate Android false-timeout symptom is outside this change.Change
Before the one-time pairing-link lookup, remove whitespace and ASCII hyphens only when the remaining value matches the existing generated-code alphabet and length, then use the existing uppercase canonical form. Exact desktop bootstrap matching happens first. The existing atomic consume, expiry, scope, and proof-key checks continue to apply to the canonical pairing code.
Extend the existing
PairingGrantStoretests for formatted codes, invalid lengths/alphabet/separators, mixed raw/formatted concurrent consumption, proof-key and scope rejection, and exact desktop credentials.Scope and approval
This is a small focused fix for an obvious input-formatting bug in the existing pairing capability. It changes the shared server credential boundary used by OAuth and browser-session pairing, so web, desktop, and mobile clients benefit without client or wire-contract changes. It also applies to remote connections because normalization happens on the destination server.
It does not change Android request timing or claim to close the entire issue. No competing open separator fix was found; #22089 explicitly covered case only.
Verification
Windows x64, Node 26.12.1, locked Effect 4.1.2, base
6577c6b6185167c0e502fa01f9e8e8f2dee6ff25.apps/server,vp test run src/auth/PairingGrantStore.test.ts --maxWorkers 1failed in the four formatted-code cases, with seven controls passing. Failure:UnknownBootstrapCredentialErrorfor an issued valid code.apps/server,vp test run src/auth/PairingGrantStore.test.ts src/provider/AgentContextBudget.test.ts --maxWorkers 1passed all 13 tests in two files.vp fmt --check apps/server/src/auth/PairingGrantStore.ts apps/server/src/auth/PairingGrantStore.test.tsandvp lintwith the same two paths passed.vp run knip:check:filesandvp run knip:check:exportsboth passed.vp run --filter ./apps/server generate --checkpassed.vp run --cache --parallel --concurrency-limit 1 --filter @t3tools/evals --filter @t3tools/scripts --filter t3 typecheckpassed all three affected packages.vp run check:changed --concurrency 1was attempted on this branch but blocked before its check stages: the ignored-input Git scan follows Windows pnpm junctions and hits the known traversal problem covered by #32023. The wrapper was stopped and is not claimed passed; its cache was not written. Every selected check was run individually with the same scope. Server tests used their owning package configuration because the root browser-project initialization selects port 62126, in this machine's Windows-excluded range; that separate problem is covered by #24007. No repository configuration or check was weakened.Real network E2E
Ran the actual server CLI twice, before and after the fix, each with a fresh temporary data directory, isolated home and environment, and a reserved loopback port:
The CLI-issued raw code authenticated an administrator through
/oauth/token. The fixture then issued disposable read-only codes through/api/auth/pairing-tokenand exercised the actual HTTP handlers, persistence, authentication, and WebSocket upgrade. No provider prompt or live T3 state was used. Credentials were kept out of the transcript. Both owned server processes were stopped afterward.Before, retrying each same issued code without separators succeeded, proving the credentials and transport were valid. After, all five formatted exchanges established authenticated sessions and returned 200 from
/api/orchestration/shell; all three Bearer flows also minted WebSocket tickets and opened/ws. Reusing the original raw code returned 401 in every flow. Slash separators and an invalid alphabet remained rejected without spending a valid code.This verifies the actual server pairing workflow over loopback HTTP. Native Android/iOS entry, Tailscale HTTPS transport, and the reported Android false timeout were not exercised; there is no changed visual surface.
Implemented with OpenAI GPT-6 via the Codex harness.