Closing the coverage gaps the review flagged as blind spots we'd be relying on at
release:
4. RELAY LOGIC TESTS. server/test_relay.py (stdlib unittest + a FakeSocket, no network)
covers the address-proof end to end: cookie issued on join, wrong cookie rejected,
right cookie verifies once; enforce mode WITHHOLDS forwarding from an unverified
address then delivers after it proves itself; watch-only forwards but records
would-block; the per-IP cap counts across BOTH v1 and v2; a NAT-rebind clears
verification (spoof-takeover guard); a forged BYE from another address can't evict
the victim; and bad/short/unknown-version headers are refused. Wired into
run-tests.ps1 (Start-Process from server\, SKIPs loudly if no Python) so a relay
change can no longer ship past the gate untested. The relay had ZERO automated
coverage before and auto-updates every user.
5. UPDATER SIGNATURE ENFORCEMENT. Extracted the two refusal branches into a pure
VerifyStagedRelease gate and added UpdaterRefusesUnsignedRelease: no-sig refused,
wrong-key refused, garbage refused, tamper (good sig over changed bytes) refused,
genuine release accepted. ReleaseSigning only proved the crypto; this proves the
updater actually REFUSES - the hijacked-release-stream threat.
6. STREAMING PASSWORD STRENGTHENING. The accept decision is now a pure
ProfilePasswordDialog.RejectionAdviceFor shared by BOTH password dialogs (also
fixes the App-review trim inconsistency - manager dialog compared untrimmed). Test
pins the load-bearing rule: requireStrong DISABLES the unchanged-exemption so an
existing weak "Games" can't keep streaming, while casual mode still grandfathers an
unchanged password and blocks a new weak one, trim-safe.
Plus the NVDA-hang cache assertions in PasswordRules (miss->hit, same-instance repeat,
Prewarm, empty/weak = no work).
Gate 71/71 + 7 relay tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The everyone-must-update release. Four coordinated changes, each from the security
discussion Ed approved 2026-07-27, plus the remembered-apps polish:
1. SIGNED RELEASES. build-release.ps1 now signs the release zip (ECDSA P-256 /
SHA-256, --sign-update verb) with a private key that lives ONLY at Ed's chosen
location outside the repo; the matching public key is embedded (UpdateSignature)
and the updater REFUSES any release whose .sig asset is missing or does not
verify - a compromised GitHub account can no longer ship code to users. The
signing verb self-checks against the embedded key so a key/embed mismatch fails
the pipeline, and the gate proves the on-disk key matches the embed when present.
2. STRONGER PASSWORDS, ENFORCED (BREAKING). PBKDF2 raised 100k -> 600k (both peers
must derive the same key, so 5.6 cannot stream with pre-5.6 AT ALL - release
notes lead with it). New PasswordStrength rule (>= 8 chars, not an infamous
password) enforced at EVERY door: both password dialogs block weak NEW entries
with concrete plain-English advice; the streaming gate walks an existing weak
password through strengthening; and ForPlainPassword - the single derivation
choke-point shared with the service - refuses weak outright, so no path streams
on a guessable password. Headless service logs the why. Per Ed: painful once,
and this coordinated-update release is the cheapest moment it will ever have.
3. RELAY ADDRESS-PROOF (watch-only). The relay sends every new client address a
random cookie and marks it verified when echoed - a forged source address can
never echo, killing the reflection attack. 5.6 clients echo automatically
(AddrCheck type 10, verbatim, self-limiting); the relay ships watch-only
(logs would-blocks) until the fleet updates, then one flag (--require-addr-check)
enforces. Per-IP entry cap (4) enforced immediately. Relay changes are committed
but NOT deployed to the Pi - they ride the v5.6 release moment.
4. Remembered-apps empty state teaches its lifecycle + manual sentence; About/
release notes written; version bumped to 5.6.
New gate steps: signing round-trip/tamper/wrong-key/embed-match; password rules incl.
the exact "Games" case; AddrCheck verbatim echo. Gate 69/69.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reworks the per-peer shaping tab (held for next release):
* Renamed the tab to "Volume, pan and EQ for peers"; the Preferences toggle now defaults ON.
* Collapsed the two master switches (Enable EQ / Enable pan) into ONE: "Enable volume, pan and
EQ for all peers" (Alt+E). Volume now obeys it too. PeerDspChain.Build takes a single enabled
flag; Profile.EnableAllPeerShaping replaces the two bools (old ones kept for load-migration).
* Peer picker is now a CheckedListBox: ticking a peer shapes them (per-peer bypass via new
PeerShaping.Enabled, default true); the focused row is the one the controls edit. Effective
shaping = master switch AND that peer's tick. Letter-nav suppressed so keys never toggle a tick.
* Three EQ modes, renamed: "3 band simple EQ", "12 band advanced graphic EQ", and the new
"16 band parametric EQ" (PeerEqMode.Parametric16Band).
* Parametric EQ: up to 16 user bands, each a boost/cut across a start->end range (PeerShaping
.ParametricBands; ParametricToPeaking maps range -> peaking centre+Q, shared by DSP and curve).
Add band dialog (spin-or-type, numeric-only, live preview, OK/Escape); Bands list sorted
bass->treble reading "X Hz to Y Hz, plus/minus N dB"; Delete key / Delete button, multi-select.
Set peer EQ to default clears the parametric list too.
* dB now spoken as words ("plus 3 dB" / "minus 6 dB" / "flat") on the graphic sliders and the
parametric list, since NVDA users typically have punctuation off and never hear a "+".
* New unbound machine-wide global shortcut "Toggle volume, pan and EQ for all peers" (not stored
in any profile) via the hotkey controller + settings store.
* Renamed the Inputs/outputs "Set volume for all received audio" to "Master receive volume".
* Added EqCurveControl: a purely-visual EQ response graph (not focusable, invisible to NVDA).
* Full manual sweep (readme.html + regenerated MANUAL.md).
Build clean; --selftest passes. Deployed to both test folders. Held for next release.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Receiver: a Mixed (plain) session is rendered on an active lane in BothIndependent mode instead of being skipped, so a WASAPI-only sender is no longer silent to a receiver that has an ASIO driver selected. Also port the per-session buffer depth-drain (stops the receive jitter buffer bloating).
Sender: add sndAudFr meter (audio frames actually sent) to localise capture vs send.
App: startup sound cue (machine-wide, Preferences); stop sending audio when no peer is reachable (issue #8). Version 3.9.1.
Server (relay): fix updater version-compare for multi-dot tags, guard the main loop against crashes, reject spoofed BYE from a mismatched endpoint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>