Skip to content

test+refactor: verify the SSH gate runs, harden dead-updater coverage (post-merge /review) - #53

Merged
kapoorankush merged 1 commit into
masterfrom
review/636-postmerge
Aug 12, 2026
Merged

test+refactor: verify the SSH gate runs, harden dead-updater coverage (post-merge /review)#53
kapoorankush merged 1 commit into
masterfrom
review/636-postmerge

Conversation

@kapoorankush

Copy link
Copy Markdown
Owner

Follow-up from running the FULL /review pipeline on the merged #636 port (#52) — the critical pass plus testing, maintainability, security, and performance specialists, Codex structured review, and adversarial passes. The batch had merged on two adversarial passes; the whole pipeline should have run first. It did now, and the code came back sound, but the specialists surfaced real gaps.

The headline: the SSH gate was never executed

The factory-reset SSH-off gate (disable_ssh_for_handoff) had only structural tests — string matches over the script text. A security gate that is only pattern-matched is unverified: a refactor could satisfy every grep and still not block. Added behavioral tests that extract the function and RUN it under bash with stubbed systemctl/raspi-config/ss:

  • port 22 still listening → the gate exits 1, poweroff never reached;
  • only :2222/:220 listening → proceeds (pins that grep -qx 22 is an exact match, not a substring);
  • nothing listening → proceeds.

And a fail-open fix the security specialist caught: the old ss -H -ltn 2>/dev/null | ... | grep conflated "ss ran, port closed" (empty output) with "ss itself errored" (also empty output), so an ss that failed at runtime read as "verified closed". It now captures ss's output and exit status separately and, on an ss error, warns and proceeds exactly like an absent ss — never a silent pass. A test drives an erroring ss and asserts the warning fires.

Dead-updater refactor + refinement

  • Both fireApply arms claim the run through one claimOptimisticRun() helper (was duplicated), with the deadUpdaterPolls reset invariant stated at the threshold declaration.
  • The reconnect-resume path resets the corpse counter unconditionally (a pre-disconnect count is stale evidence) but only enters the live reading list on unit_busy!==false — a corpse status that outlived the Phase-7 restart hands back to the poll loop instead of showing a live view for it.

Test gaps closed (each verified red without its guard)

  • JS: dialog-open promotion guard, verdict row-freeze, reconnect reset, fireApply catch-path reset.
  • Route: a stale status file carries no unit_busy key even when busy.
  • download_images: & and a bare . repo segment added to the slug hostile vectors.
  • wifi: SSID rejection pins U+2028/U+2029 and the C1 NEL.

Hygiene

The 7 bare #528 references (a dev-repo issue; public tops out at #46) are now litclock-dev#528, matching the convention every other dev ref uses here. 5 predated this work; my SSH refactor added 2.

Gates: ruff clean; pytest 3,263 with the freetype-gated tests executed locally (zero relevant skips); vitest 191. No fleet-reaching behavior change beyond the ss fail-open hardening and the reconnect-resume refinement, both correctness improvements to paths that only run during an update or a factory reset. Rides the v0.224.0 train.

…s (post-merge /review)

Follow-up from the full /review pipeline on the merged #636 port (#52).
The code was sound (Codex structured + security + performance + maintain-
ability all clean), but the specialists found real gaps:

- reset-setup: the SSH-off gate was only pattern-matched, never EXECUTED.
  A security gate you never run is unverified. Added behavioral tests that
  extract disable_ssh_for_handoff() and run it under bash with stubbed
  systemctl/raspi-config/ss: port 22 open -> exit 1 (no poweroff); only
  :2222/:220 -> proceeds (pins grep -qx 22); no ports -> proceeds. And a
  fail-open fix: Netid State  Recv-Q Send-Q                                          Local Address:Port         Peer Address:Port   Process
