S1 auth: one passkey prompt at sign-in (D108) #30

Closed
opened 2026-10-06 15:42:32 +00:00 by kayg · 10 comments
Owner

DECISIONS 108 (owner-approved, conditional): sign-in should be one passkey prompt. Today discoverable login is followed by a second local ceremony for the PRF unwrap, because the v2 PRF input is bound to the user id.

Plan: use a server-wide constant PRF input (prf.eval, no evalByCredential), so the login assertion also yields the PRF output. Existing envelopes keep their stored input (v1 or v2), so nothing is stranded.

Blocked by: the review-auth-ux answer (see the audit issue).

DECISIONS 108 (owner-approved, conditional): sign-in should be **one passkey prompt**. Today discoverable login is followed by a second local ceremony for the PRF unwrap, because the v2 PRF input is bound to the user id. **Plan:** use a server-wide constant PRF input (`prf.eval`, no evalByCredential), so the login assertion also yields the PRF output. Existing envelopes keep their stored input (v1 or v2), so nothing is stranded. **Blocked by:** the review-auth-ux answer (see the audit issue).
Author
Owner

Audit answer (item 4): a server-wide constant PRF input keeps per-credential separation, and the UUID-bound envelope AAD is unchanged. The only loss is input separation when the same credential is reused across accounts on one RP. Discoverable passkeys are bound to one user handle, so that does not arise here. The UUID-derived v2 input was never secret. Existing v2 envelopes would need rewrapping or a second-prompt fallback. The only v2 envelopes are on the monitor01 test deploy, which gets a fresh setup anyway.

So D108's condition is met. Implement the single prompt in the auth fix round, keeping v1 support and a second-prompt fallback for any stored v2 envelope.

Audit answer (item 4): a server-wide constant PRF input keeps per-credential separation, and the UUID-bound envelope AAD is unchanged. The only loss is input separation when the **same credential** is reused across accounts on one RP. Discoverable passkeys are bound to one user handle, so that does not arise here. The UUID-derived v2 input was never secret. Existing v2 envelopes would need rewrapping or a second-prompt fallback. The only v2 envelopes are on the monitor01 test deploy, which gets a fresh setup anyway. So D108's condition is met. Implement the single prompt in the auth fix round, keeping v1 support and a second-prompt fallback for any stored v2 envelope.
Author
Owner

Starting auth/server fix round: t-server base 6e5d9563f3; t-web base 7a5e5db. Read contracts, review findings and issue/parent history. Reproduce before fixing, separate commits per finding; no push or deploy. DECISIONS.md ends at 86 and docs/PROCESS.md is absent; following owner D108 approval and supplied process requirements from the issue/task.

Starting auth/server fix round: t-server base 6e5d9563f349f7e399d0b4e4618a20a2cfae3333; t-web base 7a5e5db. Read contracts, review findings and issue/parent history. Reproduce before fixing, separate commits per finding; no push or deploy. DECISIONS.md ends at 86 and docs/PROCESS.md is absent; following owner D108 approval and supplied process requirements from the issue/task.
Author
Owner

Recreated browser regression at current client: production first-run/sign-in test counts native credentials.get calls; expected 1, received 2. Implementing a fixed domain-separated SHA256(infraternal/keywrap/prf/v3) input for new enrollment and discoverable prf.eval. Stored v1/v2 inputs will use the second local prompt, without automatic rewrap; AAD stays infraternal/keywrap/v1|UUID|kind. No credential list or PRF plaintext is sent to server.

Recreated browser regression at current client: production first-run/sign-in test counts native credentials.get calls; expected 1, received 2. Implementing a fixed domain-separated SHA256(infraternal/keywrap/prf/v3) input for new enrollment and discoverable prf.eval. Stored v1/v2 inputs will use the second local prompt, without automatic rewrap; AAD stays infraternal/keywrap/v1|UUID|kind. No credential list or PRF plaintext is sent to server.
Author
Owner

Client constant-input reuse committed 35148ed (full client fast green; onboarding 4/4). Real production Chromium + Rust router/SQLite fixture proves v3 enrollment/login 1 native get, legacy v1 2 and legacy v2 2, each successful AES-GCM unwrap with unchanged AAD. Server module slice gate: fmt/clippy and 30 tests pass; no automatic rewrap chosen. Exact server commit SHAs recorded after commits.

