Add-host: a brand-new host's key is a step, not a failure #397

Open
opened 2026-11-06 12:29:39 +00:00 by kayg · 3 comments
Owner

Owner, on production: a host-key "failure" "appears on every add-host though? even something completely new?"

Today: an unknown host key is reported as a failure (FailureKind::HostKey, "Confirm this host key.", crates/plugins/ssh/src/model.rs), and the server logs WARN host bootstrap failed … failure_kind="host-key". The wizard shows a failure screen with "Check the key" before the key page, so every brand-new host first lands on an error screen.

Plan (J306, branch addhost-key-step from origin/dev 7b14fbb66):

  1. Browser spec first: a never-seen host reaches the key step with no failure screen, icon/tone not error; changed-key spec still warns.
  2. Wizard: new host (no pinned key) goes straight to a normal step "Check 's key" with fingerprint, compare command, Trust and continue / Cancel; part of progress; never trusted without the click. Changed key stays a warning (host_key_changed).
  3. Server: log a new-key challenge at INFO as an awaited confirmation, not WARN failure; keep API contract compatible.
  4. Screenshots from a production build at 1440x900, 393x852, 375x667, ink and paper.

Estimate: about 3 hours.

Owner, on production: a host-key "failure" "appears on every add-host though? even something completely new?" **Today:** an unknown host key is reported as a failure (`FailureKind::HostKey`, "Confirm this host key.", `crates/plugins/ssh/src/model.rs`), and the server logs `WARN host bootstrap failed … failure_kind="host-key"`. The wizard shows a failure screen with "Check the key" before the key page, so every brand-new host first lands on an error screen. **Plan (J306, branch `addhost-key-step` from origin/dev 7b14fbb66):** 1. Browser spec first: a never-seen host reaches the key step with no failure screen, icon/tone not error; changed-key spec still warns. 2. Wizard: new host (no pinned key) goes straight to a normal step "Check <host>'s key" with fingerprint, compare command, Trust and continue / Cancel; part of progress; never trusted without the click. Changed key stays a warning (`host_key_changed`). 3. Server: log a new-key challenge at INFO as an awaited confirmation, not WARN failure; keep API contract compatible. 4. Screenshots from a production build at 1440x900, 393x852, 375x667, ink and paper. Estimate: about 3 hours.
Author
Owner

J306 finish. Branch addhost-key-step, head 932971342bb6b55805cecdc956f7721243ffd416 (base origin/dev 7b14fbb66). Not merged.

Changes: server logs an unpinned key at INFO (host bootstrap awaiting host key confirmation), a changed pin stays WARN; wizard awaitingKey() makes a new key an ordinary step: a lone host goes straight to "Check 's key" (neutral fingerprint icon, no red), Trust and continue / Cancel, stage is running with "Waiting for you to check the key", never a failed row. Changed key: warning icon, danger tone, existing wording, Replace trusted key. Wire contract unchanged.

Evidence (compute01, ~/j306-evidence/):

  • Browser specs (production build, port 4706), pw2.log: 3 passed: existing unknown-key spec, new "never-seen host goes straight to the key step" (a MutationObserver proves no failure page, failed row or danger hero ever appears), new "changed host key stays a real warning".
  • rust.log: new_host_key_is_info_and_changed_key_is_a_warning ok, 5 passed.
  • ts.log: tsc clean, bun test src/canvas 494 pass 0 fail.
  • quick.log: scripts/verify quick FAILS at the fmt row on files this branch does not touch (bootstrap_remote.rs, bootstrap_retirement.rs, tests/drop_cleaned_stale_tests.rs, tests/pending_removal_tests.rs, plugins/remote/src/uninstall.rs): pre-existing on origin/dev; my bootstrap_runtime.rs is not in the diff list. verify fast not run because it stops at the same row; the orchestrator should rerun once dev is fmt-clean.
  • Screenshots (12, production build, ink and paper, 1440x900, 393x852, 375x667) attached below, all looked at. Note: at 375x667 the card body scrolls, so the fingerprint runs under the sticky buttons until scrolled (existing card behaviour, not changed here).
  • Not done: Rust integration against a real sshd and the test-server run (needs a deploy; ask the orchestrator); I did not run the new specs against old code to show red.