u_str ESTAB  0      0                                                           * 1137776                 * 1135318
u_dgr ESTAB  0      0                                                           * 1137016                 * 5169
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10524                   * 8661
u_dgr ESTAB  0      0                                /run/systemd/journal/dev-log 5167                    * 0
u_dgr ESTAB  0      0                                 /run/systemd/journal/socket 5169                    * 0
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1135278                 * 1136321
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 12309                   * 11902
u_dgr ESTAB  0      0                                                           * 1137024                 * 1137023
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1136439                 * 1136949
u_dgr ESTAB  0      0                                                           * 1137802                 * 1137801
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1136413                 * 1137796
u_dgr ESTAB  0      0                                                           * 1137022                 * 1137021
u_dgr ESTAB  0      0                                                           * 1136456                 * 5169
u_str ESTAB  0      0                                                           * 8633                    * 10471
u_dgr ESTAB  0      0                                                           * 8635                    * 5167
u_dgr ESTAB  0      0                                                           * 1137023                 * 1137024
u_dgr ESTAB  0      0                                                           * 1137937                 * 5169
u_dgr ESTAB  0      0                                                           * 1136445                 * 6231
u_dgr ESTAB  0      0                                                           * 1137800                 * 1137799
u_str ESTAB  0      0                                                           * 8797                    * 8796
u_str ESTAB  0      0                                                           * 1140315                 * 1140902
u_str ESTAB  0      0                                                           * 1137001                 * 1137873
u_str ESTAB  0      0                                                           * 11902                   * 12309
u_str ESTAB  0      0                                                           * 8661                    * 10524
u_str ESTAB  0      0                                                           * 1140328                 * 1143611
u_dgr ESTAB  0      0                                                           * 1137021                 * 1137022
u_dgr ESTAB  0      0                                                           * 1137790                 * 5169
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10471                   * 8633
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1135318                 * 1137776
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1140902                 * 1140315
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1137164                 * 1137932
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1137873                 * 1137001
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1137026                 * 1137025
u_dgr ESTAB  0      0                                                           * 1137801                 * 1137802
u_dgr ESTAB  0      0                                                           * 1136922                 * 5169
u_dgr ESTAB  0      0                                                           * 1137799                 * 1137800
u_str ESTAB  0      0                                                           * 8796                    * 8797
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1143611                 * 1140328
u_dgr ESTAB  0      0                                                           * 1137941                 * 1137942
u_str ESTAB  0      0                                                           * 1137223                 * 1137224
u_str ESTAB  0      0                                          /run/user/1000/bus 1136951                 * 1136950
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1137224                 * 1137223
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1140426                 * 1140425
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1141076                 * 1141896
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1141084                 * 1140439
u_dgr ESTAB  0      0                                                           * 1137942                 * 1137941
u_dgr ESTAB  0      0                                                           * 1140441                 * 5167
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10292                   * 7437
u_dgr ESTAB  0      0                                                           * 1140427                 * 5167
u_dgr ESTAB  0      0                                                           * 1138703                 * 5169
u_dgr ESTAB  0      0                                                           * 10337                   * 5169
u_str ESTAB  0      0                                                           * 1564014                 * 1564015
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1424373                 * 1420951
u_str ESTAB  0      0                                                           * 1420951                 * 1424373
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 10371                   * 10359
u_str ESTAB  0      0                                                           * 1141852                 * 1140348
u_str ESTAB  0      0                                                           * 1564015                 * 1564014
u_dgr ESTAB  0      0                                                           * 1426239                 * 5167
u_dgr ESTAB  0      0                                                           * 1141816                 * 5167
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1137242                 * 1138710
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 5078                    * 8457
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1140348                 * 1141852
u_str ESTAB  0      0                                                           * 7437                    * 10292
u_dgr ESTAB  0      0                                                           * 8564                    * 5167
u_str ESTAB  0      0                                                           * 1140439                 * 1141084
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1141004                 * 1141866
u_str ESTAB  0      0                                                           * 1141866                 * 1141004
u_str ESTAB  0      0                                                           * 1140425                 * 1140426
u_str ESTAB  0      0                                                           * 1143599                 * 1143600
u_str ESTAB  0      0                                                           * 8457                    * 5078
u_str ESTAB  0      0                                                           * 1143600                 * 1143599
u_str ESTAB  0      0                                                           * 1141896                 * 1141076
u_dgr ESTAB  0      0                                                           * 1140452                 * 5167
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10599                   * 10598
u_str ESTAB  0      0                                                           * 10384                   * 10385
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10385                   * 10384
u_dgr ESTAB  0      0                                                           * 10402                   * 5167
u_dgr ESTAB  0      0                                                           * 10609                   * 5167
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1141095                 * 1143681
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 7487                    * 10403
u_str ESTAB  0      0                                                           * 10403                   * 7487
u_str ESTAB  0      0                                                           * 10598                   * 10599
u_str ESTAB  0      0                                                           * 1143681                 * 1141095
u_dgr ESTAB  0      0                                                           * 14482                   * 5167
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1143652                 * 1141003
u_dgr ESTAB  0      0                                                           * 6232                    * 6233
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 8794                    * 7954
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1141024                 * 1141023
u_str ESTAB  0      0                                                           * 1141003                 * 1143652
u_str ESTAB  0      0                                                           * 7954                    * 8794
u_str ESTAB  0      0                                                           * 10280                   * 10279
u_str ESTAB  0      0                                                           * 1140963                 * 1140343
u_str ESTAB  0      0                                                           * 10279                   * 10280
u_str ESTAB  0      0                                                           * 1137932                 * 1137164
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 14469                   * 11193
u_str ESTAB  0      0                                                           * 9380                    * 10328
u_str ESTAB  0      0                                                           * 14484                   * 14483
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 1140343                 * 1140963
u_str ESTAB  0      0                                                           * 1140924                 * 1141818
u_str ESTAB  0      0                                                           * 14483                   * 14484
u_str ESTAB  0      0                                 /run/dbus/system_bus_socket 1141818                 * 1140924
u_str ESTAB  0      0                                                           * 11193                   * 14469
u_dgr ESTAB  0      0                                                           * 7930                    * 7931
u_str ESTAB  0      0                                 /run/systemd/journal/stdout 10328                   * 9380
u_dgr ESTAB  0      0                                                           * 7931                    * 7930
u_dgr ESTAB  0      0                                                           * 10278                   * 5167
u_str ESTAB  0      0                                                           * 1141023                 * 1141024
u_dgr ESTAB  0      0                                         /run/systemd/notify 6231                    * 0
u_dgr ESTAB  0      0                                                           * 6233                    * 6232
u_str ESTAB  0      0                  @b73e375f172f205b/bus/systemd/bus-api-user 1136950                 * 1136951
u_str ESTAB  0      0                @531ac269544b3aac/bus/systemd/bus-api-system 1136321                 * 1135278
u_str ESTAB  0      0       @18dc0229ee6613a4/bus/systemd-network/bus-api-network 1137796                 * 1136413
u_str ESTAB  0      0       @328d5fbd5f548868/bus/systemd-resolve/bus-api-resolve 1138710                 * 1137242
u_str ESTAB  0      0      @ace6c9d74fd0a3dd/bus/systemd-timesyn/bus-api-timesync 1137025                 * 1137026
u_str ESTAB  0      0                    @5e8f494281e13045/bus/systemd/bus-system 1136949                 * 1136439
u_str ESTAB  0      0                 @63a321078db32580/bus/systemd-logind/system 10359                   * 10371
???   UNCONN 0      0                                                     0.0.0.0:ipproto-255       0.0.0.0:*
icmp6 UNCONN 0      0                                                     *%ens18:ipv6-icmp               *:*
tcp   ESTAB  0      0                                               192.168.3.100:58984       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:39044       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:44338       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:47722       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:35902       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:47884       34.149.66.165:https
tcp   ESTAB  0      0                                               192.168.3.100:44494       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:53982       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:55882       160.79.104.10:https
tcp   ESTAB  0      0                                               192.168.3.100:33990       160.79.104.10:https          that ERRORS (nonzero, empty output) was read as
  'verified closed' by the old pipe-to-grep — it now captures ss's rc
  separately and warns+proceeds like an absent ss, never a silent pass.