Client constant-input reuse committed 35148ed (full client fast green; onboarding 4/4). Real production Chromium + Rust router/SQLite fixture proves v3 enrollment/login 1 native get, legacy v1 2 and legacy v2 2, each successful AES-GCM unwrap with unchanged AAD. Server module slice gate: fmt/clippy and 30 tests pass; no automatic rewrap chosen. Exact server commit SHAs recorded after commits.
Author
Owner

Native prompt evidence against production tracer build and real Rust auth router, SQLite, Chromium resident PRF and AES-GCM: v3 enrollment/discoverable login uses exactly 1 navigator.credentials.get; genuine v1 NULL-input and v2 UUID-input stored envelopes each use exactly 2 and unwrap the original DEK. No AAD change or automatic rewrap. Legacy fixture alters public setup input/metadata before enrollment, not ciphertext. check-tracer.mjs passes all three versions on Chromium 149.0.7827.55. Browser fixture also asserts new login count=1. Both fast gates pass; documentation records fallback choice.

Native prompt evidence against production tracer build and real Rust auth router, SQLite, Chromium resident PRF and AES-GCM: v3 enrollment/discoverable login uses exactly 1 navigator.credentials.get; genuine v1 NULL-input and v2 UUID-input stored envelopes each use exactly 2 and unwrap the original DEK. No AAD change or automatic rewrap. Legacy fixture alters public setup input/metadata before enrollment, not ciphertext. check-tracer.mjs passes all three versions on Chromium 149.0.7827.55. Browser fixture also asserts new login count=1. Both fast gates pass; documentation records fallback choice.
Author
Owner

Clean production-build browser rerun after this job stopped building: 4/4 pass in 46.0s. 500-key probe p99=16.40ms (<20ms); time to resume=77.00ms (<300ms). Native v3/v1/v2 prompt counts 1/2/2 and corrupted unwrap/logout-429 denial also pass. Production screenshots: t-web/apps/tracer/test-results/onboarding-setup-recovery--906cd--match-the-browser-contract/returning-sign-in-iphone-15.png; terminal-first-run-passkey-29742-esume-and-the-latency-probe/signin-dark.png and tracer-terminal.png. Screenshots are supplied for independent review, not self-approved. Evidence/self-review document server commit f1b996c; full fast finish run pending. #28 remains blocked on production host-daemon/PTY deduplication and the longer-drop writer contract; #29/#30 implementation and native evidence pass.

Clean production-build browser rerun after this job stopped building: 4/4 pass in 46.0s. 500-key probe p99=16.40ms (<20ms); time to resume=77.00ms (<300ms). Native v3/v1/v2 prompt counts 1/2/2 and corrupted unwrap/logout-429 denial also pass. Production screenshots: t-web/apps/tracer/test-results/onboarding-setup-recovery--906cd--match-the-browser-contract/returning-sign-in-iphone-15.png; terminal-first-run-passkey-29742-esume-and-the-latency-probe/signin-dark.png and tracer-terminal.png. Screenshots are supplied for independent review, not self-approved. Evidence/self-review document server commit f1b996c; full fast finish run pending. #28 remains blocked on production host-daemon/PTY deduplication and the longer-drop writer contract; #29/#30 implementation and native evidence pass.
Author
Owner

Implementation and requested native auth evidence complete for this issue; #28 integration blockers remain recorded separately.

Both lanes final fast exit 0; server head f1b996c11c, client head faa31544c6. Native auth 4/4; production paired v3/v1/v2 prompt counts 1/2/2; corrupt unwrap + logout 429 denies session and reload stays signed out. Onboarding/terminal Playwright 4/4; 500-key p99 16.40ms, resume 77ms. Auth 1024-pending p99 challenge 22us/finish 800us, Login inline 136×1024=139264 bytes excluding heap/hash tables; full-user writer lookup (512) p99 504us. Evidence/self-review: docs/verification/fix-round.md. Complete unedited gate output is posted on #28 in ordered parts for each lane. Nothing pushed/deployed; issues remain open.

Both gates' Rust summary, verbatim:

