Approve the AWS sign-in without showing a window - #85
Merged
Conversation
AWS SSO's device authorization is a multi-step approval — confirm the request, sign in, grant access — and none of the approval steps ask for anything the user has to supply. Frost now clicks them itself: the login page loads in a window that stays off screen, and a refresh covered by a live identity provider session finishes without a window, a browser tab or a Dock icon appearing at all. The window comes up the moment the page needs the user — a password or a one-time code to type, a security key to touch, a passkey to pick — and on anything off the expected path: a page Frost cannot read, a load that fails, a flow that stops making progress. In default-browser mode that same moment opens the browser instead, which is where that user's passkeys and saved passwords live, and the hidden probe is destroyed once the browser is actually up. Clicking is deliberately narrow, because the buttons next to the ones we want deny the request: only on the device-authorization hosts, only controls matched by AWS's own ids or by an exact label, never one whose label reads like a refusal, and only a few per document. Anything unrecognised stalls, and stalling shows the window. - src/approve-overlay.ts drives the page, alongside the existing overlay and under the same compile; src/auto-approve.ts watches it and owns the timers that guarantee the login is always eventually handed over. - src/page-script.ts holds the loading and sub-frame injection both injected scripts need — the overlay's dom-ready fallback for frames the document-start hook cannot reach, and the approval driver. - attachLoginIndicator() takes an onUserNeeded callback: a WebAuthn wait, and especially the modal account picker, must not happen behind a window nobody can see. - Behavior → Approve automatically turns it off, on by default. Closes #1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XK1mi3rSPLzxu7XUhyC6a
popen2
force-pushed
the
claude/frost-auto-approve-aws-o8yleo
branch
from
August 31, 2026 06:17
eec4abf to
20f9274
Compare
popen2
enabled auto-merge
August 31, 2026 06:22
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.
Closes #1. Rebased onto
mainat c378f9b (picks up #82, #84, #86, #87, #88).AWS SSO's device authorization is a multi-step approval — confirm the request, sign in, grant access — and none of the approval steps ask for anything the user has to supply. Frost now clicks them itself: the login page loads in a window that stays off screen, and a refresh covered by a live identity provider session finishes with nothing appearing at all — no window, no browser tab, no Dock icon.
When the window does come up
The moment the page needs the user, and never later than that:
verificationUriComplete, which is exactly the distinction that makes this work;onUserNeededcallback onattachLoginIndicator(). The picker is a modal sheet, so it has to ask for the window before it opens — a sheet on a window nobody can see is a prompt nobody can answer;In default browser mode that same moment hands over to
shell.openExternaland drops the silent probe — that user picked the browser because that is where their passkeys and saved passwords live. The probe is destroyed only once the browser is actually up; destroying it first and then failing to open anything would leave the run polling with nothing on screen.Clicking is deliberately narrow
The buttons next to the ones we want deny the request, so guessing wrong is worse than stalling — and stalling shows the window, which is the safe failure. The driver only clicks:
*.awsapps.com,device.sso.<region>.amazonaws.com), never on the identity provider's pages;cli_verification_btn,cli_login_button) or by an exact label ("Confirm and continue", "Allow access");Scanning continues after the user takes over, so the approval steps that follow their sign-in are still clicked for them.
Changes
src/approve-overlay.ts— the driver, browser-world code alongside the existing overlay and under the sametsconfig.overlay.jsoncompile.src/auto-approve.ts— the main-process half: injects the driver, reads its console signals, and owns the stall/total timers that guarantee the login is always eventually handed to the user.src/page-script.ts— the script loading and sub-frame injection both injected scripts need. For the WebAuthn overlay this is thedom-readyfallback for frames the document-start hook cannot reach (Show the credential overlay when the page asks for the key as it loads #87), lifted out oflogin-indicator.tsunchanged; the approval driver only ever needed that half.attachLoginIndicator()takes an optionalonUserNeededcallback.How it sits with the recent login work
tryas Handle failed logins gracefully without automatic retries #84's, so a throw never strands it, and it is torn down withdestroy()on every path.attachLoginIndicator()is awaited beforeloadURLand the destroyed-window check after it is unchanged — the driver is attached after that check, so it never scans a window that has already gone.LoginAbortedErrorstill means what Stop a failed login from reopening the login page on a loop #82 and Handle failed logins gracefully without automatic retries #84 want it to mean: a human saw a login page and did not finish it, so do not reopen one on a timer.Verification
npm run buildandnpm run lint(oxlint) pass.npm run check:overlaypasses — #87's regression test, run here under xvfb. It exercises the injection path this PR refactored, and reports the wait for both the early (document-start) and late (dom-ready, now viapage-script.ts) cases.For the driver itself, the built
dist/approve-overlay.jsis run throughnew Function("window", source)against a stubwindow, once per page shape: confirm page (clicks confirm, ignores cancel, does not mistake the prefilled code for a prompt), allow page, sign-in page (no clicks off an AWS host, asks for the user), an empty OTP field, the approved page, acli_login_buttonrelabelled "Cancel request" (not clicked), an unrecognised button (left alone, says nothing), disabled and hidden controls, and click-once. 16 checks, all passing; the harness is described in AGENTS.md.Still needs a desktop smoke test against the real AWS pages — the id and label matching, and how a hidden window behaves through a real federated redirect, are the parts that cannot be proven headlessly. A real-Electron
check:auto-approvein the style ofcheck:overlaywould be the way to close that gap, and is a natural follow-up.