- updates.js: both fireApply arms claim the run through one
  claimOptimisticRun() helper (was duplicated), with the deadUpdaterPolls
  reset invariant stated at the threshold declaration. The reconnect-resume
  path resets the corpse counter unconditionally but only enters the live
  reading list on unit_busy!==false (a corpse that outlived the Phase-7
  restart hands back to the poll loop instead of showing a live view).
- JS tests: the dialog-open promotion guard, verdict row-freeze, reconnect
  reset, and fireApply catch-path reset were untested — each now covered
  and verified to fail when its guard line is removed.
- route test: a stale status file carries no unit_busy key even when busy.
- download_images test: '&' + bare '.' repo segment added to the slug
  hostile vectors.
- wifi test: SSID rejection pins U+2028/U+2029 + C1 NEL.
- ref hygiene: the 7 bare #528 references (a dev issue) are now
  litclock-dev#528, matching the public convention.

No behavior change reaches the fleet beyond the ss fail-open hardening and
the reconnect-resume refinement; both are correctness improvements to
paths that only run during an update or a factory reset.
@kapoorankush
kapoorankush merged commit 90d9768 into master Aug 12, 2026
5 checks passed
@kapoorankush
kapoorankush deleted the review/636-postmerge branch August 12, 2026 21:27
kapoorankush added a commit that referenced this pull request Aug 16, 2026
…fixes (#56)

Ports the two pre-RC blockers fixed upstream in litclock-dev#668 and #669.
Both were found by review of the port PRs that brought #620 and #645 here, so
this closes the loop on that train.

litclock-dev#660 — the PWA "Factory reset" (litclock-reset.service runs
`--wipe-wifi --strict-env-wipe --poweroff --yes`) did not rotate the setup-WiFi
key that #620 had just made permanent. Because that path wipes WiFi, the next
power-on raises the setup hotspot broadcasting LitClock-Setup with the PREVIOUS
owner's key, permanently, surviving every reset the new owner later performs —
while the card promises "Erases everything". v0.223.0 had no such leak, so this
was a regression #620 introduced. The discriminator is `--wipe-wifi` AND the
power-off, not either alone: a hand-run `--poweroff` never raises a hotspot, and
`--wipe-wifi --reboot` is #620's same-owner WiFi reset, which must preserve.

