Harden Panama health snapshots
This commit is contained in:
@@ -17,5 +17,7 @@ ShellRoot {
|
|||||||
function queue(): void { Health.refresh(); Health.refresh(); }
|
function queue(): void { Health.refresh(); Health.refresh(); }
|
||||||
function status(): string { return JSON.stringify(Health.diagnostics()); }
|
function status(): string { return JSON.stringify(Health.diagnostics()); }
|
||||||
function repair(id: string): bool { return Health.repair(id, false); }
|
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(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,10 +152,11 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.snapshot = candidate;
|
const accepted = root.safeSnapshot(candidate);
|
||||||
root.checks = candidate.checks;
|
root.snapshot = accepted;
|
||||||
root.summary = candidate.summary;
|
root.checks = accepted.checks;
|
||||||
root.status = candidate.summary.status;
|
root.summary = accepted.summary;
|
||||||
|
root.status = accepted.summary.status;
|
||||||
root.acceptedGeneration = scanGeneration;
|
root.acceptedGeneration = scanGeneration;
|
||||||
root.diagnosticUnavailable = false;
|
root.diagnosticUnavailable = false;
|
||||||
root.lastError = "";
|
root.lastError = "";
|
||||||
@@ -167,6 +168,52 @@ Singleton {
|
|||||||
root.lastError = message;
|
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 {
|
function repair(id: string, external: bool): bool {
|
||||||
if (root.busy)
|
if (root.busy)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -9,7 +9,23 @@ set -euo pipefail
|
|||||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
harness="$repo_dir/config/dot/quickshell/health-harness.qml"
|
harness="$repo_dir/config/dot/quickshell/health-harness.qml"
|
||||||
service="$repo_dir/config/dot/quickshell/services/Health.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}}]}'
|
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() {
|
fail() {
|
||||||
printf 'health service contract: %s\n' "$1" >&2
|
printf 'health service contract: %s\n' "$1" >&2
|
||||||
@@ -18,9 +34,47 @@ fail() {
|
|||||||
|
|
||||||
[[ -f "$service" ]] || fail 'Health.qml is missing'
|
[[ -f "$service" ]] || fail 'Health.qml is missing'
|
||||||
[[ -f "$harness" ]] || fail 'health harness 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<body>.*?)\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<fields>.*?)\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)"
|
fixture_dir="$(mktemp -d /tmp/panama-health.XXXXXX)"
|
||||||
helper="$fixture_dir/panama-doctor"
|
helper="$fixture_dir/panama-doctor"
|
||||||
|
copy_bin="$fixture_dir/bin"
|
||||||
|
copy_file="$fixture_dir/copied-report.json"
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'#!/usr/bin/env bash' \
|
'#!/usr/bin/env bash' \
|
||||||
'if [[ "$1" == "--json" ]]; then' \
|
'if [[ "$1" == "--json" ]]; then' \
|
||||||
@@ -33,8 +87,13 @@ printf '%s\n' \
|
|||||||
'fi' \
|
'fi' \
|
||||||
'exit 2' >"$helper"
|
'exit 2' >"$helper"
|
||||||
chmod +x "$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=""
|
harness_pid=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@@ -43,7 +102,8 @@ cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT
|
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
|
for _ in $(seq 1 40); do
|
||||||
run ipc show 2>/dev/null | rg -q '^target health-test$' && break
|
run ipc show 2>/dev/null | rg -q '^target health-test$' && break
|
||||||
sleep 0.1
|
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' \
|
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"
|
>/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" ]] \
|
[[ "$(run ipc call health-test accept "$warning_snapshot" -1)" == "false" ]] \
|
||||||
|| fail 'older generation replaced the current snapshot'
|
|| fail 'older generation replaced the current snapshot'
|
||||||
state="$(run ipc call health-test status)"
|
state="$(run ipc call health-test status)"
|
||||||
|
|||||||
Reference in New Issue
Block a user