You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The README now shows the face-free terminal demo (docs/demo.gif) as its hero.
Fixed the <you> placeholder in the package URLs (PKGBUILD, ward.spec, build-deb.sh) that
would have shipped broken homepage links, and added packaging/README.md — a maintainer
runbook for AUR / Fedora COPR / Ubuntu PPA / Flathub and the per-release checklist.
Docs: migrating from Howdy
New Migrating from Howdy guide and an honest Ward vs Howdy
comparison table in the README (right camera, IR emitter, poses, measured threshold, liveness,
fail-open, Wayland/greeter, zero-pip deps). GitHub Discussions is now the place for questions,
ideas and "does it work on my laptop" reports (linked from the issue picker).
Polish
doctor and the app's health report now read the shadow-test latency cutoff from the engine's emitter.SHADOW_MAX_LATENCY_MS instead of a hardcoded 150, so the reported threshold can't drift
from the one the engine actually gates on.
Fixed a latent cache leak: the shadow-test vote was cached on the Engine and reset only by authenticate(), so the lab (which reuses one Engine across samples) would have frozen the first
shadow vote for the whole session once an emitter was wired in. The lab now resets it per sample.
Engine hardening & portability CI
The liveness accept/reject core is now a pure Engine._accept_votes(...) helper with its own
unit-test case table (single PASS, strict-needs-a-strong-voter, any FAIL, all-abstain with/without require_liveness_when_available, n_pass ≥ required). It was previously inline in _decide with
no direct test. Behaviour is unchanged — this is a refactor plus coverage of the security core.
Models.preload() now also builds the YuNet detector (a one-shot dummy detect, fail-soft), so
the ~50–150 ms detector init overlaps the camera open instead of landing on the first frame on the
cold path; the daemon's duplicate warm-up is removed.
New distros CI workflow (weekly + on-demand) runs the portability matrix — Debian 12,
Ubuntu 22.04, Fedora, Arch, openSUSE Tumbleweed — via the existing container harness, so the
version/distro fallbacks (3.10 TOML, 3.13 libcrypt, the YuNet graph split, dlib) are exercised.
Enrollment embeds the same frames auth does (accuracy)
Fixed a train/serve mismatch: unlock embedded a brightness-normalised crop while enrollment
embedded the raw frame, so models captured in dim/bright/night light (the "change the light"
and IR passes) could never be reproduced at unlock. Both paths now go through one shared brightness.embed_rgb helper — identical for in-range frames (well-lit enrolments unchanged),
identically normalised for out-of-range ones. Re-enroll to rebuild the affected models.
Setup steers around the missing dlib embedder
On Debian/Ubuntu/Fedora python3-dlib isn't packaged and pip is not used, so the default recogniser=dlib had no embedder and failed at the first frame. ward setup now probes dlib and
defaults to installing ArcFace when neither embedder is present, and doctor reports a missing
dlib embedder as a real failure (with a fix) instead of a note.
PAM hardening
The keyring unseal (which materialises your cleartext login password for pam_gnome_keyring / pam_kwallet) now runs only for login/unlock services (gdm-password, sddm, lightdm, login, greetd, kde) — never for sudo/su/polkit, which don't unlock a keyring and so
never need the password materialised.
ward pam enable now refuses to silently bypass a second factor: if a required/requisite 2FA
module (pam_u2f, pam_google_authenticator, pam_yubico, …) sits after where the ward line
goes, its success-jump would skip it, so enable stops with a clear message (use --force to
override). (pam_faillock is not treated as a second factor — its authfail line only acts on
failure, and it's in Fedora's default stack.)
Manager app: clearer setup, safer first run
The setup wizard's anti-spoof step now only appears when there's a colour camera (the CNN is
colour-only and abstains on IR), and it reflects the current state — a satisfied "already on" step
instead of an actionable "Turn on" when it's already enabled (the default). Matches the terminal ward setup.
Cancelling the admin password prompt during an in-app enroll/calibrate now shows a toast and
reveals the console, instead of silently flipping back (it used to look like a dead button).
The Overview On switch can't enable face unlock with nobody enrolled: on first run it's
disabled so "Start setup" is the single entry point, and toggling it on with no enrolled face
routes through the wizard (which registers first) rather than wiring up a doomed face step.
Faster, less fussy enrollment hold
Each pose fills (and the "Hold still…" cue clears) sooner: the per-pose usable-sample target
drops from 12 to 9 for the guided sweep and 8 for --style ring (the ring pools its
honest held-out gate across 12 poses, so each one can hold fewer). Both still pool 24 held-out
frames per spectrum and keep a 4-sample-per-pose model, so the measured gate is unchanged — the
hold per pose is ~25 % shorter (guided) / ~33 % shorter (ring).
The pose arrival band widens 8° → 10°, so samples start banking while the head is still
settling instead of only after a dead-stop; still well above the estimator's ±4° noise and below
the 18° nearest inter-pose gap. The arrow's overshoot-reversal is now a fixed ~13.6° of overshoot
(band-independent), so widening the band didn't push the reversal point out with it.