Skip to content

security: scope polkit face auth to an allowlist and fail closed on unresolved user (Plan 03)#84

Open
tyvsmith wants to merge 3 commits into
mainfrom
sec/03-polkit-hardening
Open

security: scope polkit face auth to an allowlist and fail closed on unresolved user (Plan 03)#84
tyvsmith wants to merge 3 commits into
mainfrom
sec/03-polkit-hardening

Conversation

@tyvsmith

@tyvsmith tyvsmith commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Stops a single face match from authorizing every polkit action, and stops the fail-open-to-root on an unresolvable username (Plan 03, findings F1 and F2).

  • Action allowlist (F1): the agent now offers face auth only for actions in a configurable allowlist polkit.face_eligible_actions (default ["org.freedesktop.login1.lock-sessions"]). High-risk actions (pkexec, PackageKit install/remove, udisks mount, accounts-service user admin) are excluded by default. A non-allowlisted action is declined (Failed) so polkit falls through to the password dialog — a fall-through, never a denial. Users may extend the list; an empty list disables face entirely.
  • Fail closed on unresolved user (F2): respond_to_polkit resolves the target name to a uid and refuses if it does not resolve, instead of substituting UID 0 (which previously authenticated an unresolvable name as root).

Verification

  • cargo build / cargo test / cargo clippy -D warnings green; unit test added for the respond_to_polkit uid-resolution branch (unresolved name → refusal, never UID 0).
  • Container: test/polkit-agent-validate.sh (busctl-driven) asserts at the agent's D-Bus boundary that it declines a non-allowlisted action_id and accepts an allowlisted one. just test-deb-pkg and just test-rpm-pkg both ship the agent binary and pass 24/24 (rpm gains dbus-daemon so the runtime D-Bus block runs on Fedora). No camera required.

Contract docs

docs/contracts.md (new [polkit] key + Polkit Agent Semantics) and docs/security.md (agent scoping — face is now scoped, not universal); config/facelock.toml sample updated.

Closes #65
Closes #66

🤖 Generated with Claude Code

…on unresolved user

Closes polkit findings F1 (no action allowlist) and F2 (UID 0 fail-open).

A single face match previously authorized *every* polkit action — pkexec,
package install, disk mount, user admin. Now the agent offers face auth only
for actions in a configurable allowlist (`polkit.face_eligible_actions`,
default `["org.freedesktop.login1.lock-sessions"]`); high-risk actions are
excluded by default. A non-allowlisted action is declined (Failed) so polkit
falls through to the password dialog — a fall-through, never a denial. Users
may extend the list; an empty list disables face entirely.

Fail closed on unresolved username: `respond_to_polkit` resolves the target
name to a uid and refuses if it does not resolve, instead of substituting
UID 0 (which authenticated an unresolvable name as root).

Container proof: pkg-validate.sh gains a busctl-driven assertion
(test/polkit-agent-validate.sh) that the agent declines a non-allowlisted
action_id and accepts an allowlisted one at its D-Bus boundary. Both
test-deb-pkg and test-rpm-pkg now ship the agent binary and pass 24/24;
rpm gains dbus-daemon so the runtime D-Bus block runs on Fedora.

Docs: docs/contracts.md (new [polkit] key + Polkit Agent Semantics),
docs/security.md (agent scoping section), config/facelock.toml sample.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 20:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the facelock-polkit-agent so a successful face match is no longer a universal authorization mechanism for arbitrary polkit actions, and it removes a fail-open-to-root behavior when the target username cannot be resolved.

Changes:

  • Introduces a configurable polkit action_id allowlist (polkit.face_eligible_actions) and declines non-allowlisted actions to force password fall-through.
  • Changes polkit response behavior to fail closed when a username cannot be resolved to a UID (never substitutes UID 0).
  • Adds container-level validation for the agent’s D-Bus boundary behavior and updates docs/sample config accordingly.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/polkit-agent-validate.sh Adds a container-friendly D-Bus boundary validation script for allowlist behavior.
test/pkg-validate.sh Hooks the new polkit-agent boundary check into package validation.
test/Containerfile.rpm-e2e Ensures the RPM E2E container includes dbus-daemon and ships the polkit agent binary + validation script.
test/Containerfile.deb-e2e Ships the polkit agent binary + validation script into the DEB E2E container.
docs/security.md Documents the new polkit agent scoping and fail-closed UID resolution behavior.
docs/contracts.md Extends config/contracts to include [polkit].face_eligible_actions and defines agent semantics.
crates/facelock-polkit/src/main.rs Implements allowlist gating, fail-closed UID resolution, and a test-mode session-bus name hook.
crates/facelock-core/src/config.rs Adds PolkitConfig to core config with defaults and tests for allowlist behavior.
config/facelock.toml Updates sample config with the new [polkit] section and guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +50
FAIL=0
BUS_SOCK="$(mktemp -u /tmp/facelock-polkit-session-bus.XXXXXX)"
SESSION_ADDR="unix:path=${BUS_SOCK}"
AGENT_PID=""
BUS_PID=""

cleanup() {
[ -n "$AGENT_PID" ] && kill "$AGENT_PID" 2>/dev/null || true
[ -n "$BUS_PID" ] && kill "$BUS_PID" 2>/dev/null || true
rm -f "$BUS_SOCK"
}
Comment on lines +58 to +62
# Wait for the session bus socket.
for _ in $(seq 1 50); do
[ -S "$BUS_SOCK" ] && break
sleep 0.1
done
tyvsmith and others added 2 commits July 4, 2026 14:04
cargo fmt --all -- --check was failing CI on unwrapped long assert!
lines added by the polkit allowlist tests. Run cargo fmt --all to
wrap them per rustfmt's max-width rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two independent reviewers flagged that docs/security.md and
docs/contracts.md overstated polkit agent behavior: they claimed a
non-allowlisted action "falls through to the password dialog handled
by another agent." polkit registers a single authentication agent per
session and does not chain agents, so a decline from this agent likely
produces an authorization denial rather than a fallthrough, depending
on the desktop's agent registration. This is fail-closed (safe) but
changes the UX story and has not been verified on a live desktop.

Replace the fallthrough claims with an explicit "under review" caveat
in both docs (including the contracts.md outcome table and the
security.md polkit config example) instead of asserting either
behavior as final. Surrounding allowlist documentation is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Polkit agent fails open to UID 0 when the username is unresolvable (F2) Polkit agent authorizes every polkit action on one face match (F1)

2 participants