litclock-dev#667 — #645's link-local blanking removed the address from the
same-IP short-circuit's coverage, so every dhcp4-change on a clock that cannot
get a lease fell through to a full e-ink render (3 renders measured for 3
identical events). Review found the same hole for a genuinely empty address,
which NM's defaults make at least as likely. Coalescing now keys on the observed
address in its own tmpfs marker, while the blanked value stays the only thing
diagnostics sees.

Port notes:

- `scripts/reset-setup.sh` HAND-MERGED. This repo has the litclock-dev#528/#636
  SSH-off gate, which does not exist upstream at all — it was authored here in
  #52/#53 — so upstream's `--poweroff` arm has nothing to order against. The
  rotation call is placed BEFORE `disable_ssh_for_handoff`, matching the gift
  arm and for the same reason: the rotation fails closed and can exit 1, leaving
  the device with its current owner, who then still needs SSH to diagnose a
  read-only card. Verified after merging: the gate body is byte-identical to
  master and all four call sites survive.
- `CLAUDE.md` deliberately NOT ported. The upstream half rewrites a #620 QA
  block this repo does not have (`grep -c 620 CLAUDE.md` → 0), so applying it
  would invent that block here. Same phantom-conflict trap as the #641/#648 half
  of the previous train.
- `tests/test_reset_setup_sh.py` hand-merged per hunk rather than wholesale:
  the conflict replaced the old `_extract_block` helper with the new harness,
  but a whole-file resolution would have discarded this repo's SSH-gate tests.
  All twelve survive.
- 4 bare dev#NNN references requalified.
- Verified nothing was dropped: of every line the two upstream commits added,
  none is missing here.

`test_gift_mode_rotates_hotspot_password_before_disabling_ssh` — added when the
two features first met in #54 — is now parametrised over BOTH handoff arms,
since #660 gave the `--poweroff` arm the same pairing and nothing covered it.
Its anchor is also fixed: it matched the function name in the COMMENT above the
call, so inverting the order in the new arm kept it green. Mutation-verified in
both arms after the fix.

Testing:
- ruff + shellcheck clean
- pytest: 3392 passed, 62 skipped
- npm run test:js: 191 passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant