#!/usr/bin/env bash # Application permissions, as far as the desktop can actually enforce them. # # The rules: # # 1. The page never claims more than the portal can do. A native binary opens # /dev/video0 directly, and a settings page implying otherwise is worse # than one that says nothing -- so the limit is stated on the page, not # buried in a comment. # 2. A device nothing has asked for is reported empty, not omitted. "No # application uses your microphone" and a page that quietly leaves the # microphone out look identical and mean very different things. # 3. Absence is not failure. The store answers "No entry for microphone" for a # device nobody has requested; treating that as an error would make the # whole page fail because one device is unused. # 4. Anything that is not an explicit "yes" is withheld. Guessing generously # about a camera is the wrong way to be wrong. # 5. A refusal states its reason. # # The write path is exercised against an application id that does not exist, so # no real application's camera access is changed. What is on this machine -- # OBS Studio and GNOME Snapshot -- is read, never written. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-permissions" service="$repo_dir/config/dot/quickshell/services/Permissions.qml" page="$repo_dir/config/dot/quickshell/modules/settings/PrivacyPage.qml" probe="org.panama.ContractProbe" fail() { printf 'permissions contract: %s\n' "$1" >&2 exit 1 } for path in "$helper" "$service" "$page"; do [[ -r "$path" ]] || fail "missing $path" done [[ -x "$helper" ]] || fail 'panama-permissions is not executable' field() { python3 -c "import json,sys; print(json.load(sys.stdin)$1)"; } state="$("$helper" snapshot)" || fail 'snapshot failed' if [[ "$(printf '%s' "$state" | field "['available']")" != "True" ]]; then printf 'permissions contract: skipped (the portal permission store is not running)\n' exit 0 fi # ── 1. The page states the limit ──────────────────────────────────────────── grep -q 'directly' "$page" \ || fail 'the page does not say that programs outside the portal reach these devices anyway' # ── 2 & 3. Unused devices are present and empty, not an error ─────────────── printf '%s' "$state" | python3 -c " import json, sys state = json.load(sys.stdin) if state['error']: raise SystemExit(f\"snapshot reported an error: {state['error']}\") names = [d['id'] for d in state['devices']] for required in ('camera', 'microphone', 'speakers'): if required not in names: raise SystemExit(f'{required} is missing from the snapshot entirely') " || fail 'a device with no recorded application was dropped or reported as an error' # ── 4 & 5. The write path, on an application that does not exist ──────────── before="$(printf '%s' "$state" | field "['devices']")" denied="$("$helper" set camera "$probe" deny)" || fail 'set deny failed' reason="$(printf '%s' "$denied" | field "['error']")" [[ -z "$reason" ]] || fail "denying refused a valid write: $reason" printf '%s' "$denied" | python3 -c " import json, sys for device in json.load(sys.stdin)['devices']: for app in device['applications']: if app['app'] == '$probe': if app['allowed']: raise SystemExit('a denied application was reported as allowed') raise SystemExit(0) raise SystemExit('the denied application was not written at all') " || fail 'deny did not take effect -- the write path is not doing anything' allowed="$("$helper" set camera "$probe" allow)" || fail 'set allow failed' printf '%s' "$allowed" | python3 -c " import json, sys for device in json.load(sys.stdin)['devices']: for app in device['applications']: if app['app'] == '$probe' and app['allowed']: raise SystemExit(0) raise SystemExit('allow did not take effect') " || fail 'allow did not take effect' # Refusals name their reason rather than merely failing. reason="$(printf '%s' "$("$helper" set camera "$probe" maybe)" | field "['error']")" [[ "$reason" == *"allow or deny"* ]] \ || fail "an invalid decision was not refused with a reason (got: $reason)" reason="$(printf '%s' "$("$helper" set nonsense "$probe" allow)" | field "['error']")" [[ -n "$reason" ]] || fail 'an unknown device was accepted' # ── Put it back ──────────────────────────────────────────────────────────── "$helper" forget camera "$probe" >/dev/null || fail 'forget failed' after="$("$helper" snapshot | field "['devices']")" [[ "$before" == "$after" ]] \ || fail 'the contract changed recorded permissions and did not restore them' printf 'permissions contract: ok\n'