From 21aa9223df0e635d660ef7765182e4f5a60891ae Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Tue, 18 Aug 2026 09:30:18 -0400 Subject: [PATCH] Harden Panama health snapshots --- config/dot/quickshell/health-harness.qml | 2 + config/dot/quickshell/services/Health.qml | 55 ++++++++++++- tests/quickshell/health-service-contract.sh | 86 ++++++++++++++++++++- 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/config/dot/quickshell/health-harness.qml b/config/dot/quickshell/health-harness.qml index f1c6f8d..572e577 100644 --- a/config/dot/quickshell/health-harness.qml +++ b/config/dot/quickshell/health-harness.qml @@ -17,5 +17,7 @@ ShellRoot { function queue(): void { Health.refresh(); Health.refresh(); } function status(): string { return JSON.stringify(Health.diagnostics()); } function repair(id: string): bool { return Health.repair(id, false); } + function report(): string { return JSON.stringify(Health.snapshot, null, 2); } + function copy(): bool { return Health.copyReport(); } } } diff --git a/config/dot/quickshell/services/Health.qml b/config/dot/quickshell/services/Health.qml index 9e41771..ab5211b 100644 --- a/config/dot/quickshell/services/Health.qml +++ b/config/dot/quickshell/services/Health.qml @@ -152,10 +152,11 @@ Singleton { return false; } - root.snapshot = candidate; - root.checks = candidate.checks; - root.summary = candidate.summary; - root.status = candidate.summary.status; + const accepted = root.safeSnapshot(candidate); + root.snapshot = accepted; + root.checks = accepted.checks; + root.summary = accepted.summary; + root.status = accepted.summary.status; root.acceptedGeneration = scanGeneration; root.diagnosticUnavailable = false; root.lastError = ""; @@ -167,6 +168,52 @@ Singleton { root.lastError = message; } + function safeSnapshot(candidate: var): var { + return { + schemaVersion: 1, + generatedAt: candidate.generatedAt, + summary: { + status: candidate.summary.status, + healthy: candidate.summary.healthy, + warnings: candidate.summary.warnings, + errors: candidate.summary.errors, + unconfigured: candidate.summary.unconfigured + }, + context: { + session: candidate.context.session, + versions: candidate.context.versions.map(version => ({ + id: version.id, + version: version.version + })) + }, + checks: candidate.checks.map(check => root.safeCheck(check)) + }; + } + + function safeCheck(candidate: var): var { + const check = { + id: candidate.id, + group: candidate.group, + title: candidate.title, + status: candidate.status, + detail: candidate.detail + }; + if (candidate.action !== undefined) + check.action = root.safeAction(candidate.action); + return check; + } + + function safeAction(candidate: var): var { + const action = { + kind: candidate.kind, + label: candidate.label, + confirm: candidate.confirm + }; + if (Object.prototype.hasOwnProperty.call(candidate, "target")) + action.target = candidate.target; + return action; + } + function repair(id: string, external: bool): bool { if (root.busy) return false; diff --git a/tests/quickshell/health-service-contract.sh b/tests/quickshell/health-service-contract.sh index 7d646ce..2feeaef 100755 --- a/tests/quickshell/health-service-contract.sh +++ b/tests/quickshell/health-service-contract.sh @@ -9,7 +9,23 @@ set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" harness="$repo_dir/config/dot/quickshell/health-harness.qml" service="$repo_dir/config/dot/quickshell/services/Health.qml" +shell="$repo_dir/config/dot/quickshell/shell.qml" warning_snapshot='{"schemaVersion":1,"generatedAt":"2026-08-18T00:00:00Z","summary":{"status":"warning","healthy":0,"warnings":2,"errors":0,"unconfigured":0},"context":{"session":"hyprland","versions":[{"id":"quickshell","version":"0.3.0"}]},"checks":[{"id":"integration.calendar","group":"integrations","title":"Calendar","status":"warning","detail":"Calendar probe timed out.","action":{"kind":"open","label":"Open Date & Time","confirm":false,"target":"datetime"}},{"id":"panama.caffeine","group":"panama-tools","title":"Caffeine","status":"warning","detail":"Duplicate inhibitors are active.","action":{"kind":"repair","label":"Release duplicate inhibitors","confirm":false}}]}' +projection_snapshot="$(jq -c ' + .fixtureSecret = "fixture-secret" + | .summary.fixtureSecret = "fixture-secret" + | .context.fixtureSecret = "fixture-secret" + | .context.versions[0].fixtureSecret = "fixture-secret" + | .checks[0].fixtureSecret = "fixture-secret" +' <<<"$warning_snapshot")" +adversarial_snapshot="$(jq -c ' + .fixtureSecret = "fixture-secret" + | .summary.fixtureSecret = "fixture-secret" + | .context.fixtureSecret = "fixture-secret" + | .context.versions[0].fixtureSecret = "fixture-secret" + | .checks[0].fixtureSecret = "fixture-secret" + | .checks[0].action.fixtureSecret = "fixture-secret" +' <<<"$warning_snapshot")" fail() { printf 'health service contract: %s\n' "$1" >&2 @@ -18,9 +34,47 @@ fail() { [[ -f "$service" ]] || fail 'Health.qml is missing' [[ -f "$harness" ]] || fail 'health harness is missing' +[[ -f "$shell" ]] || fail 'shell.qml is missing' + +# shell.qml is not started here: it is the active desktop shell. Keep this +# contract static while pinning the typed, redacted IPC boundary it exports. +python3 - "$shell" <<'PY' || fail 'health IPC contract is missing or exposes unsafe state' +import re +import sys + +text = open(sys.argv[1], encoding="utf-8").read() +match = re.search(r'IpcHandler \{\s*target: "health"(?P.*?)\n \}', text, re.S) +if not match: + raise SystemExit(1) +body = match.group("body") +required = ( + 'function refresh(): bool { return Health.refresh(); }', + 'function status(): string {', + 'summary: Health.summary,', + 'busy: Health.busy,', + 'generation: Health.generation,', + 'acceptedGeneration: Health.acceptedGeneration,', + 'checks: Health.checks.map(check => ({ id: check.id, status: check.status }))', + 'ShellState.openSettings("services");', + 'Health.refresh();', + 'function repair(id: string): bool { return Health.repair(id, true); }', +) +if any(entry not in body for entry in required): + raise SystemExit(1) +if 'Health.snapshot' in body or 'Health.diagnostics' in body: + raise SystemExit(1) +status = re.search(r'function status\(\): string \{\s*return JSON\.stringify\(\{(?P.*?)\n \}\);', body, re.S) +if not status: + raise SystemExit(1) +keys = re.findall(r'^\s*([A-Za-z][A-Za-z0-9]*):', status.group("fields"), re.M) +if keys != ["summary", "busy", "generation", "acceptedGeneration", "checks"]: + raise SystemExit(1) +PY fixture_dir="$(mktemp -d /tmp/panama-health.XXXXXX)" helper="$fixture_dir/panama-doctor" +copy_bin="$fixture_dir/bin" +copy_file="$fixture_dir/copied-report.json" printf '%s\n' \ '#!/usr/bin/env bash' \ 'if [[ "$1" == "--json" ]]; then' \ @@ -33,8 +87,13 @@ printf '%s\n' \ 'fi' \ 'exit 2' >"$helper" chmod +x "$helper" +mkdir -p "$copy_bin" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + '/usr/bin/cat > "$PANAMA_HEALTH_COPY_FILE"' >"$copy_bin/wl-copy" +chmod +x "$copy_bin/wl-copy" -run() { PANAMA_HEALTH_HELPER="$helper" qs -p "$harness" "$@"; } +run() { PATH="$copy_bin:$PATH" PANAMA_HEALTH_HELPER="$helper" PANAMA_HEALTH_COPY_FILE="$copy_file" qs -p "$harness" "$@"; } harness_pid="" cleanup() { @@ -43,7 +102,8 @@ cleanup() { } trap cleanup EXIT -PANAMA_HEALTH_HELPER="$helper" qs -p "$harness" --daemonize >/dev/null +PATH="$copy_bin:$PATH" PANAMA_HEALTH_HELPER="$helper" PANAMA_HEALTH_COPY_FILE="$copy_file" \ + qs -p "$harness" --daemonize >/dev/null for _ in $(seq 1 40); do run ipc show 2>/dev/null | rg -q '^target health-test$' && break sleep 0.1 @@ -57,6 +117,28 @@ state="$(run ipc call health-test status)" jq -e '.status == "warning" and .acceptedGeneration == 0 and .checks == ["integration.calendar", "panama.caffeine"] and .diagnosticUnavailable == false' \ >/dev/null <<<"$state" || fail "valid warning snapshot was not accepted intact: $state" +[[ "$(run ipc call health-test accept "$projection_snapshot" 0)" == "true" ]] \ + || fail 'snapshot with unknown non-action fields was rejected instead of safely projected' +stored_report="$(run ipc call health-test report)" +! grep -Fq 'fixture-secret' <<<"$stored_report" \ + || fail "accepted snapshot retained an unknown secret field: $stored_report" +[[ "$(run ipc call health-test copy)" == "true" ]] \ + || fail 'copy report was refused' +for _ in $(seq 1 40); do + [[ -f "$copy_file" ]] && break + sleep 0.1 +done +[[ -f "$copy_file" ]] || fail 'copy report did not reach wl-copy' +! grep -Fq 'fixture-secret' "$copy_file" \ + || fail 'copied report retained an unknown secret field' + +adversarial_result="$(run ipc call health-test accept "$adversarial_snapshot" 1)" +[[ "$adversarial_result" == "true" || "$adversarial_result" == "false" ]] \ + || fail "adversarial action fixture did not return a Boolean: $adversarial_result" +stored_report="$(run ipc call health-test report)" +! grep -Fq 'fixture-secret' <<<"$stored_report" \ + || fail "adversarial snapshot leaked an unknown secret field: $stored_report" + [[ "$(run ipc call health-test accept "$warning_snapshot" -1)" == "false" ]] \ || fail 'older generation replaced the current snapshot' state="$(run ipc call health-test status)"