System Contracts
Stable contracts. Do not change without updating this document.
Binaries
| Binary | Crate | Purpose |
|---|---|---|
facelock | facelock-cli | Unified CLI (daemon, auth, enroll, test, setup, etc.) |
pam_facelock.so | pam-facelock | PAM authentication module |
facelock-polkit-agent | facelock-polkit | Polkit face authentication agent (not production-ready — do not autostart; will steal polkit auth from the DE's agent) |
CLI Subcommands
| Command | Purpose |
|---|---|
facelock setup | Download models, create directories |
facelock setup --systemd | Install/enable systemd units |
facelock setup --pam | Install PAM module to /etc/pam.d/ |
facelock enroll | Capture and store a face |
facelock test | Test face recognition |
facelock list | List enrolled face models |
facelock remove <id> | Remove a specific model |
facelock clear | Remove all models for a user |
facelock preview | Live camera preview |
facelock devices | List V4L2 cameras |
facelock status | Check system status |
facelock config | Show/edit configuration |
facelock daemon | Run persistent daemon |
facelock auth --user X | One-shot auth (PAM helper) |
facelock tpm status | TPM status |
facelock encrypt | Encrypt face database |
facelock decrypt | Decrypt face database |
facelock audit | View audit log |
facelock bench | Benchmarks |
facelock restart | Restart daemon |
Operating Modes
| Mode | Config | PAM Behavior | CLI Behavior |
|---|---|---|---|
| Daemon | daemon.mode = "daemon" (default) | D-Bus IPC to daemon | Uses daemon if available, falls back to direct |
| Oneshot | daemon.mode = "oneshot" | Spawns facelock auth | Operates directly (no daemon) |
The CLI silently falls back to direct mode when the daemon is not available on D-Bus, regardless of config mode.
facelock auth Exit Codes
| Code | Meaning | PAM Code |
|---|---|---|
| 0 | Face matched | PAM_SUCCESS |
| 1 | No match / timeout / dark | PAM_AUTH_ERR |
| 2 | Error / no enrolled faces | PAM_IGNORE |
Filesystem Paths
| Path | Owner | Mode | Purpose |
|---|---|---|---|
/etc/facelock/config.toml | root:root | 644 | Configuration |
/var/lib/facelock/facelock.db | root:facelock | 640 | Face embeddings |
/var/lib/facelock/models/ | root:root | 755 | ONNX models |
/var/log/facelock/snapshots/ | root:facelock | 750 | Auth snapshots |
/usr/bin/facelock | root:root | 755 | CLI binary |
/lib/security/pam_facelock.so | root:root | 755 | PAM module |
All paths overridable via config. FACELOCK_CONFIG is honored for unprivileged processes, but privileged PAM/root auth flows ignore the environment and use either an explicit --config path or /etc/facelock/config.toml.
Config Schema
TOML format. All keys optional -- camera auto-detected, sensible defaults for everything. See Configuration for the full reference.
Sections
| Section | Key fields |
|---|---|
[device] | path (Option), max_height, rotation, warmup_frames, dark_threshold, dark_pixel_value, ir_emitter, camera_release_secs |
[recognition] | threshold, timeout_secs, detector_model, embedder_model, threads, execution_provider |
[daemon] | mode (DaemonMode enum), model_dir, idle_timeout_secs |
[storage] | db_path |
[security] | disabled, suppress_unknown, require_ir, require_frame_variance, min_auth_frames, abort_if_ssh, abort_if_lid_closed, rate_limit sub-section |
[notification] | mode (off/terminal/desktop/both), notify_prompt, notify_on_success, notify_on_failure |
[snapshots] | mode (off/all/failure/success), dir |
[encryption] | method (none/keyfile/tpm), key_path, sealed_key_path |
[audit] | enabled, path, rotate_size_mb |
[tpm] | pcr_binding, pcr_indices, tcti |
Camera Auto-Detection
When device.path is omitted:
- Enumerate
/dev/video0through/dev/video63 - Filter to VIDEO_CAPTURE devices
- Prefer IR cameras (name contains "ir"/"infrared", or supports GREY/Y16 format)
- Fall back to first available device
Database Schema
SQLite with WAL mode and foreign keys:
CREATE TABLE face_models (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user TEXT NOT NULL,
label TEXT NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE(user, label)
);
CREATE TABLE face_embeddings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model_id INTEGER NOT NULL REFERENCES face_models(id) ON DELETE CASCADE,
embedding BLOB NOT NULL, -- 512 x f32 = 2048 bytes (or encrypted blob)
sealed INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE rate_limit (
user TEXT NOT NULL,
attempt_time INTEGER NOT NULL
);
Only failed authentication attempts are recorded in rate_limit. Daemon mode and oneshot mode share the same SQLite-backed window, so daemon restarts do not clear lockout state.
IPC Protocol
D-Bus system bus (org.facelock.Daemon). Only used in daemon mode. The daemon exposes a D-Bus interface on the system bus, and both the PAM module and CLI connect as D-Bus clients. Access is controlled by D-Bus system bus policy (/etc/dbus-1/system.d/org.facelock.Daemon.conf).
Methods
Authenticate, Enroll, ListModels, RemoveModel, ClearModels, PreviewFrame, PreviewDetectFrame, ListDevices, ReleaseCamera, Ping, Shutdown
Raw camera frames require privilege. PreviewFrame remains root-only. PreviewDetectFrame returns the jpeg_data frame bytes to root unconditionally; a non-root caller receives them only after an interactive polkit authorization for the action org.facelock.preview-frames (checked via org.freedesktop.PolicyKit1.Authority.CheckAuthorization, AllowUserInteraction=true). While unauthorized — denied, prompt pending, polkit unreachable, or any D-Bus error — the daemon fails closed: jpeg_data is empty and the caller receives detection and recognition metadata only.
Capture concurrency: Authenticate, Enroll, PreviewFrame, and PreviewDetectFrame fail immediately with a daemon busy error while another capture is in flight. Clients treat this like any other daemon error and degrade to password auth.
Signals
AuthAttempted(user: s, matched: b) — emitted after each authentication attempt. Carries no similarity score. Bus policy restricts reception to root and the facelock group.
Return Types
AuthResult, Enrolled, Models, Removed, Frame, DetectFrame, Devices, Ok, Error
PAM Semantics
| Outcome | PAM Code |
|---|---|
| Face matched | PAM_SUCCESS (0) |
| No match | PAM_AUTH_ERR (7) |
| Daemon unavailable / error | PAM_IGNORE (25) |
| Timeout | PAM_AUTH_ERR (7) |
PAM module never blocks indefinitely. All operations have timeouts.
Syslog Format
pam_facelock(<service>): <result> for user <username>
Anti-Spoofing
| Defense | Config | Default |
|---|---|---|
| IR camera enforcement | security.require_ir | true |
| Frame variance check | security.require_frame_variance | true |
| Landmark liveness | security.require_landmark_liveness | false |
| Minimum auth frames | security.min_auth_frames | 3 |
| Variance threshold | FRAME_VARIANCE_THRESHOLD | 0.998 |
These defaults must not be weakened without security review.
Models
| Model | File | Size | Default |
|---|---|---|---|
| SCRFD 2.5G | scrfd_2.5g_bnkps.onnx | ~3MB | Yes |
| ArcFace R50 | w600k_r50.onnx | ~166MB | Yes |
| SCRFD 10G | det_10g.onnx | ~17MB | Optional |
| ArcFace R100 | glintr100.onnx | ~249MB | Optional |
Configurable via recognition.detector_model and recognition.embedder_model.