Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,55 @@

## Unreleased

## v0.3.6 — 2026-07-07

Security hardening batch — defense-in-depth on the D-Bus authorization surface,
a fail-open → fail-closed correction in passive liveness, and a runtime-safety
pin on the async executor. No public API or wire-format changes.

### Security

- **In-process root check on the privileged D-Bus methods** (`Enroll`,
`RemoveModel`, `ListModels`). These were root-only by the system-bus policy
file (`org.freedesktop.Visage1.conf`) alone; a missing, mis-scoped, or
overly-permissive policy — or running on the session bus — could let a
non-root caller invoke enrollment mutation or the enrollment listing. The
daemon now re-verifies the caller is root (UID 0) inside each handler (skipped
on the session bus, development mode), mirroring the defense-in-depth `Verify`
already applied.
- **`VISAGE_SESSION_BUS=0` no longer enables session-bus mode.** Session-bus
mode *skips* D-Bus caller-UID validation (development only). The flag was read
with `env::var(..).is_ok()`, so *any* value — including `VISAGE_SESSION_BUS=0`,
the natural way to turn it off — enabled it and silently disabled UID
validation (fail-open). It now enables only on a non-empty, non-`"0"` value;
unset, empty, and `"0"` all keep the secure system-bus default.
- **Passive liveness now fails closed on insufficient landmark data.**
`check_landmark_stability` reported "live" when fewer than two landmark frames
were available (fail-open), so a match backed by only a single detectable
landmark frame bypassed the liveness gate entirely. It now returns not-live in
that case; the daemon surfaces it as a (rate-limited) non-match and the user
retries. `frames_per_verify` defaults to 3, so a live subject in normal
lighting is unaffected.

### Changed

- **Pinned `zbus` to the `tokio` executor** (`default-features = false,
features = ["tokio"]`; `pam-visage` re-adds `blocking-api`), following zbus's
own recommendation for tokio integration. `visaged` awaits tokio primitives
inside `#[zbus::interface]` handlers; on zbus's default `async-io` executor any
reactor-bound tokio call added later would panic with "no reactor running", and
a single transitive dependency could silently revert the whole process to
`async-io` via Cargo feature unification. This also removes the `async-io` /
`smol` executor stack from the dependency tree.

### Added

- **AES-256-GCM known-answer test + on-disk blob-format guard** (`visaged`).
Locks the embedding-encryption primitive against the NIST GCM test vector and
the stored blob layout (12-byte nonce ‖ ciphertext ‖ 16-byte GCM tag, with
AEAD tamper rejection), so a future `aes-gcm` upgrade cannot silently change
the on-disk format and orphan existing enrollments.

## v0.3.5 — 2026-07-07

### Added
Expand Down
173 changes: 8 additions & 165 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ members = [
]

[workspace.package]
version = "0.3.5"
version = "0.3.6"
edition = "2021"
license = "MIT"
repository = "https://github.com/sovren-software/visage"
Expand All @@ -20,8 +20,16 @@ rust-version = "1.75"
# Async runtime
tokio = { version = "1", features = ["full"] }

# D-Bus IPC
zbus = "5"
# D-Bus IPC.
# Pin to the tokio executor and disable the default `async-io` feature (zbus's
# own recommendation for tokio integration). `visaged` awaits tokio primitives
# (mpsc/oneshot/Mutex) from inside `#[zbus::interface]` handlers; leaving zbus on
# its default async-io executor makes any reactor-bound tokio call (e.g. a future
# `tokio::time`) panic with "no reactor running". Keeping this on tokio also
# prevents a transitive dep from silently reverting the whole process to async-io
# via Cargo feature unification. `pam-visage` re-adds `blocking-api` for its
# synchronous proxy (it drives zbus's internal tokio runtime via `block_on`).
zbus = { version = "5", default-features = false, features = ["tokio"] }

# Logging / tracing
tracing = "0.1"
Expand Down
6 changes: 5 additions & 1 deletion crates/pam-visage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ name = "pam_visage"
crate-type = ["cdylib"]

[dependencies]
zbus = { workspace = true }
# `blocking-api` re-enables the synchronous proxy wrappers (`VisageProxyBlocking`,
# `zbus::blocking::*`) that the workspace-level `default-features = false` drops.
# The blocking calls run on zbus's lazily-initialised internal tokio runtime, so
# the PAM module needs no ambient async runtime of its own.
zbus = { workspace = true, features = ["blocking-api"] }
libc = { workspace = true }
Loading