uttrflow / uttrflow/uttrflow-swift

Browser sign-in trusts the first loopback callback before checking state, enables address reuse, and opens an unchecked device-flow URL

Open
#695 0 comments 0 reactions 0 assignees View on GitHub
area:general bug P0 security
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## What happens

The browser sign-in listens on a loopback port and trusts the first request that looks like a callback. Three hardening gaps are verified on `main` (26d7bc1):

1. **The first well-formed callback wins, whatever its `state`.** `SystemLoopbackListener.handle` (`Sources/UttrflowAccount/LoopbackListener+System.swift:105-115`) stores any request that has a `code` and a `state` in `received` and resumes the waiter. It also answers that request with the "Signed in" page (`:107`, `:132-145`). The state is only compared later, in `HTTPAuthenticationService.awaitBrowser` (`Sources/UttrflowAccount/HTTPAuthenticationService.swift:202-207`). A mismatch there throws and ends the attempt. Any local process that connects to the port first with `GET /callback?code=x&state=y` makes the real sign-in fail. The port comes from the OS and the range is small enough to scan. The browser coming back afterwards is never read. PKCE (`:216`) means a stolen or injected code cannot be spent, so this is a denial of sign-in and not a takeover.
2. **Address reuse is switched on for a one-shot ephemeral port.** `parameters.allowLocalEndpointReuse = true` (`LoopbackListener+System.swift:33`). The listener binds `port: .any` and never needs to rebind a fixed port, so the flag only widens who can share the socket.
3. **The device-code sign-in opens whatever address the server sends.** `beginDeviceSignIn` builds `verificationURL` from `verification_uri_complete` / `verification_uri` with `URL(string:)` and checks nothing else (`HTTPAuthenticationService.swift:157-159`). `OnboardingFlow` passes it to `NSWorkspace.shared.open` (`Sources/UttrflowUX/OnboardingFlow.swift:334`, `Sources/Uttrflow/Onboarding/OnboardingWindowController.swift:46-47`). A `file:`, `x-apple.systempreferences:` or other app-scheme URL in that response would be opened by the system. Only the backend (over TLS) supplies it today.

Also, accepted connections are appended to `connections` with no cap while an attempt is pending (`:95-101`).

## Why it matters

Sign-in is the one flow that ends with a long-lived refresh token in the Keychain. A person whose sign-in keeps failing with "that sign-in does not answer this attempt" has no way to tell that something else answered first. Checking the server-supplied URL costs one line and removes a class of mistake, or compromise, on the server side.

## How to reproduce

1. Start a browser sign-in and note the port in the `redirect_uri` of the page that opens.
2. Before finishing in the browser, run `curl "http://127.0.0.1:/callback?code=x&state=y"`. It answers with the "Signed in" page.
3. Finish sign-in in the browser. The app reports that the sign-in does not answer this attempt.

A unit test can do the same against `SystemLoopbackListener` with two requests and no browser.

## Acceptance criteria

- The listener is given the expected `state`. It answers a callback with any other state (or none) with the failure page, does not resolve the wait, and keeps listening for the real one until the attempt is closed.
- The "Signed in" page is sent only for the callback that matches.
- `allowLocalEndpointReuse` is removed, or a comment records why an ephemeral port needs it.
- `beginDeviceSignIn` refuses a verification URL whose scheme is not `https`, before anything is opened.
- Connections accepted per attempt are capped.
- Tests in `Tests/UttrflowAccountTests/LoopbackTests.swift` and `HTTPAuthenticationServiceTests.swift` cover a wrong-state callback followed by the right one, and a non-`https` verification URL.

Contributor guide

Open the contributing guide

Research direction

Start with SystemLoopbackListener in Sources/UttrflowAccount/LoopbackListener+System.swift and HTTPAuthenticationService in Sources/UttrflowAccount/HTTPAuthenticationService.swift, then inspect the URL handoff in the named OnboardingFlow files. Run the existing loopback and authentication tests before adding coverage for wrong-state callbacks, unsafe verification URLs, and connection limits. Done means the real callback remains usable, only HTTPS verification URLs are opened, and the acceptance tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, swift
Domain
authentication, desktop, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.