Files
Panama/tests/quickshell/polkit-agent-contract
T
Gabriel Brown e1faaf7a76 Drop the extension, and give the test suite a front door
Phase 6, the last of the fresh-install spec.

159 scripts lose their .sh: 110 contracts, 47 Vicinae commands, 2 compositor
contracts. A shebang and the executable bit already select the interpreter. The
extension only ever added something that had to stay in sync, and the rename
proved the point twice over in the space of an hour.

The spec's stated risk was Vicinae's script discovery. One script was renamed and
reloaded on its own before the other 46 followed; it came back as
scripts:panama.capture and all 47 resolve. What the probe turned up instead is
that the extension was never only a filename: Vicinae's command IDs embed it, so
every ID changed. Nothing in this repository refers to them, so nothing breaks.
The only trace is Vicinae's metadata.json, whose visited map had two Panama
entries that are now orphaned -- two commands lost their usage ranking and will
earn it back. Worth knowing before anyone renames these again on a machine that
has a keybind pointing at one.

Rewriting the references by exact filename missed two things it structurally
could not see: a name built from a variable, settings-$page.sh, and a glob,
-name '*.sh'. Both were in the contract that counts the generated commands, which
promptly reported 47 expected and 0 found. The mechanical part of a rename is the
part that looks finished.

The three subcommands. panama doctor fronts a health check that already existed
and already ran at the end of every install but could not be reached from a
terminal. panama upgrade re-runs the installer from anywhere. panama test runs
the suite, which had no entry point at all -- 121 files that were the main safety
net in this repository and were invisible in it.

Writing that runner found three tests nothing was running.
calendar_agenda_bridge_test, home_assistant_bridge_test and kdeconnect_bridge_test
are unittest suites without the executable bit, so no contract invoked them and
the first draft of the runner skipped them silently. All three pass, and have
passed unobserved for weeks. The runner collects *_test.py as well now, because a
runner with a blind spot is worse than no runner for the same reason a dependency
checker with one is: it reports PASS.

Six worktrees pruned. Each was re-checked rather than trusted to the spec's list,
and two needed it: panama-commands is not on feat/panama-commands but on
feat/gnome-tweaks-parity, and fix/panama-displays-review reads [ahead 3] -- ahead
of its remote, not of main, with every commit patch-equivalent to landed work.
roadmap-completion stays; it has five commits that are genuinely unlanded. The
branches are left alone: pruning a worktree costs nothing, deleting a branch is a
decision.

121 contracts pass.

Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
2026-08-20 21:55:55 -04:00

