MAS: let the sandbox bind the OAuth loopback callback server - #51
Merged
Conversation
Google's desktop OAuth flow redirects to a loopback HTTP server on a random port, and custom URI schemes are no longer supported, so there is no alternative to listening. The MAS entitlements granted network.client but not network.server, so the App Sandbox denied listen() with EPERM and neither sign-in nor picking a document could complete. The failure compounded itself: the server was assigned before listen() could throw, and both flows guard startup with `if (!this.callbackServer)`. Every later attempt therefore saw a live-looking server, skipped startup, and built a redirect_uri on port 0 -- which browsers refuse with ERR_UNSAFE_PORT. The server is now assigned only once it is genuinely listening, and the error names the missing entitlement instead of surfacing a bare EPERM. No new provisioning profile is needed: sandbox entitlements are not profile-scoped. The profile carries only application-identifier, team-identifier, application-groups and keychain-access-groups, and the build already ships app-sandbox and network.client without either being listed there. Closes #50 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The helper's return annotation did not match vi.spyOn's instance type. Caught by CI, not locally: vitest's inline type check covers less than `tsc --noEmit` does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Picking a Google Doc fails in the App Store build:
listen EPERMon thefirst try,
http://localhost:0/callback…→ERR_UNSAFE_PORTon everytry after that.
Root cause.
resources/entitlements.mas.plistgrantedcom.apple.security.network.clientbut notcom.apple.security.network.server, so the App Sandbox deniedlisten().Google's desktop flow has no alternative — custom URI schemes are no
longer supported,
leaving loopback as the only redirect option.
Why the symptom changed on retry.
startCallbackServerSyncassignedthis.callbackServerbeforelisten()could throw, and bothsignIn()and
pickDocument()guard startup withif (!this.callbackServer). Everylater attempt saw a live-looking server, skipped startup, and left
callbackPortat0. The server is now assigned only once it isgenuinely listening, and the error names the entitlement.
No new provisioning profile. Decoded
dist.provisionprofilecarriesonly
application-identifier,team-identifier,application-groupsand
keychain-access-groups— sandbox entitlements are notprofile-scoped, and the build already ships
app-sandboxandnetwork.clientwithout either appearing there.Verification
Both tests were watched failing first, and the second reproduced the
exact production symptom: it timed out because the retry reused the
dead server, opened the browser on port 0, and waited forever for a
callback that could never arrive. After the fix it fails fast on the
second
createServercall instead.The entitlement itself is not unit-testable — it can only be confirmed by
the next TestFlight build.
plutil -lintpasses on the plist.pnpm test1033 passed / 62 files · typecheck clean · lint clean.Closes #50
🤖 Generated with Claude Code