| fmt                       | PASS         |
| clippy                    | PASS         |
| test                      | PASS         |
| cargo-deny                | PASS         |
| static builds             | PASS         |

Both gates' protocol summary, verbatim:

 7 pass
 0 fail
 34390 expect() calls

Server tests, verbatim:

test result: ok. 42 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 31.76s

All three ignored release measurement tests were run separately and passed. Final live-socket release rerun (16 simultaneous × 200 = 3200) p50 1123us/p99 2190us, load 1.92/2.42/2.56; local WebSocket control + SQLite only, no PTY claim.

Implementation and requested native auth evidence complete for this issue; #28 integration blockers remain recorded separately. Both lanes final fast exit 0; server head f1b996c11cf4b25dcb08ba2e7f68ab671b88dbb2, client head faa31544c6a26f2aec743e526fb78c1499eaee28. Native auth 4/4; production paired v3/v1/v2 prompt counts 1/2/2; corrupt unwrap + logout 429 denies session and reload stays signed out. Onboarding/terminal Playwright 4/4; 500-key p99 16.40ms, resume 77ms. Auth 1024-pending p99 challenge 22us/finish 800us, Login inline 136×1024=139264 bytes excluding heap/hash tables; full-user writer lookup (512) p99 504us. Evidence/self-review: docs/verification/fix-round.md. Complete unedited gate output is posted on #28 in ordered parts for each lane. Nothing pushed/deployed; issues remain open. Both gates' Rust summary, verbatim: ```text | fmt | PASS | | clippy | PASS | | test | PASS | | cargo-deny | PASS | | static builds | PASS | ``` Both gates' protocol summary, verbatim: ```text 7 pass 0 fail 34390 expect() calls ``` Server tests, verbatim: ```text test result: ok. 42 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 31.76s ``` All three ignored release measurement tests were run separately and passed. Final live-socket release rerun (16 simultaneous × 200 = 3200) p50 1123us/p99 2190us, load 1.92/2.42/2.56; local WebSocket control + SQLite only, no PTY claim.
Author
Owner

Cleanup verified: both worktrees are clean at server f1b996c11c and client faa31544c6. Removed both temporary target symlinks and this job’s 18GB/4.7GB scratch build targets. Production screenshots remain in t-web/apps/tracer/test-results; complete gate output and release measurements are on the issues. No running auth fixture remains; no push, deployment or issue closure. #28 acceptance remains blocked on the previously recorded production-daemon/PTY and longer-drop writer contract boundaries.

Cleanup verified: both worktrees are clean at server f1b996c11cf4b25dcb08ba2e7f68ab671b88dbb2 and client faa31544c6a26f2aec743e526fb78c1499eaee28. Removed both temporary target symlinks and this job’s 18GB/4.7GB scratch build targets. Production screenshots remain in t-web/apps/tracer/test-results; complete gate output and release measurements are on the issues. No running auth fixture remains; no push, deployment or issue closure. #28 acceptance remains blocked on the previously recorded production-daemon/PTY and longer-drop writer contract boundaries.
Author
Owner

review-auth3 independently rebuilt production client and verified real Rust/native Chromium149 PRF/GCM: v3/v1/v2 login prompts1/2/2; each unwrap succeeds, exactly1 login proof is submitted and PRF extension absent. Existing input metadata and ciphertext are retained; AAD unchanged. D108 owner approval verified in issue history. Native baseline4 scenarios pass. Auth completion race and busy socket revocation remain rework findings on#29; full audit/gates pending.

review-auth3 independently rebuilt production client and verified real Rust/native Chromium149 PRF/GCM: v3/v1/v2 login prompts1/2/2; each unwrap succeeds, exactly1 login proof is submitted and PRF extension absent. Existing input metadata and ciphertext are retained; AAD unchanged. D108 owner approval verified in issue history. Native baseline4 scenarios pass. Auth completion race and busy socket revocation remain rework findings on#29; full audit/gates pending.
Author
Owner

Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: D108 single passkey prompt implemented (faa3154 in dev and prod) and independently verified by review-auth3. Reopen if anything is still missing.

Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: D108 single passkey prompt implemented (faa3154 in dev and prod) and independently verified by review-auth3. Reopen if anything is still missing.
kayg closed this issue 2026-10-28 17:52:21 +00:00
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#30
No description provided.