111 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# The authentication prompt asks for the password to everything, so the rules
# here are not style, they are the reason it was safe to write at all.
#
# 1. The agent process must never handle a password. It talks to polkitd and
# hands the shell a request; the shell talks to the setuid helper.
# 2. The password reaches the helper on STDIN and nowhere else. argv is
# world-readable through /proc, so an argument is a broadcast.
# 3. The prompt takes EXCLUSIVE keyboard focus. A password field that lets
# keystrokes through to the window behind it is a keylogger with extra
# steps.
# 4. The request file carries a one-time capability and is created 0600.
# 5. The stock agent stays installed. Only one agent may register per session,
# so a broken replacement must have something to fall back to.
#
# Static, plus a live check of the runtime directory. It never authenticates.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
agent="$repo_dir/config/dot/quickshell/scripts/panama-polkit-agent"
service="$repo_dir/config/dot/quickshell/services/Polkit.qml"
prompt="$repo_dir/config/dot/quickshell/modules/polkit/PolkitPrompt.qml"
autostart="$repo_dir/config/dot/hypr/autostart.lua"
fail() {
printf 'polkit agent contract: %s\n' "$1" >&2
exit 1
}
for path in "$agent" "$service" "$prompt" "$autostart"; do
[[ -r "$path" ]] || fail "missing $path"
done
[[ -x "$agent" ]] || fail 'the agent is not executable'
# ── 1. The agent never touches a password ───────────────────────────────────
# Comments and docstrings are stripped first: this file EXPLAINS at length why
# it must not touch a password, and an earlier version of this check failed on
# the explanation rather than on any behaviour.
agent_code="$(python3 - "$agent" <<'STRIP'
import ast, sys
source = open(sys.argv[1], encoding="utf-8").read()
tree = ast.parse(source)
# Drop every docstring, then print what is left as code.
for node in ast.walk(tree):
if isinstance(node, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
if (node.body and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.Constant)
and isinstance(node.body[0].value.value, str)):
node.body.pop(0)
print(ast.unparse(tree))
STRIP
)"
[[ -n "$agent_code" ]] || fail 'could not read the agent source'
grep -qE 'polkit-agent-helper|PAM_PROMPT|password' <<<"$agent_code" \
&& fail 'the agent process handles passwords; that belongs on the shell side only'
# ── 2. The password goes to the helper on stdin ─────────────────────────────
grep -q 'stdinEnabled: true' "$service" \
|| fail 'the helper is spawned without a writable stdin, so the password has nowhere to go'
grep -q 'helper.write(root.pendingSecret' "$service" \
|| fail 'the password is not written to the helper stdin'
# It must never appear in the argument list.
helper_command="$(grep -n 'helper.command' "$service" || true)"
grep -qE 'pendingSecret|password' <<<"$helper_command" \
&& fail 'the password appears in the helper command line'
grep -q 'root.pendingSecret = "";' "$service" \
|| fail 'the password is never cleared after being handed over'
# ── 3. The prompt owns the keyboard ─────────────────────────────────────────
grep -q 'WlrKeyboardFocus.Exclusive' "$prompt" \
|| fail 'the prompt does not take exclusive keyboard focus'
grep -q 'field.text = ""' "$prompt" \
|| fail 'the password field is not cleared when the prompt closes'
# ── 4. The request file is private ──────────────────────────────────────────
grep -q '0o600' "$agent" || fail 'the request file is not created 0600'
grep -q '0o700' "$agent" || fail 'the runtime directory is not private'
grep -qE 'O_CREAT \| os\.O_EXCL' "$agent" \
|| fail 'the request file is not created exclusively, so it could be pre-created by someone else'
# ── 5. A fallback exists ────────────────────────────────────────────────────
grep -q 'hyprpolkitagent' "$autostart" \
|| fail 'nothing records how to get the stock agent back if this one fails'
command -v rpm >/dev/null 2>&1 && {
rpm -q hyprpolkitagent >/dev/null 2>&1 \
|| fail 'the stock agent is no longer installed, so there is nothing to fall back to'
}
# Only one agent may be started by the session. Lua comments are excluded: the
# file documents how to restore the stock agent, and that sentence is not a
# command the session runs.
started="$(grep -vE '^\s*--' "$autostart" | grep -E 'systemctl --user start .*polkit' | head -1)"
[[ -n "$started" ]] || fail 'the session starts no polkit agent at all'
grep -q 'panama-polkit-agent' <<<"$started" \
|| fail 'the session does not start the Panama agent'
grep -q 'hyprpolkitagent.service' <<<"$started" \
&& fail 'the session starts both agents; the second to register will fail'
# ── Live: the runtime directory is private, if it exists ────────────────────
runtime="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/panama-polkit"
if [[ -d "$runtime" ]]; then
mode="$(stat -c '%a' "$runtime")"
[[ "$mode" == "700" ]] || fail "the runtime directory is mode $mode, not 700"
leaked="$(find "$runtime" -type f ! -perm 600 2>/dev/null | head -1)"
[[ -z "$leaked" ]] || fail "a request file is readable beyond its owner: $leaked"
fi
printf 'polkit agent contract: PASS (password never leaves the shell, prompt owns the keyboard, fallback intact)\n'