#!/usr/bin/env bash

# A stored password must never leave the keyring except onto the clipboard,
# on purpose, one at a time.
#
# Four rules, each a way this feature could leak what it exists to protect:
#
#   1. Listing secrets must not read them. Enumerating the keyring reports
#      labels and attributes; it never asks the keyring for a value.
#   2. A secret must never reach a command line. /proc makes argv readable by
#      every process on the machine, so a password passed as an argument is
#      published to all of them. It goes on stdin or not at all.
#   3. A secret must never reach an error message, a log, or the settings page.
#      An exception raised while holding a password does not get to choose what
#      text is printed.
#   4. Forgetting one is irreversible, so the page confirms first.
#
# Read-only. It lists the real keyring -- which is safe, because listing is the
# thing being verified as safe -- and never copies, deletes, or unlocks.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
helper="$repo_dir/config/dot/quickshell/scripts/panama-keyring"
service="$repo_dir/config/dot/quickshell/services/Keyring.qml"
page="$repo_dir/config/dot/quickshell/modules/settings/PrivacyPage.qml"

fail() {
    printf 'secrets contract: %s\n' "$1" >&2
    exit 1
}

for path in "$helper" "$service" "$page"; do
    [[ -r "$path" ]] || fail "missing $path"
done
[[ -x "$helper" ]] || fail 'panama-keyring is not executable'

# ── 1. Listing does not read values ──────────────────────────────────────────
summary_body="$(sed -n '/^def item_summary/,/^def /p' "$helper")"
[[ -n "$summary_body" ]] || fail 'item_summary is missing'
grep -qE 'load_secret|get_secret|get_text' <<<"$summary_body" \
    && fail 'the item summary reads secret values; enumerating must never require the value'

report_body="$(sed -n '/^def items_report/,/^def /p' "$helper")"
grep -qE 'load_secret|get_secret' <<<"$report_body" \
    && fail 'the item report reads secret values'

# ── 2. No secret on a command line ───────────────────────────────────────────
copy_body="$(sed -n '/^def copy_secret/,/^def /p' "$helper")"
[[ -n "$copy_body" ]] || fail 'copy_secret is missing'
grep -q 'input=encoded' <<<"$copy_body" \
    || fail 'the secret does not reach the clipboard tool on stdin'
# The value must not appear inside any argument list.
grep -qE '\[.*(secret|encoded).*\]' <<<"$copy_body" \
    && fail 'the secret appears inside a command argument list, which publishes it through /proc'
grep -q 'digest = hashlib.sha256' <<<"$copy_body" \
    || fail 'the clipboard is cleared without comparing a hash, so it either holds the secret or clears the wrong thing'
grep -qE 'Popen\(.*secret|Popen\(.*encoded' <<<"$copy_body" \
    && fail 'the clipboard-clearing process is handed the secret'

# ── 3. No secret in output ───────────────────────────────────────────────────
grep -qE '^\s*print\((secret|value|encoded)' <<<"$copy_body" \
    && fail 'the secret is printed'
grep -q 'completed.stderr' <<<"$copy_body" \
    && fail "the clipboard tool's stderr is echoed while a secret is in hand"

# The service must not hold one either.
grep -qiE 'property (string|var) (secret|password|value)\b' "$service" \
    && fail 'the Keyring service declares a property that would hold a secret value'

# ── 4. Forgetting is confirmed ───────────────────────────────────────────────
grep -q 'confirmingPath' "$page" \
    || fail 'the page deletes a stored secret without a confirmation step'
grep -q 'Keyring.forget(' "$page" \
    || fail 'the page cannot forget a secret at all'
# The guard itself, not merely the word "confirming" somewhere nearby: the
# button's own label and tone both mention it, so proximity proves nothing.
# What must exist is the early return that turns the FIRST press into a request
# for confirmation rather than a deletion.
grep -q 'if (!secretRow.confirming)' "$page" \
    || fail 'the first press on Forget is not turned into a confirmation step'
grep -q 'root.confirmingPath = secretRow.itemPath;' "$page" \
    || fail 'nothing records which item is awaiting confirmation'

# Opening the page must not enumerate anyone's passwords as a side effect.
grep -qE 'Component.onCompleted:.*Keyring.list\(\)' "$page" \
    && fail 'the page lists stored secrets when it opens rather than when asked'

# ── The list itself, on the real keyring ─────────────────────────────────────
command -v jq >/dev/null 2>&1 || { printf 'secrets contract: SKIP (no jq)\n'; exit 0; }
listing="$("$helper" items 2>/dev/null)" || fail 'listing stored secrets failed'
jq -e '.collections | type == "array"' <<<"$listing" >/dev/null \
    || fail 'the listing has no collections'

# No field anywhere in the payload may be named like a value.
offenders="$(jq -r '[paths | map(tostring) | join(".")] | map(select(test("(secret|password|value|token)$";"i"))) | join(", ")' <<<"$listing")"
[[ -z "$offenders" ]] || fail "the listing carries value-shaped fields: $offenders"

# Every item reports where it came from, so a row can be identified without it.
jq -e '[.collections[].items[] | (.path | startswith("/org/freedesktop/secrets/")) and (.label | length > 0)] | all' \
    <<<"$listing" >/dev/null || fail 'an item is missing its path or label'

# ── Refusals ─────────────────────────────────────────────────────────────────
# A refused path must not reach the clipboard at all, which is checked by
# stubbing the clipboard tool rather than inferred from an exit code -- the
# helper answers with the item list plus an error field, so its exit status is
# deliberately 0 even when it refuses.
work="$(mktemp -d /tmp/panama-secrets.XXXXXX)"
trap 'rm -rf "$work"' EXIT
mkdir -p "$work/bin"
cat >"$work/bin/wl-copy" <<'STUB'
#!/usr/bin/env bash
printf 'called: %s\n' "$*" >>"$PANAMA_SECRETS_CALL_LOG"
cat >>"$PANAMA_SECRETS_CALL_LOG"
STUB
chmod +x "$work/bin/wl-copy"
export PANAMA_SECRETS_CALL_LOG="$work/calls"
: >"$PANAMA_SECRETS_CALL_LOG"

refusal() {
    PATH="$work/bin:$PATH" "$helper" "$@" 2>/dev/null | jq -r '.error // ""'
}

[[ -n "$(refusal copy /etc/passwd)" ]] \
    || fail 'copy accepted a path that is not a stored secret'
[[ -n "$(refusal copy ../../etc/passwd)" ]] \
    || fail 'copy accepted a relative path'
[[ -n "$(refusal copy /org/freedesktop/secrets/collection/login)" ]] \
    || fail 'copy accepted a collection path rather than an item'
[[ -n "$(refusal forget /org/freedesktop/secrets/collection/login)" ]] \
    || fail 'forget accepted a collection path, which would delete a whole keyring'
[[ -n "$(refusal copy /org/freedesktop/secrets/collection/login/999999)" ]] \
    || fail 'copy accepted an item that does not exist'

[[ ! -s "$PANAMA_SECRETS_CALL_LOG" ]] \
    || fail 'a refused request still reached the clipboard'

PATH="$work/bin:$PATH" "$helper" copy >/dev/null 2>&1 \
    && fail 'copy with no argument was accepted'
[[ ! -s "$PANAMA_SECRETS_CALL_LOG" ]] \
    || fail 'a malformed request still reached the clipboard'

printf 'secrets contract: PASS (%d items listed, none readable from the listing)\n' \
    "$(jq '[.collections[].items[]] | length' <<<"$listing")"
