SecureEye lets you unlock a session and approve sudo on Linux by looking at the camera. It started as a fork of Howdy. Howdy’s PAM module launches its Python recognition script as root on every login attempt, so the whole recognition stack runs inside the authentication path. Most of my work on SecureEye went into moving it out.

What is left in PAM is a small C++ module. It checks whether face login applies at all, sends one request to a recognition daemon and waits for the answer under a hard deadline. Any outcome it cannot verify counts as a failure, and the password prompt takes over.

It is alpha software. Keep a password set up; face login is meant to sit in front of it.

Architecture

  1. sudo / login
  2. pam_secureEye.so
  3. authd.sock
  4. secureeye-authd
  5. camera
One request per attempt. PAM and the daemon only talk through the socket (dashed).

PAM module

pam_secureEye.so · C++

Runs inside the PAM stack. Pre-checks run first: SecureEye disabled, an SSH session, a closed lid, or no enrolled face model. Then it sends a single request over the socket and polls with a deadline. No Python is linked in.

IPC boundary

/run/secureeye/authd.sock · mode 0660

Length-prefixed JSON frames: a 4-byte big-endian length, then at most 4096 bytes of UTF-8. One request and one response per connection, with a strict protocol version. Requests carry the user, PAM service, TTY and a deadline_ms between 100 ms and 30 s.

Daemon

secureeye-authd · Python · systemd

Runs as the unprivileged secureeye user with only the video group added. Holds the camera, the detector and the face models. Its own worker deadline sits 700 ms inside the client’s, so it replies before PAM gives up on it.

Recognition

An attempt reads camera frames for up to timeout seconds (4 by default). Each frame is equalised with CLAHE. Frames that are too dark are skipped, which matters with IR cameras whose emitters flash, and the rest are scaled down to at most 320 px high.

Each detected face is encoded and compared with the user’s enrolled encodings by Euclidean distance. The nearest one has to fall under the certainty threshold, 0.35 by default. The detector is built once and cached in the daemon, so only the first attempt after a restart pays for loading the models.

dlib
HOG or CNN face detector with dlib’s 128-d ResNet face descriptor, the same encoder Howdy uses. Available on every architecture.
MediaPipe
The default on x86_64. BlazeFace finds the face and the 478-point face mesh is fitted to it. The encoding is the mesh geometry itself, normalised and resampled to 128 values. There is no trained face-recognition model in this path.

After a match, optional “rubberstamp” checks can run before the login is accepted. One asks the user to nod, another waits for a hotkey. They are off by default.

Failure policy

How each outcome maps to a PAM return code. With the module set to auth sufficient, both non-success results drop through to the next module, which is normally the password prompt.

WhenPAM resultEffect
SecureEye disabled, SSH session, lid closed, no face modelPAM_AUTHINFO_UNAVAILSkipped before the camera turns on
Face matchedPAM_SUCCESSLogged in
No match before the timeout, frames too dark, bad device, rubberstamp failedPAM_AUTH_ERRPassword prompt
Daemon unreachable, IPC timeout, malformed frame, version mismatchPAM_AUTH_ERRPassword prompt

Install

All three distributions get the same single secure-eye package. It holds the PAM module, the daemon, the recognition runtime and the secureEye CLI. The recognition backends are ordinary distribution packages instead of a bundled virtualenv, so the daemon runs on the system Python and nothing has to be rebuilt after a Python upgrade.

Debian / Ubuntu

sudo add-apt-repository ppa:vhrabar/tools
sudo apt update
sudo apt install secure-eye

Enables the PAM profile and starts the daemon on install.

Fedora / RHEL

sudo dnf copr enable vhrabar/SecureEye
sudo dnf copr enable vhrabar/python-extras
sudo dnf install secure-eye
sudo systemctl enable --now secureeye-authd.service

Add pam_secureEye.so to PAM yourself, through an authselect profile.

Arch Linux

paru -S secure-eye
sudo systemctl enable --now secureeye-authd.service

Add auth sufficient pam_secureEye.so to /etc/pam.d/sudo or system-auth.