**J306 finish.** Branch `addhost-key-step`, head `932971342bb6b55805cecdc956f7721243ffd416` (base origin/dev 7b14fbb66). Not merged. **Changes**: server logs an unpinned key at INFO (`host bootstrap awaiting host key confirmation`), a changed pin stays WARN; wizard `awaitingKey()` makes a new key an ordinary step: a lone host goes straight to "Check <host>'s key" (neutral fingerprint icon, no red), Trust and continue / Cancel, stage is running with "Waiting for you to check the key", never a failed row. Changed key: warning icon, danger tone, existing wording, Replace trusted key. Wire contract unchanged. **Evidence** (compute01, ~/j306-evidence/): - Browser specs (production build, port 4706), `pw2.log`: 3 passed: existing unknown-key spec, new "never-seen host goes straight to the key step" (a MutationObserver proves no failure page, failed row or danger hero ever appears), new "changed host key stays a real warning". - `rust.log`: `new_host_key_is_info_and_changed_key_is_a_warning` ok, 5 passed. - `ts.log`: tsc clean, `bun test src/canvas` 494 pass 0 fail. - `quick.log`: `scripts/verify quick` FAILS at the fmt row on files this branch does not touch (bootstrap_remote.rs, bootstrap_retirement.rs, tests/drop_cleaned_stale_tests.rs, tests/pending_removal_tests.rs, plugins/remote/src/uninstall.rs): pre-existing on origin/dev; my bootstrap_runtime.rs is not in the diff list. `verify fast` not run because it stops at the same row; the orchestrator should rerun once dev is fmt-clean. - Screenshots (12, production build, ink and paper, 1440x900, 393x852, 375x667) attached below, all looked at. Note: at 375x667 the card body scrolls, so the fingerprint runs under the sticky buttons until scrolled (existing card behaviour, not changed here). - Not done: Rust integration against a real sshd and the test-server run (needs a deploy; ask the orchestrator); I did not run the new specs against old code to show red.
Author
Owner

J309 progress (not the verdict yet)

  • Merged origin/dev (48358a5fa, newer than 68ee8d9a5) into addhost-key-step in a local review worktree. cargo fmt --check is clean (~claude/j309-evidence/fmt.log: FMT_EXIT=0).
  • Red-before:
    • Unmodified J306 specs on origin/dev: all 3 fail. Two fail on the new labels and tone (red-dev-wizard.log, red-dev-wizard-rerun.log).
    • The "no failure" spec only fails on a button label there. So I ran a copy with the labels removed (red-probe-dev-wizard.log). On the old wizard a never-seen host shows a failed row on the progress page (46 observed), which is the owner's complaint.
    • On the branch, all 3 pass (green-branch-merged.log: 3 passed).
  • Real-sshd run: still working through harness setup (the throwaway server needs the host-connection unlock before add-host). Result to follow.
  • Screenshots reviewed: J306's 12 plus my own batch captures. Details in the verdict.
**J309 progress (not the verdict yet)** - Merged origin/dev (48358a5fa, newer than 68ee8d9a5) into `addhost-key-step` in a local review worktree. `cargo fmt --check` is clean (`~claude/j309-evidence/fmt.log`: `FMT_EXIT=0`). - Red-before: - Unmodified J306 specs on origin/dev: all 3 fail. Two fail on the new labels and tone (`red-dev-wizard.log`, `red-dev-wizard-rerun.log`). - The "no failure" spec only fails on a button label there. So I ran a copy with the labels removed (`red-probe-dev-wizard.log`). On the old wizard a never-seen host shows a **failed row** on the progress page (46 observed), which is the owner's complaint. - On the branch, all 3 pass (`green-branch-merged.log`: 3 passed). - Real-sshd run: still working through harness setup (the throwaway server needs the host-connection unlock before add-host). Result to follow. - Screenshots reviewed: J306's 12 plus my own batch captures. Details in the verdict.
Author
Owner

