#!/usr/bin/env bash # The Sharing page must report what is true, and never claim a security # property it cannot back up. # # The two failures worth a test: # # A service that is not installed shown as a switch. That is what the panel # this replaces does, and the switch does nothing. # # "Keys only" claimed for SSH when the configuration is silent. OpenSSH's # default accepts passwords, so stating the stronger thing without evidence # would tell someone their machine is safer than it is. # # Read-only: this reads service state and never enables or disables anything. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-sharing" service="$repo_dir/config/dot/quickshell/services/Sharing.qml" page="$repo_dir/config/dot/quickshell/modules/settings/SharingPage.qml" fail() { printf 'sharing contract: %s\n' "$1" >&2 exit 1 } for path in "$helper" "$service" "$page"; do [[ -r "$path" ]] || fail "missing $path" done [[ -x "$helper" ]] || fail 'panama-sharing is not executable' # ── The password-authentication claim is evidence-based ───────────────────── summary="$(sed -n '/function passwordLoginSummary/,/^ }/p' "$service")" [[ -n "$summary" ]] || fail 'the service does not summarize password sign-in' grep -q 'stated === ""' <<<"$summary" \ || fail 'the summary does not distinguish "configured" from "silent"' grep -qE 'system default' <<<"$summary" \ || fail 'a silent configuration is not reported as the system default' # "Keys only" may only be said when the file actually says no. keys_line="$(grep -n 'keys only' <<<"$summary" | head -1)" [[ -n "$keys_line" ]] || fail 'the summary never reports keys-only' grep -q 'toLowerCase() === "no"' <<<"$summary" \ || fail 'keys-only is claimed without checking what the configuration says' # ── Absent software is reported, not offered ──────────────────────────────── grep -q 'is not installed' "$page" \ || fail 'the page does not say when the software for a row is missing' # A switch for a service that is not installed must be disabled. grep -q 'Sharing.remoteLogin?.installed === true' "$page" \ || fail 'the remote login switch is enabled regardless of whether SSH is installed' grep -q 'Sharing.remoteDesktop?.available === true' "$page" \ || fail 'the remote desktop switch is enabled regardless of whether it is available' # Turning on remote desktop without credentials would start a service nobody # can connect to; the page must require them first. grep -q 'hasCredentials === true' "$page" \ || fail 'remote desktop can be enabled with no credentials set' grep -q 'hasCredentials' "$helper" \ || fail 'the helper does not know whether credentials exist' grep -qiE 'grdctl.*(password|username)[^)]*\)' "$helper" \ && fail 'the helper passes remote desktop credentials on a command line' # ── Privilege boundaries ──────────────────────────────────────────────────── # Remote login is system-wide and must go through a prompt; remote desktop is a # user service and must not ask for one. login_body="$(sed -n '/^def set_remote_login/,/^def /p' "$helper")" grep -q 'pkexec' <<<"$login_body" \ || fail 'changing a system-wide service does not ask for authorization' desktop_body="$(sed -n '/^def set_remote_desktop/,/^def /p' "$helper")" grep -q 'pkexec' <<<"$desktop_body" \ && fail 'a user service asks for administrator rights it does not need' grep -q '"--user"' <<<"$desktop_body" \ || fail 'remote desktop is not managed as a user service' # ── The remote desktop password never passes through Panama ──────────────── # grdctl takes it on a terminal and core-dumps without one, so the only two # options were a terminal hand-off or an argument -- and an argument publishes # it through /proc to every process on this machine. grep -q 'set-credentials' "$service" \ || fail 'the service cannot set remote desktop credentials at all' grep -qE 'set-credentials".*(password|secret)' "$service" \ && fail 'the service puts a password on the command line' grep -q 'set-credentials' "$helper" \ && fail 'the helper handles credentials; that path cannot prompt and must stay in a terminal' grep -q 'kitty' "$service" \ || fail 'credentials are not handed to a terminal, so nothing can prompt for them' grep -q 'clear-rdp-credentials' "$helper" \ || fail 'stored credentials cannot be cleared' # ── The page says the true thing in the right place ───────────────────────── # # Three copy rules, each of which was a real failure before it was a rule. # # The failure banner floated above every card as a full-width red bar, so a # grdctl error that concerned one row repainted the whole page as broken. It # belongs inside the card whose action failed -- the machine card, which is # where refresh and the hostname live. page_code="$(grep -vE '^\s*//' "$page")" python3 - "$page" <<'PY' || fail 'the failure message is not inside the first card, so one row failing reads as the page failing' import re import sys lines = open(sys.argv[1], encoding="utf-8").read().splitlines() start = next((i for i, line in enumerate(lines) if re.search(r"\bSettingsCard\s*\{", line)), None) if start is None: raise SystemExit(1) depth = 0 end = None for index in range(start, len(lines)): depth += lines[index].count("{") - lines[index].count("}") if depth <= 0: end = index break if end is None: raise SystemExit(1) card = "\n".join(lines[start:end + 1]) raise SystemExit(0 if "Sharing.lastError" in card else 1) PY # The terminal is the mechanism, not the explanation. "Opens kitty" tells # somebody the name of a program they did not ask about and still leaves them # wondering why a settings page cannot take a password; the reason it cannot is # the sentence worth printing. ! grep -qi 'kitty' <<<"$page_code" \ || fail 'the page names the terminal application in text the user reads; that belongs to the service' grep -q 'never passes through Panama' <<<"$page_code" \ || fail 'the credentials row does not say why the password is set elsewhere' # A row for software that is not here has to be worth reading. "Not installed" # on its own is a dead end; what installing it would give you is not. grep -q 'install it and this becomes a switch' <<<"$page_code" \ || fail 'the file sharing row does not say what installing Samba would unlock' grep -q 'does not install software' <<<"$page_code" \ || fail 'the file sharing row does not say that Settings will not install it for you' # ── The snapshot reflects the machine ─────────────────────────────────────── command -v jq >/dev/null 2>&1 || { printf 'sharing contract: SKIP (no jq)\n'; exit 0; } snapshot="$("$helper" snapshot 2>/dev/null)" || fail 'snapshot failed' jq -e '.hostname | length > 0' <<<"$snapshot" >/dev/null || fail 'no hostname reported' jq -e '.remoteLogin | has("installed") and has("active") and has("enabled")' <<<"$snapshot" >/dev/null \ || fail 'remote login state is incomplete' jq -e '.remoteDesktop | has("available") and has("hasCredentials")' <<<"$snapshot" >/dev/null \ || fail 'remote desktop state is incomplete' # Installed-ness must match what is actually on this machine, not a guess. expected_samba=$(command -v smbd >/dev/null 2>&1 && echo true || echo false) actual_samba="$(jq -r '.fileSharing.installed' <<<"$snapshot")" [[ "$expected_samba" == "$actual_samba" ]] \ || fail "file sharing reports installed=$actual_samba but smbd presence is $expected_samba" # No credential may appear in the snapshot. offenders="$(jq -r '[paths | map(tostring) | join(".")] | map(select(test("(password|secret|credential)$";"i"))) | join(", ")' <<<"$snapshot")" [[ -z "$offenders" ]] || fail "the snapshot carries credential-shaped fields: $offenders" printf 'sharing contract: PASS (remote login %s, remote desktop %s)\n' \ "$(jq -r 'if .remoteLogin.active then "on" else "off" end' <<<"$snapshot")" \ "$(jq -r 'if .remoteDesktop.active then "on" else "off" end' <<<"$snapshot")"