Security Model

Threat Model

Facelock is a local biometric authentication system. The threat model assumes:

  • Attacker has physical access to the machine (the entire point of face auth is physical-presence scenarios like unlocking a laptop)
  • Attacker may have a photo or video of the enrolled user
  • Attacker does not have root (if they do, game over regardless)
  • Attacker cannot modify files in /etc/facelock/, /var/lib/facelock/, or /lib/security/

Privacy Guarantees

Facelock is designed to keep biometric data under the user's exclusive control:

  • Local-only inference: All face detection and recognition runs on-device via ONNX Runtime. No images, embeddings, or metadata are ever transmitted over the network.
  • No telemetry: Facelock contains zero analytics, tracking, or phone-home code. After the one-time model download during facelock setup, it never contacts any server.
  • No cloud dependencies: Authentication works fully offline. No account registration, no API keys, no external services.
  • Data stays on disk: Face embeddings are stored in a local SQLite database (/var/lib/facelock/facelock.db) with restrictive permissions (640, root:facelock). Optional AES-256-GCM encryption with TPM-sealed keys provides defense in depth.
  • Open source: All code is MIT/Apache-2.0 licensed. No proprietary blobs or obfuscated network calls. Privacy claims are verifiable by reading the source.

Attack Vectors & Mitigations

1. Photo/Video Spoofing (CRITICAL)

Attack: Hold a photo or video of the enrolled user in front of the camera.

Why this matters: This is the #1 attack against face authentication. Without mitigation, anyone with a Facebook photo can unlock the machine.

Mitigations (layered, implement all):

A. IR Camera Enforcement (Required)

security.require_ir config flag, default true:

[security]
require_ir = true  # Refuse to authenticate on RGB-only cameras

Rationale: Phone screens and printed photos do not emit infrared light correctly. An IR camera sees a flat, textureless surface where a real face would have depth and skin texture in IR. This single check eliminates the vast majority of spoofing attacks.

Limitation: IR camera detection by format/name is heuristic. Some cameras report YUYV but are actually IR. The facelock devices command should display whether each camera is detected as IR.

B. Frame Variance Check (Required)

Require minimum variance across consecutive frames during authentication. Real faces have micro-movements causing slight embedding variation. A static photo produces near-identical embeddings (similarity > 0.99).

Config:

[security]
require_frame_variance = true  # Reject static images (photo attack defense)
min_auth_frames = 3            # Minimum frames before accepting match

In IR mode, verify that the face region has expected IR texture characteristics:

  • Real skin has micro-texture visible in IR
  • Photos/screens appear as flat, uniform surfaces in IR
  • Compute standard deviation of pixel intensity within the face bounding box
  • Reject faces with abnormally low texture variance

2. Model Tampering

Attack: Replace ONNX model files with adversarial models that always match (or match specific attackers).

Mitigations:

A. SHA256 Verification at Load Time (Required)

Verify model integrity not just at download, but every time the daemon loads models. Tampered files are rejected before any inference runs.

B. File Permissions on Model Directory (Required)

# Models owned by root, not writable by others
chown -R root:root /var/lib/facelock/models
chmod 755 /var/lib/facelock/models
chmod 644 /var/lib/facelock/models/*.onnx

3. Embedding / Database Security

Attack: Read or modify the SQLite database to extract biometric data or inject fake embeddings.

Mitigations:

A. Database File Permissions (Required)

# Database owned by root, readable only by root and facelock group
chown root:facelock /var/lib/facelock/facelock.db
chmod 640 /var/lib/facelock/facelock.db

B. Embedding Sensitivity Warning

Face embeddings are biometric data. Unlike passwords, they cannot be changed. The database contains irreversible biometric templates -- if compromised, the user's face embeddings cannot be "rotated" like a password.

C. Encryption at Rest (Implemented)

For high-security deployments, embeddings can be encrypted with AES-256-GCM using either a plaintext key file (encryption.method = "keyfile") or a TPM-sealed key (encryption.method = "tpm"). The TPM method seals the AES key at rest; it is unsealed at daemon startup and held in memory. See Configuration for the [encryption] and [tpm] sections.

4. D-Bus IPC Security

Attack: Unauthorized user connects to the daemon via D-Bus to trigger auth, enroll faces, or extract data.

Mitigations:

A. D-Bus System Bus Policy (Required)

The D-Bus system bus policy (/etc/dbus-1/system.d/org.facelock.Daemon.conf) restricts which users and groups can own the bus name and invoke methods. Only root and members of the facelock group are granted access.

B. D-Bus Message Size Limits (Required)

The D-Bus bus daemon enforces message size limits, preventing memory exhaustion attacks.

Throttle authentication attempts: 5 per user per 60 seconds by default. Prevents brute-force and rapid-retry attacks.

5. PAM Module Hardening

A. Audit Logging (Required)

All authentication attempts are logged to syslog with user, service, and outcome:

pam_facelock(sudo): match for user alice
pam_facelock(sudo): no_match for user bob

This creates an audit trail in /var/log/auth.log or journald.

Allow different PAM services to have different security levels:

[security.pam_policy]
allowed_services = ["sudo", "polkit-1"]
denied_services = ["login", "sshd", "su"]

6. Daemon Process Hardening

After initialization, the daemon drops all unnecessary capabilities.

B. systemd Hardening (Required)

The systemd unit includes: ProtectSystem=strict, ProtectHome=yes, NoNewPrivileges=yes, PrivateTmp=yes, and other sandboxing directives.

Security Configuration Reference

[security]
disabled = false
abort_if_ssh = true          # Refuse face auth over SSH
abort_if_lid_closed = true   # Refuse if laptop lid closed
require_ir = true            # CRITICAL: refuse RGB-only cameras (anti-spoof)
require_frame_variance = true # Reject static images (photo defense)
require_landmark_liveness = false # Require landmark movement between frames (off by default)
min_auth_frames = 3          # Minimum frames before accepting (variance check)

[notification]
enabled = true               # Show "Identifying face..." on login screen

[security.pam_policy]
allowed_services = ["sudo", "polkit-1"]
denied_services = ["login", "sshd"]

[security.rate_limit]
max_attempts = 5             # Max auth attempts per user
window_secs = 60             # Rate limit window

Summary: Security Implementation Priority

PriorityMitigation
P0IR camera enforcement (require_ir)
P0Frame variance check (anti-photo)
P0Model SHA256 at load time
P0D-Bus system bus policy
P0D-Bus message size limits
P0PAM audit logging
P0Database file permissions
P1IR texture validation
P1Rate limiting
P1systemd hardening
P1Capability dropping
P1Service-specific PAM policy
P2Embedding encryption at rest
P2Memory zeroing on drop
P2Constant-time similarity comparison