J309 review of addhost-key-step @ 932971342: APPROVE (three non-blocking follow-ups below; the doc-comment one is a one-line fix I'd take before merge)

Evidence is in ~claude/j309-evidence/ on compute01, named per item. Nothing was pushed or merged. The merge below is local to my review worktree.

1. Merge with dev, fmt

Merged origin/dev (48358a5fa, newer than 68ee8d9a5) into the branch: clean merge. cargo fmt --check under pipefail gave FMT_EXIT=0 (fmt.log). cargo build --package infraternal on the merge: BUILD_EXIT=0 (build.log).

2. Red before, green after

  • Old wizard (origin/dev + J306's specs): all 3 fail (red-dev-wizard.log). Two of those fail only on the new labels and tone, so on their own they prove little.
    • The key "no failure" spec failed once in open(), so that run is not valid red (red-dev-wizard.log).
    • Rerun alone, it fails on the "Trust and continue" label (red-dev-wizard-rerun.log). Again that is a rename, not the claim.
    • So I ran a copy with the new labels removed (red-probe-dev-wizard.log). On the old wizard a never-seen host puts a failed row on the progress page, observed 46 times by the MutationObserver. This is the owner's complaint, and the old fingerprint icon was already neutral.
  • Branch + merged dev: 3 passed, EXIT=0 (green-branch-merged.log).

3. Real sshd, throwaway branch server

Throwaway infraternal server built from the merge (debug), own state dir and virtual passkey (the shared test-admin passkey was not touched). A rootless sshd on 127.0.0.1 with a key it had never seen and no authorised keys, so nothing could be installed. Artifacts: real-run3.log, shots/real-before-click.json, shots/real-after-click.json, shots/real-sshd-full.log.

  • Before the click:
    • The wizard is on "Check 127.0.0.1's key": fingerprint, Cancel, Trust and continue.
    • A page-wide MutationObserver recorded no failure page, no failed row, no danger hero (saw: []).
    • Server log: INFO … host bootstrap awaiting host key confirmation … step="connect". No WARN.
    • sshd saw only pre-auth probes (strict-check refusal and key scans, all [preauth]). No authentication attempt.
  • After the click:
    • A new connection reaches sshd (Connection closed by authenticating user claude … [preauth]).
    • The one WARN is failure_kind="auth", a real sign-in refusal because I authorised nothing. It is not host-key. The wizard then shows "Sign in to 127.0.0.1" with the method choices.
    • The "failed row" observations after the click come from that auth failure, not from the key step.
  • Test passed (1 passed, real-run3.log).

4. Screenshots (J306's 12 plus mine)

J306's 12: ~claude/.cache/codex-scratch/tracer-1130126/evidence/addhost-wizard/hostkey*-{1440x900,393x852,375x667}-{ink,paper}.png. My re-captures of the same states: tracer-1718750/…, plus shots/batch-*.png and the real-server shots.

  • 1440×900 and 393×852, ink and paper: correct. Neutral fingerprint icon on a new key, warning triangle in danger tone plus "Warning: this host's trusted key changed…" on a changed key. "Trust and continue" / "Cancel" against "Replace trusted key" / "Back". Text edges align, nothing wraps badly.
  • 375×667, the fingerprint under the sticky footer: this is not a collision under "Text never collides". The footer has its own border and the content scrolls under it; nothing overlaps. It does not need fixing now: the layout is the same as before J306 (only labels changed), and the new-key page still shows the heading, the fingerprint's first lines and a clipped last line that signals scrolling. The part that bothers me is the changed-key page at 375×667: the fingerprint is almost entirely below the fold, above a "Replace trusted key" button. Follow-up below.

5. Security

  • Nothing is trusted without the click. Pinning only happens in TrustedOpenSsh::confirm: it needs the challenge id, a matching fingerprint, the same previous pin, no external trust, a live confirmation authority and a 120 s window. J306 did not touch any of it. The real run above shows no authenticated connection before the click.
  • A changed key cannot take the neutral path.
    • The neutral UI needs kind == host-key, host_key_changed !== true and fingerprints present (awaitingKey).
    • The server sets host_key_changed = observation.previous.is_some() and serialises it whenever it is true (model.rs, trust.rs).
    • A key that conflicts with trust the person configured elsewhere gets no challenge and no fingerprints (observe returns None), so it fails awaitingKey and stays the old failure.
    • The only INFO downgrade is HostKey && !host_key_changed, so a changed pin still logs WARN (unit test new_host_key_is_info_and_changed_key_is_a_warning, seen passing in J306's rust.log).

Follow-ups, none blocking

  1. Doc comment (D345), trivial: in provisioning-client.ts the new awaitingKey doc block was inserted between awaitingFresh's doc comment and awaitingFresh itself. awaitingFresh now has no doc comment and awaitingKey has two. Move the "True while the attempt is paused on earlier-setup leftovers…" line back above awaitingFresh.
  2. Batch row spinner: with two hosts, the new-key row says "Waiting for you to check the key" but still shows the busy spinner ring (shots/batch-393x852.png). Nothing is working there; a neutral or waiting mark would fit "The icon matches the news" better.
  3. 375×667 changed-key page: the fingerprint sits mostly under the fold at the point of a security decision. Suggest tightening the hero/title block on short screens. Pre-existing, so a separate TASTE item.

Not done

I did not run verify fast or verify quick (reviewer rule). I read J306's Rust and TypeScript gate logs (rust.log: 5 passed; ts.log: 494 pass, 0 fail). The quick.log fmt failure there was the dev drift you described, which the merge cleared. Cleaned up: the review worktree's target dir, /tmp/j309s and my red worktree. No servers or sshd of mine are left running.

## J309 review of `addhost-key-step` @ 932971342: **APPROVE** (three non-blocking follow-ups below; the doc-comment one is a one-line fix I'd take before merge) Evidence is in `~claude/j309-evidence/` on compute01, named per item. Nothing was pushed or merged. The merge below is local to my review worktree. ### 1. Merge with dev, fmt Merged origin/dev (48358a5fa, newer than 68ee8d9a5) into the branch: clean merge. `cargo fmt --check` under `pipefail` gave `FMT_EXIT=0` (`fmt.log`). `cargo build --package infraternal` on the merge: `BUILD_EXIT=0` (`build.log`). ### 2. Red before, green after - **Old wizard (origin/dev + J306's specs):** all 3 fail (`red-dev-wizard.log`). Two of those fail only on the new labels and tone, so on their own they prove little. - The key "no failure" spec failed once in `open()`, so that run is not valid red (`red-dev-wizard.log`). - Rerun alone, it fails on the "Trust and continue" label (`red-dev-wizard-rerun.log`). Again that is a rename, not the claim. - So I ran a copy with the new labels removed (`red-probe-dev-wizard.log`). On the old wizard a never-seen host puts a **failed row** on the progress page, observed 46 times by the MutationObserver. This is the owner's complaint, and the old fingerprint icon was already neutral. - **Branch + merged dev:** `3 passed`, `EXIT=0` (`green-branch-merged.log`). ### 3. Real sshd, throwaway branch server Throwaway `infraternal server` built from the merge (debug), own state dir and virtual passkey (the shared test-admin passkey was not touched). A rootless sshd on 127.0.0.1 with a key it had never seen and no authorised keys, so nothing could be installed. Artifacts: `real-run3.log`, `shots/real-before-click.json`, `shots/real-after-click.json`, `shots/real-sshd-full.log`. - Before the click: - The wizard is on "Check 127.0.0.1's key": fingerprint, `Cancel`, `Trust and continue`. - A page-wide MutationObserver recorded **no failure page, no failed row, no danger hero** (`saw: []`). - Server log: `INFO … host bootstrap awaiting host key confirmation … step="connect"`. No WARN. - sshd saw only pre-auth probes (strict-check refusal and key scans, all `[preauth]`). No authentication attempt. - After the click: - A new connection reaches sshd (`Connection closed by authenticating user claude … [preauth]`). - The one WARN is `failure_kind="auth"`, a real sign-in refusal because I authorised nothing. It is not host-key. The wizard then shows "Sign in to 127.0.0.1" with the method choices. - The "failed row" observations after the click come from that auth failure, not from the key step. - Test passed (`1 passed`, `real-run3.log`). ### 4. Screenshots (J306's 12 plus mine) J306's 12: `~claude/.cache/codex-scratch/tracer-1130126/evidence/addhost-wizard/hostkey*-{1440x900,393x852,375x667}-{ink,paper}.png`. My re-captures of the same states: `tracer-1718750/…`, plus `shots/batch-*.png` and the real-server shots. - **1440×900 and 393×852, ink and paper:** correct. Neutral fingerprint icon on a new key, warning triangle in danger tone plus "Warning: this host's trusted key changed…" on a changed key. "Trust and continue" / "Cancel" against "Replace trusted key" / "Back". Text edges align, nothing wraps badly. - **375×667, the fingerprint under the sticky footer:** this is **not a collision** under "Text never collides". The footer has its own border and the content scrolls under it; nothing overlaps. It **does not need fixing now**: the layout is the same as before J306 (only labels changed), and the new-key page still shows the heading, the fingerprint's first lines and a clipped last line that signals scrolling. The part that bothers me is the **changed-key page at 375×667**: the fingerprint is almost entirely below the fold, above a "Replace trusted key" button. Follow-up below. ### 5. Security - Nothing is trusted without the click. Pinning only happens in `TrustedOpenSsh::confirm`: it needs the challenge id, a matching fingerprint, the same previous pin, no external trust, a live confirmation authority and a 120 s window. J306 did not touch any of it. The real run above shows no authenticated connection before the click. - A changed key cannot take the neutral path. - The neutral UI needs `kind == host-key`, `host_key_changed !== true` and fingerprints present (`awaitingKey`). - The server sets `host_key_changed = observation.previous.is_some()` and serialises it whenever it is true (`model.rs`, `trust.rs`). - A key that conflicts with trust the person configured elsewhere gets no challenge and no fingerprints (`observe` returns `None`), so it fails `awaitingKey` and stays the old failure. - The only INFO downgrade is `HostKey && !host_key_changed`, so a changed pin still logs WARN (unit test `new_host_key_is_info_and_changed_key_is_a_warning`, seen passing in J306's `rust.log`). ### Follow-ups, none blocking 1. **Doc comment (D345), trivial:** in `provisioning-client.ts` the new `awaitingKey` doc block was inserted between `awaitingFresh`'s doc comment and `awaitingFresh` itself. `awaitingFresh` now has no doc comment and `awaitingKey` has two. Move the "True while the attempt is paused on earlier-setup leftovers…" line back above `awaitingFresh`. 2. **Batch row spinner:** with two hosts, the new-key row says "Waiting for you to check the key" but still shows the busy spinner ring (`shots/batch-393x852.png`). Nothing is working there; a neutral or waiting mark would fit "The icon matches the news" better. 3. **375×667 changed-key page:** the fingerprint sits mostly under the fold at the point of a security decision. Suggest tightening the hero/title block on short screens. Pre-existing, so a separate TASTE item. ### Not done I did not run `verify fast` or `verify quick` (reviewer rule). I read J306's Rust and TypeScript gate logs (`rust.log`: 5 passed; `ts.log`: 494 pass, 0 fail). The `quick.log` fmt failure there was the dev drift you described, which the merge cleared. Cleaned up: the review worktree's target dir, `/tmp/j309s` and my red worktree. No servers or sshd of mine are left running.
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#397
No description provided.