中文 | Let an ordinary RGB webcam (laptop front camera / USB camera) that Windows Hello doesn't support unlock your PC by face.
This is the design & decision record: why it's built this way, where it stands now, and what's left. For end-user instructions see README.md; for dev setup, code map, and the gotcha list see contribute.md.
The main path is wired up and in the formal release line: lock-screen tile → named pipe → LocalSystem service → InsightFace recognition → read the LSA password → pack a KERB credential to really unlock. Both local accounts and Microsoft accounts (MSA-backed local login) are verified end-to-end in a VM and on real hardware. The project now has one-click install / clean uninstall, in-app updates, recoverable upgrades, and automated GitHub Release distribution.
| Module | Status |
|---|---|
| Stages 1–4: Python recognition / liveness / enrollment / auth orchestration + PySide6 console | ✅ done |
| Stage 5: C++ Credential Provider lock-screen integration (tile → service → KERB unlock) | ✅ main path done, verified on real hardware |
| Local account + Microsoft account (MSA-backed) unlock | ✅ verified end-to-end |
Live liveness prompts at the lock screen (auth_start/auth_poll async pair) |
✅ done |
Custom lock-screen tile avatar (WIC reads ProgramData, falls back to solid blue) |
✅ done |
| Lock-screen 3-attempt face retry (press "→" to retry), then fall back to password (CP-side) | ✅ done |
| Lock-screen auth starts only from "→" or the configured hotkey, not from tile preselection | ✅ done |
| Refreshed wide console UI + service / CP / model / camera diagnostics | ✅ done |
Installed-mode / dev-mode path split (.installed marker + FACEHELLO_HOME) |
✅ done |
| Portable package (standalone CPython + deps) + Inno installer + Chinese wizard | ✅ done |
| One-click clean uninstall (stop/remove service, unregister CP, wipe LSA password + gallery) | ✅ done |
| Size trimming: PySide6-Essentials + buffalo_l pruning / FP16 recognition | ✅ v1.0.5 setup: 260,924,312 bytes (~249 MiB) |
| GitHub Release automation (push a tag → CI builds setup.exe) | ✅ done |
5-4 hardening: pipe ACL (SYSTEM+Admins), CP server-identity verification, failure fallback / lockout, better logging, dev-only authenticate |
✅ done (Python + CP, shipped in v1.0.5) |
| Face-unlock global switch + separate sign-in / workstation-unlock scopes | ✅ done (v1.0.6) |
| Console service-log view + redacted diagnostic ZIP export | ✅ done (v1.0.6) |
| Optional multi-person protection (off by default; reject before matching) | ✅ done (v1.0.6) |
| Bounded SCM service recovery (60s, 120s, then no action; 24h reset) | ✅ done (v1.0.6) |
| Sleep/resume acceptance | 🚧 short + long sleep passed; remaining hardware scenarios tracked |
| Code signing: fixed personal self-signed certificate, signer pin, signed DLL / installer / uninstaller | ✅ done; no official CA certificate planned |
| Further slimming: opencv-headless, drop scipy/onnx transitive deps | ⏳ to do |
| Passive anti-spoofing (Silent-Face MiniFASNet, default-on + fail-open if model missing) | ✅ done |
| Same-name multi-template enrollment ("Add angle" appends, unlock takes the max similarity, FIFO cap) | ✅ done |
pytest for security logic (lockout / margin / anti-spoof gate / authenticate gating), wired into CI |
✅ done |
See §7 Roadmap for the full plan; hardening details are in §9.2, and uninstall safety is in §10.5.
Windows Hello face doesn't support ordinary webcams — not because the models aren't good enough, but because of hardware- and framework-layer constraints:
- Must be an IR (infrared) camera — for anti-spoofing (photo / screen attacks) + low-light imaging.
- Must have a WBF driver — the camera has to register as a "biometric sensor" through the Windows Biometric Framework for the system to accept it.
- Liveness detection — IR + depth do this naturally; monocular RGB is weaker.
Goal of this project: use an ordinary RGB webcam + open-source deep-learning models to "unlock the PC by face," as a sign-in method alongside password / PIN (it never removes password login).
| Route | How it works | Pros | Cons | Verdict |
|---|---|---|---|---|
| A. Credential Provider | Implement an ICredentialProvider COM, add a custom sign-in method, and submit a credential to winlogon on a match |
No IR hardware, no driver signing, free model choice | C++ COM is painful to debug; needs secure password storage | Adopted |
| B. WBDI biometric driver | Write a UMDF driver to disguise the camera as a Hello-compatible sensor (Sensor/Engine/Storage Adapter) | Native "Settings → Sign-in options → Face" integration | A step harder; needs driver signing; RGB likely rejected by IR/ESS policy | Dropped |
Decision: take Route A. First get the "recognition + liveness" pipeline working in Python, then build the C++ Credential Provider as the sign-in integration layer, with the two talking over local IPC (a named pipe). In practice this route panned out.
┌──────────────────────────────────────────────────┐
│ Lock / sign-in screen (LogonUI, SYSTEM) │
│ └─ FaceHello Credential Provider (C++ COM DLL) │
│ │ ① auth_start / auth_poll │
│ ▼ │
│ Local IPC: named pipe \\.\pipe\FaceHello (JSON) │
│ │ │
│ ▼ │
│ Face auth service (Python, LocalSystem, resident) │
│ ├─ Camera capture (OpenCV, CAP_DSHOW) │
│ ├─ Liveness (MediaPipe FaceLandmarker: blink/turn)│
│ ├─ Passive anti-spoofing (MiniFASNet) │
│ ├─ Recognition (InsightFace ArcFace, 512-d) │
│ └─ Optional multi-face gate + gallery match │
│ → return {ok, user, similarity} │
│ │ │
│ ▼ ② on match │
│ The CP reads the password from the LSA Secret in │
│ the SYSTEM context and builds a │
│ KERB_INTERACTIVE_UNLOCK_LOGON to unlock │
└──────────────────────────────────────────────────┘
The two layers are deliberate: the Python recognition stack can't be crammed into the LogonUI process, so it's split into "DLL handles UI / service handles the algorithm," coupled only by the named-pipe protocol — changing Python doesn't require recompiling the DLL. The password never travels over IPC: the service only returns {ok, user, similarity}; the CP reads the LSA itself in the SYSTEM context.
| Stage | Actually used | Notes |
|---|---|---|
| Face detection | InsightFace det_10g (buffalo_l) |
Same package as recognition, DET_SIZE=(320,320) |
| Feature recognition | ArcFace w600k_r50 (buffalo_l, 512-d) |
onnxruntime CPU inference, high accuracy |
| Active liveness | blink (EAR) + head turn (solvePnP) random challenge | MediaPipe Tasks FaceLandmarker, 468 landmarks |
| Passive anti-spoofing | Silent-Face MiniFASNetV2 (2.7_80x80) detects screen/replay |
bundles its own RetinaFace crop; default-on, can disable; fail-open if model missing |
Passive anti-spoofing is enabled (toggle in Settings): during recognition MiniFASNet samples several frames to reject screen/photo/video replays before matching — a sub-threshold score rejects at once, while a frame with no detected face is inconclusive and samples more, and only after antispoof_max_frames consecutive no-face frames does it fail-open (so a replay can't slip through on a single detection-miss frame). It covers the recorded-video replay that active liveness can't. Two gotchas: (1) MiniFASNet is extremely crop-sensitive, so it must use its companion RetinaFace box (InsightFace's tighter box misjudges); (2) input is BGR in [0,255] (the official ToTensor leaves /255 commented out).
- A monocular RGB camera inherently can't stop photo / video attacks — exactly why Microsoft mandates IR.
- This approach is weaker than native Hello, and is basically unusable in low light.
- Liveness is the floor, otherwise a single photo unlocks the machine; don't disable it by default for "convenience."
- The password is stored in an LSA Secret and never travels over IPC; the gallery stores feature vectors (not photos), encrypted on disk with machine-scoped DPAPI.
- Never remove the system's password / PIN provider — a fallback sign-in must always remain. Register the CP / test on real hardware only after taking a snapshot + keeping a spare admin account + a system restore point.
- Multi-account anti-misrouting (margin): when several people enroll on one machine, they share a single gallery and threshold, and
best_matchreturns the single most-similar template across the whole gallery — if two people's features are close, A can be matched as B and thus unlock the wrong account (an inherent monocular-RGB weakness). To guard against this, recognition adds a margin check: beyondsimilarity ≥ match_threshold, it also requiresbest − most-similar-other-person ≥ match_margin(default 0.05); if the two are too close it's judged "ambiguous identity" and rejected outright — better to fall back to the password than to risk unlocking the wrong account. With only one person enrolled there is no rival (margin = ∞) so it never triggers; the value is tunable on the Settings tab, andmatch_margin = 0disables it. Rivals are distinguished by profile name, so multiple templates of the same person don't compete with each other. - Optional multi-person protection: when enabled,
AuthSessioncounts detected faces before anti-spoofing and identity matching. Two or more faces cause an immediate biometric rejection, which consumes one of the CP tile's three attempts. It is off by default because bystanders, screens, posters, or distant faces can cause false rejections; when off, the existing largest-face behavior is unchanged.
The operational gotcha list (non-ASCII paths, the ~40s
FaceLandmarker.close()block, the SYSTEM-service font cache, etc.) lives in contribute.md; this section keeps only design-level decisions.
- Liveness landmarks: mediapipe 0.10.x removed the legacy
solutions, so this uses the Tasks APIFaceLandmarker(RunningMode.VIDEO+ strictly increasing timestamps); the EAR / solvePnP logic downstream is unchanged. - Non-ASCII path workaround: the working directory contains Chinese → mediapipe is fed bytes via
model_asset_buffer, OpenCV usescv2.imdecode(np.fromfile(...)); InsightFace/onnxruntime load.onnxwith native Unicode support, fine. - Recognition perf tuning:
FaceAnalysis(allowed_modules=["detection","recognition"])runs only 2 models; a startup warmup really runs one sample image to move the cold-start cost to launch time. Measured single-face 640×480 recognition ≈ 250 ms. - Cold-start disk I/O: deleted the unused
1k3d68/2d106/genderagefrom buffalo_l, cold read 341 MB → 191 MB, no loss in recognition accuracy.
- Stage 1 — Python recognition prototype: camera → detection → recognition (
camera/detector/matcher). - Stage 2 — Liveness: blink (EAR) + head turn (solvePnP) active random challenge (
liveness). - Stage 3 — Enrollment / matching: multi-frame averaged enrollment + DPAPI-encrypted gallery (
enroll/store); supports same-name multi-template ("Add angle" appends to cover different angles/lighting; unlock takes the max similarity over same-name templates,max_templates_per_nameFIFO cap). - Stage 4 — Auth orchestration + console:
authstate machine + PySide6 console (app/), including the refreshed wide UI and readiness diagnostics. - Stage 5 — Credential Provider: C++ COM lock-screen integration + LSA credential + KERB unlock. Main path done, verified on real hardware (see §9).
- Distribution — setup.exe: portable package + Inno Chinese wizard + clean uninstall + Release automation shipped in the formal
v1.0.0line; later releases added in-app updates, recoverable upgrades, and automatic installed-state acceptance (see §10). - Hardening (5-4): pipe ACL (SYSTEM+Admins), CP-side LocalSystem server verification, failure fallback / lockout, better logging, and dev-only
authenticate; completed and released inv1.0.5. - Operational controls and diagnostics (
v1.0.6): global + per-scenario face-unlock switches, console log view / redacted export, optional multi-person protection, and bounded SCM recovery. - Code signing: release DLL, installer, and uninstaller use the project's fixed personal self-signed certificate. The build pins its DER SHA-256 and requires the PFX from local storage or GitHub Secrets; no official CA certificate is planned (see SIGNING.md).
- Passive anti-spoofing: Silent-Face MiniFASNet (
2.7_80x80) + RetinaFace crop, multi-frame sampling during recognition to detect replays; default-on, can disable, fail-open if model missing. - Optional enhancements: finish the remaining sleep/resume hardware matrix, GPU/NPU inference, lower-friction passive liveness, and further slimming.
- Not going real Hello (WBF driver): RGB is limited by IR/ESS policy and most likely can't register as an official Hello face, so it's dropped.
- Credential storage = LSA Secret:
face_hello/cred_vault.py, keyL$FaceHello_<user>, password as UTF-16LE. Writing / deleting needs Administrator (the console); reading needs SYSTEM (the lock-screen CP). - Who reads the password = the CP itself (it's SYSTEM inside LogonUI). The password never travels over IPC; the service only returns
{ok, user, similarity}. - Identity contract (all four must match for unlock to succeed): profile name ==
GetUserName()(local SAM name) == LSA keyL$FaceHello_<user>== KERB account name. Microsoft accounts go through MSA-backed local login, the same chain; the LSA key name can't contain a backslash. - Component split:
- ① Console (GUI, Administrator): enroll a face + write the sign-in password to an LSA Secret.
- ② Auth service (resident, LocalSystem): camera + recognition + liveness, exposing
authenticate/auth_start/auth_pollover the named pipe. - ③ Credential Provider (C++ COM): lock-screen tile → call ② → on success read the LSA → build
KERB_INTERACTIVE_UNLOCK_LOGONand submit.
- Credential vault
cred_vault(LSA Secret read/write) + test CLI. Admin write → SYSTEM read verified. - Service-ization: named pipe
\\.\pipe\FaceHello, single-instance serial, JSON messages; synchronousping/authenticate(the latter bypasses lockout, so it's dev-only — rejected in installed mode) + the async pairauth_start/auth_poll(the lock screen refreshes liveness prompts while recognizing). Self-tested withscripts/auth_client.py. - C++ Credential Provider (based on Microsoft's SampleV2CredentialProvider): tile enumeration, scan-thread polling,
SignalAutoLogonauto-submit,GetSerializationreads the LSA → KERB unlock. - Custom lock-screen tile avatar: the CP uses WIC to read the first image (PNG/JPG/BMP) in
C:\ProgramData\FaceHello\, scales it to 128×128, and falls back to a solid blue placeholder on failure. - Explicit auth start: tile selection only shows the prompt; face auth starts from the "→" submit path or the optional single-key hotkey mirrored to
C:\ProgramData\FaceHello\hotkey.txt. - Lock-screen retry (post-d): on a failed scan the tile keeps the "→" submit button as a retry entry — up to 3 attempts (
kMaxFaceAttempts, any failure counts), then it stops scanning and prompts the user to use their password. The Python service is unchanged (its own lockout, 5 fails / 30s, stays the brute-force gate; 3 < 5). - End-to-end verification: VM (snapshotted) + real hardware, both local and Microsoft accounts unlock by face successfully. A SYSTEM/session-0 process can open the camera at the lock screen (the service can run as LocalSystem). The 3-attempt retry is verified on real hardware.
- Pipe ACL + anti-squatting:
CreateNamedPipeinservice.pynow carries an explicit DACL allowing only SYSTEM + Administrators (the CP is SYSTEM so it can connect; admin-context GUI /auth_client.pytesting still works, while local non-privileged processes can no longer impersonate the CP and invoke auth). It also setsFILE_FLAG_FIRST_PIPE_INSTANCE: if an instance of the name already exists, creation fails → we log a security warning + back off, never silently.- CP-side server identity verification (
v1.0.5): after connecting,PipeClient::CallusesGetNamedPipeServerProcessId, opens the server process token, and requires itsTokenUserto be LocalSystem (S-1-5-18). If the PID or token can't be verified, or the SID differs, the CP closes the pipe before sending any request and falls back to password / PIN. Snapshot-VM acceptance covered both the real LocalSystem service and a non-SYSTEM process squatting on the same pipe name.
- CP-side server identity verification (
- Failure fallback / lockout: in-memory counting in
_AuthRunner— only genuine biometric rejections count (mismatch / ambiguous identity / liveness failure / no face seen); infrastructure errors (not enrolled / camera unavailable / exception) don't. Afterlockout_max_fails(default 5) consecutive fails it cools down forlockout_seconds(default 30); during the cooldownauth_startreturns "locked, use password" without opening the camera; a success or cooldown expiry resets it. Thresholds are tunable on the Settings tab (0 = off). The camera path's open timeout is cut from 30s to 8s (authenticate_blocking(camera_timeout_s=8)) so a missing/busy camera falls back to the password fast instead of stalling. The system password / PIN provider always remains as the inherent fallback. - Better logging:
service.pyadoptslogging+RotatingFileHandler(service.log, ~1MB×3, with timestamps / levels), replacing bareprint; in service mode a_StreamToLoggershim routessys.stdout/stderr(native-lib prints and uncaught tracebacks) into the same rotating log (purely C-level stderr noise may not land — those are just startup banners, not audit content). Passwords are never logged; the username + similarity are (a local log, readable by SYSTEM / admins). - Further hardening (post-v0.1.2): the sync
authenticatecommand (which bypasses lockout) is now dev-only, rejected in installed mode, so the production pipe only exposesping+auth_start/auth_poll; passive anti-spoofing samples multiple frames instead of one, so a replay can't slip through on a single detection-miss frame; the security state machines (lockout / margin / anti-spoof gate /authenticategating) got pytest coverage wired into CI.
- Face-unlock scopes: the console writes
C:\ProgramData\FaceHello\auth_scope.txtas a one-byte bitmask (0off,1sign-in,2workstation unlock,3both). Missing or malformed data defaults to both scopes for upgrade compatibility. Windows 10+ commonly reports both startup/sign-out andWin+LasCPUS_LOGON, so the CP uses the current WTS session username to distinguish them; if the query fails while only one scope is enabled, it hides FaceHello rather than violating the configured scope.CPUS_CREDUIand UAC remain unsupported. - Service-log diagnostics: the console reads the latest 200 lines, reports modification time and WARNING / ERROR counts, opens the log directory, and exports a fixed-whitelist ZIP containing the report plus rotated logs. Export redacts usernames and credential-like assignments and never includes
faces.dat, LSA Secrets, templates, or camera images. - Bounded SCM recovery: install/update configures restart after 60 seconds for the first abnormal stop, 120 seconds for the second, and an explicit final
SC_ACTION_NONE; failures reset after 24 hours.SERVICE_CONFIG_FAILURE_ACTIONS_FLAGcovers non-zero service stops from Python exceptions. A normal Administrator stop does not restart the service, and installed acceptance checks the exact policy. - Sleep/resume evidence first: short- and long-duration sleep passed hardware acceptance. Hibernation, lid-close, Fast Startup, camera contention, and post-Windows-Update recovery remain in
SLEEP_RESUME_ACCEPTANCE.md; no power-event framework or unconditional tracker reset is added without a reproducible failure.
Goal: a single setup.exe (Inno Setup, admin privileges) that on a clean Win10/11 machine does, in one click, "lay down files → create the writable data dir → register and auto-start the service → register the CP DLL → drop a console shortcut," and can uninstall completely cleanly without ever leaving a broken LogonUI behind. Implemented and included in v1.0.0.
- Python runtime = standalone CPython + deps shipped alongside (not PyInstaller). Ship a python-build-standalone (same source as uv) + a pre-installed
site-packages; the service / GUI run the source via an absolute path topython.exe. Rationale: the native data files of mediapipe / insightface / onnxruntime are preserved as-is, sidestepping freeze hooks and the pywin32-service freezing pitfalls. - Models baked into the installer: buffalo_l detection + the FP16-quantized w600k_r50 recognition model + face_landmarker.task are bundled, so the first unlock doesn't depend on a network download. Source/dev first-run download remains the pruned FP32 buffalo_l (~191 MB).
- Installed-mode / dev-mode split:
config.pyswitches on the.installedmarker file at the install root (orFACEHELLO_HOME). A marker file rather than just an env var, because the SCM caches the system env block until the next reboot, so a freshly-installed service can't see a newly-set variable; a marker file lands on disk with the install, so the service / GUI are immediately consistent. - Build the C++ DLL with
/MTstatic CRT: avoids a VC++ runtime dependency. - Automatic installed-state acceptance:
doctor.pykeeps its model / camera / pipe hardware check and can also capture a pre-install baseline containing only hashes, sizes, and counts. After repairing ProgramData ACLs but before changing the service or CP, the installer captures that baseline. Once the new service is ready, it verifies the release build, SCM auto-start, ImagePath and bounded recovery policy, pipe version / protocol, current-version CP DLL,service.log, face gallery, and system Credential Provider / Filter registration. Success deletes the temporary baseline; failure retains it and enters the old-payload / CP recovery path. The baseline contains no usernames, face embeddings, Windows passwords, or LSA Secrets. - Shared service-health contract: installer maintenance, console diagnostics, and
doctor.pyall useprobes.service_healthto distinguish not-ready, version mismatch, protocol mismatch, and malformed response states. Update checking likewise reports local-network, GitHub rate-limit / remote, manifest, disk, download-response, and hash / signature failures separately. - In-app updates with recoverable upgrades: the console handles explicit update checks, resumable downloads, and a second verification pass before launching the normal installer through UAC. There is no SYSTEM updater, silent installation, or automatic Windows restart. The manifest carries version, protocol, SHA-256, and signer constraints. Before an upgrade, Inno backs up the old payload; if post-install configuration or automatic acceptance fails, it restores the old files and CP before returning the service to its pre-upgrade state.
- Code signing: tagged releases require the fixed personal self-signed PFX from GitHub Secrets. The workflow verifies the certificate's DER SHA-256 against
FACEHELLO_EXPECTED_SIGNER_SHA256, signs the CP DLL, installer, and uninstaller, and verifies them before upload. The installer bundles only the public CER, checks it against the build-info signer pin, and establishes local trust after UAC approval. No official CA / Azure / EV certificate is planned; the private key remains local or in GitHub Secrets only (see SIGNING.md).
C:\Program Files\FaceHello\ (read-only, program files)
├─ python\ standalone CPython (incl. site-packages)
├─ app\ face_hello\ source (copied as-is)
├─ winservice_main.py uninstall_cleanup.py
├─ FaceHelloCP.dll (/MT build, Release|x64)
├─ models\ buffalo_l\ + face_landmarker.task
└─ .installed installed-mode marker (empty file)
C:\ProgramData\FaceHello\ (writable, runtime data; shared by SYSTEM and the elevated GUI)
├─ data\ faces.dat service.log
├─ lang.txt hotkey.txt auth_scope.txt CP-readable console setting mirrors
└─ <avatar>.png lock-screen tile avatar (read by the CP as SYSTEM)
Why data goes to ProgramData: Program Files is read-only for normal users, while the enrollment GUI runs in the (elevated) user context and the service runs as LocalSystem — both need to write faces.dat / logs. ProgramData is pure-ASCII and writable by both.
-
config.pypath split: in installed modeDATA_DIR→C:\ProgramData\FaceHello\data,MODELS_DIR→models\under the install root; dev mode keeps repo-relative paths, not breaking theuv runflow. - Service ImagePath absolute:
winservice_main.pyinstall writes the ImagePath as a fully-qualified, quotedpython.exe ...winservice_main.py, no longer depending on the venv / current directory.MPLBACKEND=Agg+MPLCONFIGDIRpoint at the ProgramData data dir. - CP DLL avatar path: the
C:\ProgramData\FaceHello\hard-coded incp/CFaceCredential.cppmatchesAVATAR_DIR.
- Build the CP DLL:
MSBuild cp\FaceHelloCP.sln /p:Configuration=Release /p:Platform=x64(/MT). - Portable package:
scripts/build_release.py— grab standalone CPython 3.11 → install thedistdependency group → slim → copy source + models + DLL + pywin32 runtime DLLs. Output defaults to%LOCALAPPDATA%\FaceHello-build\FaceHello. - Inno compile:
installer\FaceHello.iss→installer\Output\FaceHello-Setup-x.y.z.exe. The.iss/.islare UTF-8 with BOM (otherwise Chinese Windows reads them as GBK → mojibake). The Chinese wizard shipsChineseSimplified.islin the repo (Inno has no built-in Chinese). - CD automation:
.github/workflows/release.yml— pushing av*tag prepares / prunes / FP16-quantizes models, verifies the fixed signer pin, builds and signs the DLL + portable package + Inno installer / uninstaller, verifies them, then uploads the GitHub Release. The version is injected from the tag, soinstaller/pyprojectdon't need manual edits. - Signing:
build_releasesigns the CP DLL viaFACEHELLO_SIGN_PFX; Inno/DSignsigns setup.exe + the uninstaller. Tagged CI releases require the PFX/password secrets, verify the fixed signer pin before building, delete the temporary PFX, and verify every signature before upload (see SIGNING.md).
The [UninstallRun] + [UninstallDelete] in installer\FaceHello.iss are implemented, ordered stop-service → unregister → delete files:
-
winservice_main.py stop→regsvr32 /s /u FaceHelloCP.dll(unregister before deleting the DLL, otherwise a registry remnant points at a non-existent DLL → LogonUI risk) →winservice_main.py remove. - Completely clean:
uninstall_cleanup.pywipes the LSA sign-in password + deletes the gallery;[UninstallDelete]removes the ProgramData data dir and the runtime-generated files under the install dir (.pyc / .installed / service.log / avatar).The uninstall policy was changed from the early "prompt the user to keep or delete" to unconditionally wipe clean (the user explicitly asked for "a completely clean uninstall").
- Recovery path (documented): if the CP misbehaves, safe mode / another admin account
regsvr32 /u/ delete this CLSID key under HKLM Credential Providers.
- Clean VM (snapshot) end-to-end: install → enroll → lock-screen unlock → uninstall clean, LogonUI normal.
- Real-hardware end-to-end (local + Microsoft accounts).
- Authenticode-sign
setup.exe, uninstaller, andFaceHelloCP.dllwith the fixed personal self-signed certificate; the embeddedpython.exeremains signed by python.org and is not re-signed. First install can still show an unknown-publisher warning until the pinned public certificate is trusted locally. - 5-4 hardening complete: pipe ACL, first-instance protection, CP-side LocalSystem server verification, failure fallback / lockout, production
authenticategating, and rotating logs are all landed; the final CP check shipped and passed VM acceptance inv1.0.5.
- The install directory is user-selectable (
DefaultDirName={autopf}\FaceHello, changeable). The bulk (models + Python deps) follows the install dir, so installing to D: leaves C: almost untouched. - ✅ Any fixed internal NTFS drive (C/D/E…).
- ❌ Forbidden: removable / USB drives, network drives, BitLocker drives not auto-unlocked at boot — the CP DLL has to load early at the lock screen (LogonUI/SYSTEM), and those drives may not be mounted yet → the tile fails to load.
- The only thing fixed on C: is
C:\ProgramData\FaceHello\data(gallery + logs, tiny, tens of KB ~ a few MB). - Space-in-path regression:
C:\Program Files\FaceHelloitself contains a space; the ImagePath is fully-qualified and quoted, and install/auto-start are verified in a VM and on real hardware (cf. the other project's issue #25 in §11).
The published v1.0.5 installer is 260,924,312 bytes (~249 MiB). The main reductions are:
- PySide6 → PySide6-Essentials (the
distdependency group): drops Addons, the biggest beingQt6WebEngineCore.dll(~196 MB); then manually deleteqml/, non-zh/entranslations/,include/, etc. - buffalo_l pruning + FP16 recognition: delete
1k3d68(144 MB) /2d106/genderage, keep det_10g + w600k_r50, then convertw600k_r50from FP32 (~174 MB) to FP16 (~87 MB) in release CI. Local conversion may retainw600k_r50.fp32.bakfor rollback;build_release.pyexcludes*.bakfrom packages. - Exclude
buffalo_l.zip: the pre-extraction raw archive (~281 MB) left over after insightface downloads; kept out of the package (otherwise setup.exe doubles to ~597 MB — a pitfall hit by CI's fresh download). - General cleanup:
__pycache__,tests/.
Optional further slimming (not done, low priority):
- opencv-python → opencv-python-headless: the dist group still uses
opencv-python. Note mediapipe hard-depends onopencv-contrib-python(which also provides cv2), so a headless swap can't avoid both coexisting; needs separate hands-on trimming. - Drop transitive deps:
scipy(92 M),onnx(41 M, ≠ theonnxruntimeneeded at runtime).skimagealmost certainly stays (insightface's face alignment usesSimilarityTransform, which drags in scipy); theonnxpackage (used for model building, inference relies only on onnxruntime) may be removable — verify withoffline_check.pybefore touching it.
- pywin32 service viability on standalone CPython: verified.
build_release.pycopiespythoncomXX.dll/pywintypesXX.dllto the python root, so the service (SYSTEM) can importwin32service/servicemanagerand be started by the SCM. - GitHub Release asset experience: the current ~249 MiB installer uploads / downloads normally, well under the 2 GB limit.
FaceWinUnlock-Tauri (Tauri+Vue3 GUI / Rust CP DLL / OpenCV recognition / SQLite credential store; core went closed-source from 2026-03) overlaps with this project in function. Distilled from its real user bug list:
- Space-in-path install: their issue #25 was auto-start failing under a space-containing path. This project's service ImagePath is fully-qualified and quoted, and
C:\Program Files\FaceHello(with a space) install/auto-start is regression-verified (see §10.7). - Uninstall order corroborated: their flow "uninstall core component first → then the main program, else a broken CP remains" matches this project's "stop service →
regsvr32 /u→ then delete." - Side evidence: they also hit the OpenCV Chinese-directory problem, confirming the necessity of this project's
imdecode/model_asset_bufferworkarounds.
Research item (undecided): they use a Tongyi RGB passive-liveness model (cv_manual_face-liveness_flrgb, threshold 0.6) instead of an active challenge, which feels smoother, but they admit its liveness accuracy was never tuned well. Could serve as this project's "low-friction mode" or a defense-in-depth candidate, lower priority than the existing active challenge.
Architecture-difference takeaway: they send the password in cleartext over the pipe (their README admits the sniffing risk); this project's password never crosses IPC (the CP reads the LSA itself as SYSTEM); recognition with ArcFace 512-d beats their classic OpenCV features. On size, their Rust + system WebView2 is far smaller than this project's embedded Python, but that's not a reason to rewrite (see the §10.8 trade-off).
- Howdy on Linux (ordinary camera + PAM-like Hello face login) — recognition / liveness ideas worth borrowing.
- Microsoft's official sample
microsoft/Windows-classic-samples→SampleV2CredentialProvider(Credential Provider skeleton). - InsightFace: https://github.com/deepinsight/insightface