First run

  1. Point device_path at your camera with sudo secureEye config, then restart the daemon.
  2. Enrol your face with sudo secureEye add.
  3. Open a new terminal and run sudo -i.

The README covers the PAM setup for each distribution step by step. README

Releases

One release workflow runs the tests and builds and install-tests the deb, RPM and Arch packages. If those pass, it publishes to the Launchpad PPA, Copr and the AUR. CHANGELOG.md is the only place release notes are written; the Debian and RPM changelogs and the GitHub release text are generated from it.

v0.2.1

Fixed
  • COPR builds failed in the source stage, before any chroot was built: .copr/Makefile resolved the release version with git describe while make parsed the file, which happens before the recipe installs git. The version came out empty and set-package-version.sh aborted. It is now resolved inside the recipe, after git is installed, and a clone with no reachable tag fails with a message that says so rather than with a blank version.

v0.2.0

Changed · Added · Removed · Fixed

Changed

  • SecureEye now ships as a single secure-eye package on Debian, RPM and Arch, replacing libpam-secureeye, secureeye-authd and the transitional metapackage. Existing installs are swapped automatically on upgrade.
  • The recognition backends are ordinary distribution packages instead of a bundled virtualenv: python3-mediapipe (amd64/x86_64 only) and python3-dlib, from ppa:vhrabar/tools on Ubuntu and copr:vhrabar/python-extras on Fedora. Select one with detector_backend in /etc/secureEye/config.ini.
  • The daemon and the secureEye launcher run on the system Python interpreter, so nothing has to be rebuilt after a Python upgrade. The systemd unit and the launcher now use the interpreter meson was configured with rather than a hardcoded virtualenv path.
  • Releases no longer carry .deb or .rpm attachments. Install from the PPA, COPR or the AUR instead.

Added

  • Arch Linux packaging is published to the AUR as secure-eye.
  • CHANGELOG.md is the single source of truth for release notes. scripts/set-package-version.sh renders a release's section into the Debian and RPM changelogs, and scripts/changelog-section.sh extracts it for the GitHub release body.
  • A single release workflow that runs the tests, verifies the deb, RPM and Arch packages build and install, and only then publishes to the Launchpad PPA, COPR and the AUR. COPR builds are pinned to the released tag.
  • A Prepare release workflow that sets the version, tags it and drafts the release from the changelog.

Removed

  • The install-time virtualenv, the vendored Python wheels and the dpkg trigger that rebuilt the virtualenv whenever python3 was upgraded.

Fixed

  • Launchpad PPA uploads carry the real changelog entries; every upload previously read "New upstream release".
  • Fedora and RHEL install instructions: the package ships no systemd preset, so secureeye-authd.service has to be enabled manually. The README claimed installation enabled it.
  • The CLI reference lists the set command.

v0.1.3

First Arch release: SecureEye now packages and installs on Arch-like distributions, alongside the existing Debian/Ubuntu/RPM support.

Fixed
  • Implemented detector caching and reduced time constraints on CPU-based systems.

v0.1.2

First RPM release: SecureEye now packages and installs on Fedora and RHEL-family distributions, alongside the existing Debian/Ubuntu support.

Added
  • RPM packaging mirroring the Debian split layout: three packages — libpam-secureeye (PAM module), secureeye-authd (daemon plus the Python recognition runtime), and the secure-eye transitional metapackage.
  • COPR distribution (vhrabar/SecureEye): install on Fedora with dnf copr enable vhrabar/SecureEye && dnf install secure-eye.
  • GitHub Actions workflow building RPMs for x86_64, x86_64-v3 and aarch64, attaching them to GitHub releases, with an install smoke test.
  • scripts/build-rpm.sh, to build the RPMs in a Fedora container.
  • aarch64 dlib backend: since Fedora ships no python3-dlib and there is no prebuilt aarch64 wheel, the dlib recognition wheel (pinned to 20.0.1) is compiled from source during the RPM build.

v0.1.1

Fixed
  • MediaPipe no longer uses a non-existing PipAudio on some configurations.

Showing the latest 5 of 6 releases. Full changelog

Limits