#!/usr/bin/env bash # The production change that breaks these cases is accepting an unverified # download, a wrong signer, or executable/malformed provenance data. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" fixtures="$repo_dir/tests/setup/fixtures/provenance" config="$repo_dir/setup/provenance/installers.conf" provenance_readme="$repo_dir/setup/provenance/README.md" test_tmp="$(mktemp -d)" host_gnupg="${GNUPGHOME:-$HOME/.gnupg}" host_rpmdb="/usr/lib/sysimage/rpm/rpmdb.sqlite" ambient_gnupg="$test_tmp/fresh-ambient-gnupg" cleanup() { rm -rf -- "$test_tmp" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM fail() { printf 'package provenance contract: %s\n' "$*" >&2 exit 1 } # The rotation ledger is part of the trust contract: it must preserve GPG's # producer status, name every independent pin site, and claim only evidence # retained from the one authorized Terra container. if awk '/gpg/ && /[|]/ && /awk/ { found = 1 } END { exit !found }' \ "$provenance_readme"; then fail 'provenance README pipes GPG into a parser and can lose producer status' fi for rotation_site in 'setup/provenance/keys/' 'setup/provenance/installers.conf' \ '_require_policy_value' 'tests/setup/package-provenance-contract' \ 'setup/provenance/README.md'; do grep -qF "$rotation_site" "$provenance_readme" \ || fail "provenance rotation policy omits $rotation_site" done grep -qF 'same-container post-check independently confirmed' "$provenance_readme" \ && fail 'Terra proof claims an unretained same-container post-check' expect_success() { "$@" || fail "expected success: $*" } expect_failure() { if "$@"; then fail "expected failure: $*" fi } assert_file_bytes() { local path="$1" expected="$2" [[ "$(<"$path")" == "$expected" ]] || fail "unexpected bytes in $path" } snapshot() { local path="$1" if [[ -e "$path" ]]; then stat -c '%i:%s:%Y:%Z' "$path" else printf 'absent' fi } snapshot_gpg_state() { local path="$1" file relative [[ -d "$path" ]] || { printf 'absent\n' return 0 } { printf 'directory\0' find "$path" -mindepth 1 -print0 | LC_ALL=C sort -z \ | while IFS= read -r -d '' file; do relative="${file#"$path"/}" printf '%s\0' "$relative" if [[ -L "$file" ]]; then printf 'symlink\0%s\0' "$(readlink -- "$file")" elif [[ -f "$file" ]]; then printf 'file\0%s\0' "$(sha256sum -- "$file" | awk '{ print $1 }')" elif [[ -d "$file" ]]; then printf 'directory\0' else printf 'other\0%s\0' "$(stat -c '%F:%a:%s:%Y:%Z' "$file")" fi done } | sha256sum | awk '{ print $1 }' } snapshot_file_state() { local path="$1" if [[ -f "$path" ]]; then printf 'file:%s:%s\n' "$(stat -c '%a:%s:%Y:%Z' "$path")" \ "$(sha256sum "$path" | awk '{ print $1 }')" elif [[ -L "$path" ]]; then printf 'symlink:%s\n' "$(readlink -- "$path")" else printf 'absent\n' fi } export GNUPGHOME="$ambient_gnupg" mkdir -m 700 "$ambient_gnupg" printf 'baseline-value\n' > "$ambient_gnupg/unexpected-entry" ambient_before="$(snapshot "$ambient_gnupg")" ambient_gpg_files_before="$(snapshot_gpg_state "$ambient_gnupg")" printf 'tampered-value\n' > "$ambient_gnupg/unexpected-entry" [[ "$ambient_gpg_files_before" != "$(snapshot_gpg_state "$ambient_gnupg")" ]] \ || fail 'ambient GPG snapshot ignored unexpected file content' printf 'baseline-value\n' > "$ambient_gnupg/unexpected-entry" ambient_gpg_files_before="$(snapshot_gpg_state "$ambient_gnupg")" before_gnupg="$(snapshot "$host_gnupg")" before_gpg_files="$(snapshot_gpg_state "$host_gnupg")" before_rpmdb="$(snapshot "$host_rpmdb")" before_repo_files="$(snapshot_gpg_state /etc/yum.repos.d)" before_rpm_key_files="$(snapshot_gpg_state /etc/pki/rpm-gpg)" before_system_flatpak="$(snapshot_file_state /var/lib/flatpak/repo/config)" before_system_flathub_key="$(snapshot_file_state /var/lib/flatpak/repo/flathub.trustedkeys.gpg)" before_user_flatpak="$(snapshot_file_state "$HOME/.local/share/flatpak/repo/config")" before_bashrc="$(snapshot_file_state "$HOME/.bashrc")" # Runtime and agent installs must consume the reviewed provenance table. Keep # this scan at the public script boundary because a command hidden elsewhere in # the installer can bypass every archive-level test below. installer="$repo_dir/setup/scripts/install-packages" grep -qFx 'PRIVILEGED_TMPDIR=/var/tmp' "$installer" \ || fail 'privileged staging parent is selectable through caller environment' for openh264_binding in 'fedora-cisco-openh264.repo' \ '--repo=fedora-cisco-openh264' '--from-repo=fedora-cisco-openh264'; do grep -qF -- "$openh264_binding" "$installer" \ || fail "desktop repository scope omits $openh264_binding" done if grep -qF 'DESKTOP_REPO_ARGS=("${RPMFUSION_REPO_ARGS[@]}" --repo=terra)' "$installer" \ || grep -qF 'HYPRLAND_REPO_ARGS=("${DESKTOP_REPO_ARGS[@]}"' "$installer"; then fail 'Terra remains in a broad desktop or Hyprland repository scope' fi multimedia_scope="$(sed -n '/^# --- Codecs and multimedia/,/^# --- Install Development Packages/p' "$installer")" [[ "$multimedia_scope" != *'HYPRLAND_REPO_ARGS'* \ && "$multimedia_scope" != *'--repo=terra'* \ && "$multimedia_scope" != *'--repo=panama-hyprland'* ]] \ || fail 'multimedia transactions admit Terra or the Hyprland COPR' unsafe_installers=() for forbidden in \ 'curl[^|]*\|[[:space:]]*bash' \ 'nvm[[:space:]]+install[[:space:]]+--lts' \ 'npm[[:space:]]+install[[:space:]]+-g[[:space:]]+pnpm' \ 'npm[[:space:]]+install[[:space:]]+-g[[:space:]]+@openai/codex' \ 'releases/latest' \ 'api\.github\.com/.*/releases/latest'; do while IFS= read -r finding; do [[ -n "$finding" ]] && unsafe_installers+=("$finding") done < <(grep -nE "$forbidden" "$installer" || true) done if (( ${#unsafe_installers[@]} > 0 )); then printf 'package provenance contract: moving or piped installer inputs:\n' >&2 printf ' %s\n' "${unsafe_installers[@]}" >&2 fail 'replace each finding with a reviewed, verified installation path' fi # Every package-solving DNF command declares its dependency source set. An # unrelated enabled operator repository may remain configured, but it cannot # participate in a Panama transaction merely because DNF discovered it. unscoped_dnf="$(python3 - "$repo_dir/install" "$installer" <<'PY' import sys for path in sys.argv[1:]: lines = open(path, encoding="utf-8").read().splitlines() index = 0 while index < len(lines): command = lines[index].strip() start = index + 1 while command.endswith("\\") and index + 1 < len(lines): command = command[:-1] + " " + lines[index + 1].strip() index += 1 index += 1 if "sudo dnf" not in command or " config-manager " in command: continue if "--repo=" not in command and "REPO_ARGS" not in command: print(f"{path}:{start}:{command}") PY )" [[ -z "$unscoped_dnf" ]] || fail "unscoped DNF transaction(s): $unscoped_dnf" # Re-running install-packages must be keyed to every reviewed trust input it # consumes. The update-command fixture proves each input changes the digest; # this public-boundary guard keeps any of those inputs from being silently # removed from the installer state definition. for state_input in \ 'setup/packages' \ 'setup/scripts/install-packages' \ 'setup/lib/artifact-provenance' \ 'setup/lib/extras-catalog' \ 'setup/lib/machine-role' \ 'setup/provenance'; do grep -Fq "$state_input" "$repo_dir/install" \ || fail "packages hash does not name required state input: $state_input" done # This must be the only production file sourced by the contract. # shellcheck source=../../setup/lib/artifact-provenance source "$repo_dir/setup/lib/artifact-provenance" fixture_fingerprint='6016FF18CAE298CE3648EE2325E01F765E1EF9FA' tiny_sha256='291bd319ae85488101e908e37fc0fa1b0da1429ba27e10d2b391cb3f60dd44ea' base64 --decode "$fixtures/signed-fixture.rpm.base64" > "$test_tmp/signed-fixture.rpm" base64 --decode "$fixtures/unsigned-fixture.rpm.base64" > "$test_tmp/unsigned-fixture.rpm" base64 --decode "$fixtures/wrong-signer-fixture.rpm.base64" > "$test_tmp/wrong-signer-fixture.rpm" # The helper owns the producer-status check; it cannot depend on a sourcing # script having enabled pipefail before the GPG-to-parser pipeline runs. gpg() { printf 'pub:-:4096:1:0000000000000000:0:0::-:::scESC::::::23::0:\n' printf 'fpr:::::::::%s:\n' "$fixture_fingerprint" return 42 } set +o pipefail expect_failure _primary_key_fingerprints "$fixtures/fixture-key.asc" set -o pipefail unset -f gpg [[ "$(wc -l < "$fixtures/SHASUMS256.txt")" -eq 4 ]] || fail 'signed checksum fixture is not four lines' [[ "$(cmp -l "$fixtures/tiny-artifact" "$fixtures/tiny-artifact-tampered" | wc -l)" -eq 1 ]] \ || fail 'tampered artifact does not differ by exactly one byte' expect_success key_fingerprint_matches "$fixtures/fixture-key.asc" "$fixture_fingerprint" [[ "$ambient_before" == "$(snapshot "$ambient_gnupg")" ]] \ || fail 'fingerprint inspection created ambient GPG state' [[ "$ambient_gpg_files_before" == "$(snapshot_gpg_state "$ambient_gnupg")" ]] \ || fail 'fingerprint inspection created an ambient GPG keybox or trust database' expect_failure key_fingerprint_matches "$fixtures/fixture-key.asc" '0000000000000000000000000000000000000000' cat "$fixtures/fixture-key.asc" "$fixtures/wrong-signer-key.asc" > "$test_tmp/combined-key.asc" expect_failure key_fingerprint_matches "$test_tmp/combined-key.asc" "$fixture_fingerprint" # A producer can print a valid-looking primary fingerprint and still fail on # later malformed input. Public matchers must preserve that failure rather # than returning mapfile's successful process-substitution status. primary_key_fingerprints_definition="$(declare -f _primary_key_fingerprints)" _primary_key_fingerprints() { printf '%s\n' "$fixture_fingerprint" return 42 } expect_failure key_fingerprint_matches "$fixtures/fixture-key.asc" "$fixture_fingerprint" expect_failure verify_detached_signature \ "$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt" eval "$primary_key_fingerprints_definition" cat "$fixtures/fixture-key.asc" > "$test_tmp/malformed-key.asc" printf '\n-----BEGIN PGP PUBLIC KEY BLOCK-----\ninvalid\n' >> "$test_tmp/malformed-key.asc" malformed_status=0 malformed_home="$test_tmp/malformed-gnupg" mkdir -m 700 "$malformed_home" malformed_gpg_output="$(GNUPGHOME="$malformed_home" gpg --batch --with-colons \ --import-options show-only --import "$test_tmp/malformed-key.asc" 2>/dev/null)" \ || malformed_status=$? awk -F: '$1 == "pub" { primary = 1; next } primary && $1 == "fpr" { print $10; primary = 0 }' \ <<<"$malformed_gpg_output" > "$test_tmp/malformed-key.fingerprints" (( malformed_status != 0 )) || fail 'malformed GPG fixture did not exercise a producer failure' [[ "$(<"$test_tmp/malformed-key.fingerprints")" == "$fixture_fingerprint" ]] \ || fail 'malformed GPG fixture did not emit the valid-looking partial fingerprint' expect_failure key_fingerprint_matches "$test_tmp/malformed-key.asc" "$fixture_fingerprint" expect_failure verify_detached_signature \ "$test_tmp/malformed-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt" expect_success verify_detached_signature \ "$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt" expect_failure verify_detached_signature \ "$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/tiny-artifact" expect_failure verify_detached_signature \ "$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/tiny-artifact-tampered" cp "$fixtures/SHASUMS256.txt.asc" "$test_tmp/bad-signature.asc" sed -i 's/^=MJqv$/=MJqa/' "$test_tmp/bad-signature.asc" expect_failure verify_detached_signature \ "$fixtures/fixture-key.asc" "$test_tmp/bad-signature.asc" "$fixtures/SHASUMS256.txt" expect_failure verify_detached_signature \ "$test_tmp/combined-key.asc" "$fixtures/wrong-signer-SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt" mkdir "$test_tmp/bin" cat > "$test_tmp/bin/curl" <<'STUB' #!/usr/bin/env bash set -euo pipefail output='' connect_timeout='' max_time='' max_filesize='' while (($#)); do case "$1" in --output) output="$2"; shift 2 ;; --connect-timeout) connect_timeout="$2"; shift 2 ;; --max-time) max_time="$2"; shift 2 ;; --max-filesize) max_filesize="$2"; shift 2 ;; *) shift ;; esac done [[ "$connect_timeout" == 10 && "$max_time" == 600 && "$max_filesize" == "${CURL_EXPECTED_MAX_BYTES:?}" ]] || exit 65 case "${CURL_FIXTURE:?}" in good) cp "$CURL_FIXTURE_ROOT/tiny-artifact" "$output" ;; oversized) head -c 1025 /dev/zero > "$output" ;; interrupted) printf 'partial' > "$output"; exit 42 ;; *) exit 64 ;; esac STUB chmod +x "$test_tmp/bin/curl" PATH="$test_tmp/bin:$PATH" export PATH CURL_FIXTURE_ROOT="$fixtures" CURL_EXPECTED_MAX_BYTES=1024 destination="$test_tmp/destination" export CURL_FIXTURE=good expect_success download_sha256 'https://fixture.invalid/good' "$tiny_sha256" 1024 "$destination" cmp -s "$fixtures/tiny-artifact" "$destination" || fail 'verified download changed artifact bytes' [[ ! -e "$destination.part" ]] || fail 'successful download left a part file' export CURL_FIXTURE=good expect_failure download_sha256 'https://fixture.invalid/uppercase-digest' \ '291BD319AE85488101E908E37FC0FA1B0DA1429BA27E10D2B391CB3F60DD44EA' 1024 \ "$test_tmp/uppercase-destination" printf 'known-good\n' > "$destination" export CURL_FIXTURE=good expect_failure download_sha256 'https://fixture.invalid/bad-digest' \ '0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination" assert_file_bytes "$destination" 'known-good' [[ ! -e "$destination.part" ]] || fail 'bad digest left a part file' protected="$test_tmp/protected-known-good" printf 'known-good\n' > "$protected" ln -s "$protected" "$destination.part" expect_failure download_sha256 'https://fixture.invalid/symlink-part' \ '0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination" assert_file_bytes "$protected" 'known-good' [[ -L "$destination.part" ]] || fail 'symlinked destination part was not preserved' rm -f -- "$destination.part" ln "$protected" "$destination.part" expect_failure download_sha256 'https://fixture.invalid/hard-link-part' \ '0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination" assert_file_bytes "$protected" 'known-good' [[ "$(stat -c %i "$protected")" == "$(stat -c %i "$destination.part")" ]] \ || fail 'hard-linked destination part was not preserved' rm -f -- "$destination.part" export CURL_FIXTURE=oversized expect_failure download_sha256 'https://fixture.invalid/oversized' "$tiny_sha256" 1024 "$destination" assert_file_bytes "$destination" 'known-good' [[ ! -e "$destination.part" ]] || fail 'oversized download left a part file' export CURL_FIXTURE=interrupted expect_failure download_sha256 'https://fixture.invalid/interrupted' "$tiny_sha256" 1024 "$destination" assert_file_bytes "$destination" 'known-good' [[ ! -e "$destination.part" ]] || fail 'interrupted download left a part file' expect_success rpm_signature_matches \ "$test_tmp/signed-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint" expect_failure rpm_signature_matches \ "$test_tmp/unsigned-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint" expect_failure rpm_signature_matches \ "$test_tmp/signed-fixture.rpm" "$fixtures/fixture-key.asc" '0000000000000000000000000000000000000000' expect_failure rpm_signature_matches \ "$test_tmp/wrong-signer-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint" expect_failure rpm_signature_matches \ "$test_tmp/wrong-signer-fixture.rpm" "$test_tmp/combined-key.asc" "$fixture_fingerprint" expect_success load_installer_provenance "$config" [[ "${INSTALLER_PROVENANCE[BUN_VERSION]:-}" == '1.4.0' ]] || fail 'valid provenance was not loaded' for reviewed_value in \ 'NODE_VERSION 24.20.0' \ 'NODE_X86_64_SHA256 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \ 'NODE_X86_64_BINARY_SHA256 89af8424dd53e560b1933f87ba650d8bf57c83ca5a04600eefb31f416aabbae7' \ 'NODE_AARCH64_SHA256 5f4ddab610c1ab2016b3c227cebdbf6d9495161487e4739c7b90090595f465f7' \ 'NODE_AARCH64_BINARY_SHA256 23a5637c2470fde09fcc1acc77c1b92e04e3d7e3e6e80ff7df6f5831958d1477' \ 'BUN_VERSION 1.4.0' \ 'BUN_X86_64_SHA256 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'BUN_X86_64_BINARY_SHA256 33d56b070be6a9e3da0ab013038b43d1645d0534ca811ecdba4472599117eb4b' \ 'BUN_AARCH64_SHA256 4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e' \ 'BUN_AARCH64_BINARY_SHA256 086c4121c8738a8e0f5ed730e8a461bc3973b4444e372ddb77aef9a747fa2ae9' \ 'CODEX_VERSION 0.150.1' \ 'CODEX_X86_64_SHA256 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \ 'CODEX_X86_64_BINARY_SHA256 abf1bb1643a79f73aa78ee627e111e02d4f8c98f25813a0cf6ce277709664386' \ 'CODEX_AARCH64_SHA256 1ecac3f87823efb98153233b076ea3d6e34a7a8cebe43c5285dc5f79e1514639' \ 'CODEX_AARCH64_BINARY_SHA256 7a49aabe11fd95a1c968d79e16b5f1b17c3219002c5f7129d467f415f8460feb' \ 'RUSTDESK_VERSION 1.4.9' \ 'RUSTDESK_X86_64_SHA256 eb1b053ac5b2f774f2271f7fbbfd2ea475899f7a55135c5e172bc54b9388f108'; do read -r name expected <<<"$reviewed_value" [[ "${INSTALLER_PROVENANCE[$name]:-}" == "$expected" ]] \ || fail "$name does not match the reviewed release" done for key_spec in \ 'terra44 TERRA_FINGERPRINT' \ 'claude-code CLAUDE_CODE_FINGERPRINT' \ 'bun BUN_FINGERPRINT' \ 'rpmfusion-free RPMFUSION_FREE_FINGERPRINT' \ 'rpmfusion-nonfree RPMFUSION_NONFREE_FINGERPRINT' \ 'hyprland-copr HYPRLAND_COPR_FINGERPRINT' \ 'flathub FLATHUB_FINGERPRINT' \ 'claude-desktop CLAUDE_DESKTOP_FINGERPRINT'; do read -r key_file fingerprint_name <<<"$key_spec" expect_success key_fingerprint_matches "$repo_dir/setup/provenance/keys/$key_file.asc" \ "${INSTALLER_PROVENANCE[$fingerprint_name]}" done [[ "$before_gnupg" == "$(snapshot "$host_gnupg")" ]] || fail 'host GPG state changed' [[ "$before_gpg_files" == "$(snapshot_gpg_state "$host_gnupg")" ]] || fail 'host GPG state changed' [[ "$before_rpmdb" == "$(snapshot "$host_rpmdb")" ]] || fail 'host RPM database changed' [[ "$ambient_before" == "$(snapshot "$ambient_gnupg")" ]] || fail 'production helper created ambient GPG state' [[ "$ambient_gpg_files_before" == "$(snapshot_gpg_state "$ambient_gnupg")" ]] \ || fail 'production helper created or changed an ambient GPG entry' parser_fixture="$test_tmp/installers.conf" cp "$config" "$parser_fixture" printf 'UNKNOWN_KEY=value\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" printf 'BUN_VERSION=1.4.0\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" grep -v '^NODE_VERSION=' "$config" > "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" sed -i 's/^BUN_VERSION=/ BUN_VERSION=/' "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" sed -i 's/^BUN_VERSION=.*/BUN_VERSION=$(id -u)/' "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" printf 'BUN_ARMV7_URL=https://fixture.invalid/bun\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" # Repository setup runs from a fixture copy of the installer with every # external command replaced. A contract failure can therefore inspect exact # ordering and staged bytes without consulting or changing the host. installer_fixture="$test_tmp/installer-fixture" mkdir -p "$installer_fixture/setup/lib" "$installer_fixture/setup/provenance/keys" \ "$installer_fixture/setup/scripts" cp "$repo_dir/setup/lib/artifact-provenance" "$repo_dir/setup/lib/extras-catalog" \ "$repo_dir/setup/lib/machine-role" "$installer_fixture/setup/lib/" cp "$config" "$installer_fixture/setup/provenance/installers.conf" cp "$repo_dir"/setup/provenance/keys/*.asc "$installer_fixture/setup/provenance/keys/" sed '/^# --- The server path/,$d' "$repo_dir/setup/scripts/install-packages" \ > "$installer_fixture/setup/scripts/install-packages" cat >> "$installer_fixture/setup/scripts/install-packages" <<'STUB' exercise_agent_install_boundary() { setup_node() { printf 'agent:node\n' >> "$COMMAND_LOG"; } install_pnpm() { printf 'agent:pnpm\n' >> "$COMMAND_LOG"; } install_bun() { printf 'agent:bun\n' >> "$COMMAND_LOG"; } install_claude_code() { printf 'agent:claude:78\n' >> "$COMMAND_LOG"; return 78; } install_codex() { printf 'agent:codex\n' >> "$COMMAND_LOG"; } install_optional_agent_tools } STUB artifact_root="$test_tmp/runtime-artifacts" mkdir -p "$artifact_root/build" for arch_spec in \ 'x86_64 x64 x64' \ 'aarch64 arm64 aarch64'; do read -r machine node_arch bun_arch <<<"$arch_spec" node_top="node-v24.20.0-linux-$node_arch" mkdir -p "$artifact_root/build/$node_top/bin" printf '#!/usr/bin/env bash\n# fixture %s\nprintf "v24.20.0\\n"\n' "$machine" \ > "$artifact_root/build/$node_top/bin/node" chmod +x "$artifact_root/build/$node_top/bin/node" cp "$artifact_root/build/$node_top/bin/node" "$artifact_root/node-$machine.bin" tar -C "$artifact_root/build" -cJf "$artifact_root/node-$machine.tar.xz" "$node_top" rm -rf -- "$artifact_root/build/$node_top" bun_top="bun-linux-$bun_arch" mkdir -p "$artifact_root/build/$bun_top" printf '#!/usr/bin/env bash\n# fixture %s\nprintf "1.4.0\\n"\n' "$machine" \ > "$artifact_root/build/$bun_top/bun" chmod +x "$artifact_root/build/$bun_top/bun" cp "$artifact_root/build/$bun_top/bun" "$artifact_root/bun-$machine.bin" (cd "$artifact_root/build" && zip -q "$artifact_root/bun-$machine.zip" \ "$bun_top/" "$bun_top/bun") rm -rf -- "$artifact_root/build/$bun_top" mkdir -p "$artifact_root/build/bin" "$artifact_root/build/codex-path" \ "$artifact_root/build/codex-resources/zsh/bin" printf '#!/usr/bin/env bash\n# fixture %s\nprintf "codex-cli 0.150.1\\n"\n' "$machine" \ > "$artifact_root/build/bin/codex" cp "$artifact_root/build/bin/codex" "$artifact_root/codex-$machine.bin" printf '#!/usr/bin/env bash\nprintf "code mode host\\n"\n' \ > "$artifact_root/build/bin/codex-code-mode-host" printf '{"target":"%s"}\n' "$machine" > "$artifact_root/build/codex-package.json" for package_binary in codex-path/rg codex-resources/bwrap codex-resources/zsh/bin/zsh; do printf '#!/usr/bin/env bash\nprintf "package resource\\n"\n' \ > "$artifact_root/build/$package_binary" done chmod +x "$artifact_root/build/bin/codex" \ "$artifact_root/build/bin/codex-code-mode-host" \ "$artifact_root/build/codex-path/rg" "$artifact_root/build/codex-resources/bwrap" \ "$artifact_root/build/codex-resources/zsh/bin/zsh" tar -C "$artifact_root/build" --no-recursion -czf "$artifact_root/codex-$machine.tar.gz" \ bin/ bin/codex bin/codex-code-mode-host codex-package.json \ codex-path/ codex-path/rg codex-resources/ codex-resources/bwrap \ codex-resources/zsh/ codex-resources/zsh/bin/ codex-resources/zsh/bin/zsh rm -rf -- "$artifact_root/build/bin" "$artifact_root/build/codex-path" \ "$artifact_root/build/codex-resources" rm -f -- "$artifact_root/build/codex-package.json" done python3 - "$artifact_root/bun-symlink.zip" "$artifact_root/bun-special.zip" \ "$artifact_root/bun-directory.zip" <<'PY' import stat import sys import zipfile for destination, directory_type, entry_type, content in ( (sys.argv[1], stat.S_IFDIR, stat.S_IFLNK, b"../../outside-bun"), (sys.argv[2], stat.S_IFDIR, stat.S_IFSOCK, b'#!/usr/bin/env bash\nprintf "1.4.0\\n"\n'), (sys.argv[3], stat.S_IFREG, stat.S_IFREG, b'#!/usr/bin/env bash\nprintf "1.4.0\\n"\n'), ): with zipfile.ZipFile(destination, "w") as archive: directory = zipfile.ZipInfo("bun-linux-x64/") directory.create_system = 3 directory.external_attr = (directory_type | 0o755) << 16 archive.writestr(directory, b"") binary = zipfile.ZipInfo("bun-linux-x64/bun") binary.create_system = 3 binary.external_attr = (entry_type | 0o755) << 16 archive.writestr(binary, content) PY mkdir -p "$artifact_root/build/wrong-node/bin" "$artifact_root/build/wrong-codex" printf '#!/usr/bin/env bash\nprintf "v24.20.0\\n"\n' > "$artifact_root/build/wrong-node/bin/node" printf '#!/usr/bin/env bash\nprintf "codex-cli 0.150.1\\n"\n' > "$artifact_root/build/wrong-codex/codex" chmod +x "$artifact_root/build/wrong-node/bin/node" "$artifact_root/build/wrong-codex/codex" tar -C "$artifact_root/build" -cJf "$artifact_root/node-bad.tar.xz" wrong-node tar -C "$artifact_root/build" -czf "$artifact_root/codex-bad.tar.gz" wrong-codex/codex mkdir -p "$artifact_root/build/bun-linux-x64" printf '#!/usr/bin/env bash\nprintf "1.4.0\\n"\n' > "$artifact_root/build/bun-linux-x64/bun" printf 'unexpected\n' > "$artifact_root/build/bun-linux-x64/extra" chmod +x "$artifact_root/build/bun-linux-x64/bun" (cd "$artifact_root/build" && zip -q "$artifact_root/bun-bad.zip" \ bun-linux-x64/ bun-linux-x64/bun bun-linux-x64/extra) cp "$test_tmp/unsigned-fixture.rpm" "$artifact_root/rustdesk.rpm" printf '#!/usr/bin/env bash\nprintf "executed\\n" > "$OUTSIDE_EXECUTED"\nprintf "v24.20.0\\n"\n' \ > "$artifact_root/outside-node" printf '#!/usr/bin/env bash\nprintf "executed\\n" > "$OUTSIDE_EXECUTED"\nprintf "codex-cli 0.150.1\\n"\n' \ > "$artifact_root/outside-codex" chmod +x "$artifact_root/outside-node" "$artifact_root/outside-codex" node_escape_top='node-v24.20.0-linux-x64' mkdir -p "$artifact_root/build/$node_escape_top/bin" ln -s "$artifact_root/outside-node" "$artifact_root/build/$node_escape_top/bin/node" tar -C "$artifact_root/build" -cJf "$artifact_root/node-symlink-escape.tar.xz" "$node_escape_top" rm -rf -- "$artifact_root/build" make_stub_commands() { local case_root="$1" mkdir -p "$case_root/bin" "$case_root/home" "$case_root/tmp" \ "$case_root/etc/profile.d" "$case_root/etc/yum.repos.d" \ "$case_root/etc/pki/rpm-gpg" "$case_root/flatpak-repo" \ "$case_root/root-staging" cat > "$case_root/etc/profile.d/nvm.sh" <<'STUB' nvm() { printf 'nvm:%s\n' "$*" >> "$COMMAND_LOG" } STUB cat > "$case_root/bin/uname" <<'STUB' #!/usr/bin/env bash printf '%s\n' "${STUB_ARCH:-x86_64}" STUB cat > "$case_root/bin/stat" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ "${1:-}" == -c && "${2:-}" == '%u:%a' && "${3:-}" == -- \ && ( "${4:-}" == "$STUB_ETC"/* || "${4:-}" == "$STUB_FLATPAK_REPO"/* ) ]]; then [[ -f "$4" && ! -L "$4" ]] || exit 1 printf '0:%s\n' "$(/usr/bin/stat -c %a "$4")" exit 0 fi exec /usr/bin/stat "$@" STUB cat > "$case_root/bin/mktemp" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ "${STUB_SIGNAL_PHASE:-}" == gpg-home && "${1:-}" == -d ]]; then directory="$(/usr/bin/mktemp "$@")" printf '%s\n' "$directory" printf 'signal:gpg-home\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "${STUB_SIGNAL_PHASE:-}" == repo-work && "${1:-}" == -d \ && "$*" != *'-u'* ]]; then directory="$(/usr/bin/mktemp "$@")" printf '%s\n' "$directory" printf 'signal:repo-work\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi exec /usr/bin/mktemp "$@" STUB cat > "$case_root/bin/mkdir" <<'STUB' #!/usr/bin/env bash set -euo pipefail target="${!#}" if [[ "${STUB_SIGNAL_PHASE:-}" == gpg-home \ && "$(basename -- "$target")" == panama-gpg.* ]]; then /usr/bin/mkdir "$@" printf 'signal:gpg-home\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "${STUB_SIGNAL_PHASE:-}" == repo-work \ && "$(basename -- "$target")" == panama-rpmfusion.* ]]; then /usr/bin/mkdir "$@" printf 'signal:repo-work\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi exec /usr/bin/mkdir "$@" STUB cat > "$case_root/bin/chmod" <<'STUB' #!/usr/bin/env bash set -euo pipefail /usr/bin/chmod "$@" target="${!#}" if [[ "${STUB_SWAP_AFTER_REPO_WRITE:-}" == "$(basename -- "$target")" \ && "${1:-}" == 0600 ]]; then printf 'swapped after repository write\n' > "$target" printf '%s\n' "$target" > "$STUB_SWAP_MARKER" fi STUB cat > "$case_root/bin/tar" <<'STUB' #!/usr/bin/env bash set -euo pipefail /usr/bin/tar "$@" if [[ "${STUB_SIGNAL_PHASE:-}" == extract && "$*" == *'-x'* ]]; then printf 'signal:extract\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "${STUB_NODE_ESCAPE:-}" == hardlink && "$*" == *'-xJf'* ]]; then destination='' while (($#)); do if [[ "$1" == -C ]]; then destination="$2" break fi shift done [[ -n "$destination" ]] || exit 71 node="$destination/node-v24.20.0-linux-x64/bin/node" rm -f -- "$node" ln "$ARTIFACT_ROOT/outside-node" "$node" fi if [[ -n "${STUB_TRAVERSAL_ERROR:-}" && "$*" == *'-x'* ]]; then destination='' arguments=("$@") for ((index = 0; index < ${#arguments[@]}; index++)); do if [[ "${arguments[index]}" == -C ]]; then destination="${arguments[index + 1]}" break fi done [[ -n "$destination" ]] || exit 73 case "$STUB_TRAVERSAL_ERROR" in Node) selected="$destination/node-v24.20.0-linux-x64/bin/node" outside="$ARTIFACT_ROOT/outside-node" mkdir "$destination/node-v24.20.0-linux-x64/.unreadable" chmod 000 "$destination/node-v24.20.0-linux-x64/.unreadable" ;; Codex) selected="$destination/bin/codex" outside="$ARTIFACT_ROOT/outside-codex" ;; esac rm -f -- "$selected" ln -s -- "$outside" "$selected" fi STUB cat > "$case_root/bin/find" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ -n "${STUB_TRAVERSAL_ERROR:-}" && "${1:-}" == *'.stage.'* ]]; then printf 'find:traversal-error:%s\n' "$STUB_TRAVERSAL_ERROR" >> "$COMMAND_LOG" if [[ "$STUB_TRAVERSAL_ERROR" == Node ]]; then root="$1" shift exec /usr/bin/find "$root/.unreadable" "$@" fi printf 'malformed traversal output' exit 74 fi exec /usr/bin/find "$@" STUB cat > "$case_root/bin/unzip" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ -n "${STUB_BUN_ATTRIBUTE:-}" && "${1:-}" == -q ]]; then destination='' while (($#)); do if [[ "$1" == -d ]]; then destination="$2" break fi shift done [[ -n "$destination" ]] || exit 72 mkdir -p "$destination/bun-linux-x64" cp "$ARTIFACT_ROOT/bun-x86_64.bin" "$destination/bun-linux-x64/bun" chmod +x "$destination/bun-linux-x64/bun" exit 0 fi exec /usr/bin/unzip "$@" STUB cat > "$case_root/bin/mv" <<'STUB' #!/usr/bin/env bash set -euo pipefail destination="${!#}" if [[ "${STUB_SIGNAL_PHASE:-}" == activation ]]; then case "$destination" in */.nvm/versions/node/v24.20.0|*/.bun/versions/1.4.0|*/.local/lib/panama/codex/0.150.1) printf 'signal:activation\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 ;; esac elif [[ "${STUB_SIGNAL_PHASE:-}" == link ]]; then case "$destination" in */.bun/bin/bun|*/.local/bin/codex) printf 'signal:link\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 ;; esac fi case "${STUB_LATE_COLLISION:-}:$destination" in Node:*/.nvm/versions/node/v24.20.0|\ Bun:*/.bun/versions/1.4.0|\ Codex:*/.local/lib/panama/codex/0.150.1) if [[ ! -e "$destination" ]]; then mkdir -p -- "$destination" printf 'preserved collision\n' > "$destination/collision-marker" printf 'mv:late-collision:%s\n' "$STUB_LATE_COLLISION" >> "$COMMAND_LOG" fi ;; esac exec /usr/bin/mv "$@" STUB cat > "$case_root/bin/rpm" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ "$*" == '-E %fedora' ]]; then printf 'rpm:release\n' >> "$COMMAND_LOG" printf '%s\n' "${STUB_FEDORA_RELEASE:-44}" elif [[ "${1:-}" == -q ]]; then package="${!#}" printf 'rpm:query:%s\n' "$package" >> "$COMMAND_LOG" case "$package" in terra-release) [[ "${STUB_TERRA_INSTALLED:-0}" == 1 \ || -s "${STUB_TERRA_RPM_STATE:?}" ]] ;; claude-desktop-extra) [[ "${STUB_CLAUDE_DESKTOP_INSTALLED:-0}" == 1 ]] ;; rustdesk) [[ -n "${STUB_RUSTDESK_VERSION:-}" ]] || exit 1 [[ "$*" != *--queryformat* ]] || printf '%s' "$STUB_RUSTDESK_VERSION" ;; *) exit 1 ;; esac else exit 64 fi STUB cat > "$case_root/bin/curl" <<'STUB' #!/usr/bin/env bash set -euo pipefail output='' max_filesize='' connect_timeout='' max_time='' url='' while (($#)); do case "$1" in --output) output="$2"; shift 2 ;; --max-filesize) max_filesize="$2"; shift 2 ;; --connect-timeout) connect_timeout="$2"; shift 2 ;; --max-time) max_time="$2"; shift 2 ;; --fail|--location) shift ;; *) url="$1"; shift ;; esac done [[ -n "$output" && "$connect_timeout" == 10 && "$max_time" == 600 ]] || exit 65 output_name="$(basename "$output")" output_name="${output_name#.}" output_name="${output_name%.part.*}" printf 'curl:%s:max=%s:output=%s\n' "$url" "$max_filesize" "$output_name" >> "$COMMAND_LOG" if [[ "${STUB_SIGNAL_PHASE:-}" == download ]]; then printf 'partial' > "$output" printf 'signal:download\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "${STUB_DOWNLOAD_INTERRUPT:-}" == 1 ]]; then printf 'partial' > "$output" exit 42 fi case "$url" in *rpmfusion-free*) cp "$SIGNED_RPM" "$output" ;; *rpmfusion-nonfree*) cp "$SIGNED_RPM" "$output" ;; *flathub.flatpakrepo) cp "$FLATHUB_DESCRIPTOR" "$output" ;; *node-v24.20.0-linux-x64.tar.xz) if [[ "${STUB_NODE_ESCAPE:-}" == symlink ]]; then cp "$ARTIFACT_ROOT/node-symlink-escape.tar.xz" "$output" else cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+node-bad.tar.xz}" "$output" 2>/dev/null \ || cp "$ARTIFACT_ROOT/node-x86_64.tar.xz" "$output" fi ;; *node-v24.20.0-linux-arm64.tar.xz) cp "$ARTIFACT_ROOT/node-aarch64.tar.xz" "$output" ;; *bun-linux-x64.zip) cp "$ARTIFACT_ROOT/${STUB_BUN_ATTRIBUTE:+bun-$STUB_BUN_ATTRIBUTE.zip}" "$output" 2>/dev/null \ || cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+bun-bad.zip}" "$output" 2>/dev/null \ || cp "$ARTIFACT_ROOT/bun-x86_64.zip" "$output" ;; *bun-linux-aarch64.zip) cp "$ARTIFACT_ROOT/bun-aarch64.zip" "$output" ;; *codex-package-x86_64-unknown-linux-musl.tar.gz) cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+codex-bad.tar.gz}" "$output" 2>/dev/null \ || cp "$ARTIFACT_ROOT/codex-x86_64.tar.gz" "$output" ;; *codex-package-aarch64-unknown-linux-musl.tar.gz) cp "$ARTIFACT_ROOT/codex-aarch64.tar.gz" "$output" ;; *rustdesk-1.4.9-0.x86_64.rpm) cp "$ARTIFACT_ROOT/rustdesk.rpm" "$output" ;; *) exit 66 ;; esac STUB cat > "$case_root/bin/sha256sum" <<'STUB' #!/usr/bin/env bash set -euo pipefail file="${!#}" if [[ "${STUB_TOGGLE_KEY_VERIFY:-}" == "$(basename -- "$file")" \ && "$file" != "$PRIVILEGED_TMPDIR"/panama-install.*/* ]]; then printf 'attacker key bytes\n' > "$file" printf '%s\n' "$file" > "$STUB_SWAP_MARKER" fi if [[ "${STUB_DIGEST_MISMATCH:-}" == 1 ]]; then printf '%064d %s\n' 0 "$file" exit 0 fi if [[ "${STUB_TRAVERSAL_ERROR:-}" == Node ]] \ && cmp -s "$file" "$ARTIFACT_ROOT/outside-node"; then printf '%s %s\n' 89af8424dd53e560b1933f87ba650d8bf57c83ca5a04600eefb31f416aabbae7 "$file" exit 0 elif [[ "${STUB_TRAVERSAL_ERROR:-}" == Codex ]] \ && cmp -s "$file" "$ARTIFACT_ROOT/outside-codex"; then printf '%s %s\n' abf1bb1643a79f73aa78ee627e111e02d4f8c98f25813a0cf6ce277709664386 "$file" exit 0 fi for spec in \ 'node-x86_64.tar.xz 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \ 'node-aarch64.tar.xz 5f4ddab610c1ab2016b3c227cebdbf6d9495161487e4739c7b90090595f465f7' \ 'node-bad.tar.xz 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \ 'node-symlink-escape.tar.xz 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \ 'bun-x86_64.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'bun-aarch64.zip 4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e' \ 'bun-bad.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'bun-symlink.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'bun-special.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'bun-directory.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \ 'codex-x86_64.tar.gz 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \ 'codex-aarch64.tar.gz 1ecac3f87823efb98153233b076ea3d6e34a7a8cebe43c5285dc5f79e1514639' \ 'codex-bad.tar.gz 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \ 'rustdesk.rpm eb1b053ac5b2f774f2271f7fbbfd2ea475899f7a55135c5e172bc54b9388f108'; do read -r fixture digest <<<"$spec" if cmp -s "$file" "$ARTIFACT_ROOT/$fixture"; then if [[ "$fixture" == rustdesk.rpm ]]; then stat -c '%d:%i' "$file" > "$VERIFIED_RUSTDESK_INODE" fi printf '%s %s\n' "$digest" "$file" exit 0 fi done for binary_spec in \ 'node-x86_64.bin 89af8424dd53e560b1933f87ba650d8bf57c83ca5a04600eefb31f416aabbae7' \ 'node-aarch64.bin 23a5637c2470fde09fcc1acc77c1b92e04e3d7e3e6e80ff7df6f5831958d1477' \ 'bun-x86_64.bin 33d56b070be6a9e3da0ab013038b43d1645d0534ca811ecdba4472599117eb4b' \ 'bun-aarch64.bin 086c4121c8738a8e0f5ed730e8a461bc3973b4444e372ddb77aef9a747fa2ae9' \ 'codex-x86_64.bin abf1bb1643a79f73aa78ee627e111e02d4f8c98f25813a0cf6ce277709664386' \ 'codex-aarch64.bin 7a49aabe11fd95a1c968d79e16b5f1b17c3219002c5f7129d467f415f8460feb'; do read -r fixture digest <<<"$binary_spec" if cmp -s "$file" "$ARTIFACT_ROOT/$fixture"; then printf '%s %s\n' "$digest" "$file" exit 0 fi done /usr/bin/sha256sum "$@" STUB cat > "$case_root/bin/gpg" <<'STUB' #!/usr/bin/env bash set -euo pipefail key="${!#}" fingerprint='' toggle_saved='' if [[ "${STUB_TOGGLE_KEY_VERIFY:-}" == "$(basename -- "$key")" \ && "$key" != "$PRIVILEGED_TMPDIR"/panama-install.*/* ]]; then toggle_saved="$(mktemp)" cp -- "$key" "$toggle_saved" cp -- "$REVIEWED_KEYS/$(basename -- "$key")" "$key" fi for candidate in "$REVIEWED_KEYS"/*.asc; do if cmp -s "$key" "$candidate"; then case "$(basename "$candidate")" in terra44.asc) fingerprint='AE09157A4DE88B497EA1D5D300CDAB43DE226D6F' ;; claude-code.asc) fingerprint='31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE' ;; rpmfusion-free.asc) fingerprint='E9A491A3DE247814E7E067EAE06F8ECDD651FF2E' ;; rpmfusion-nonfree.asc) fingerprint='79BDB88F9BBF73910FD4095B6A2AF96194843C65' ;; hyprland-copr.asc) fingerprint='97E23476C89635135407C7D5E9BA41342C4B2995' ;; flathub.asc) fingerprint='6E5C05D979C76DAF93C081354184DD4D907A7CAE' ;; claude-desktop.asc) fingerprint='825A7D15D78BABE45646D5DF382409F597908867' ;; esac break fi done [[ -n "$fingerprint" ]] || exit 1 printf 'gpg:fingerprint:%s\n' "$fingerprint" >> "$COMMAND_LOG" printf 'pub:-:4096:1:0000000000000000:0:0::-:::scESC::::::23::0:\n' printf 'fpr:::::::::%s:\n' "$fingerprint" if [[ -n "$toggle_saved" ]]; then cp -- "$toggle_saved" "$key" rm -f -- "$toggle_saved" fi STUB cat > "$case_root/bin/rpmkeys" <<'STUB' #!/usr/bin/env bash set -euo pipefail action='' package='' while (($#)); do case "$1" in --dbpath) shift 2 ;; --import) action=import; package="$2"; shift 2 ;; --checksig) action=checksig; shift; [[ "${1:-}" == --verbose ]] && shift; package="$1"; shift ;; *) shift ;; esac done printf 'rpmkeys:%s:%s\n' "$action" "$(basename "$package")" >> "$COMMAND_LOG" if [[ "$action" == import \ && ( "${STUB_SIGNAL_PHASE:-}" == rpmdb || "${STUB_SIGNAL_PHASE:-}" == root-review ) ]]; then printf 'signal:%s\n' "$STUB_SIGNAL_PHASE" >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "$action" == checksig ]]; then [[ "${STUB_RPM_SIGNATURE_FAIL:-}" != "$(basename "$package")" ]] || exit 1 printf 'Header OpenPGP signature: OK\n' fi STUB cat > "$case_root/bin/sudo" <<'STUB' #!/usr/bin/env bash set -euo pipefail if [[ "${1:-}" == mktemp && "${2:-}" == -d ]]; then printf 'sudo:root-create\n' >> "$COMMAND_LOG" directory="$(/usr/bin/mktemp -d "$3")" printf '%s\n' "$directory" if [[ "${STUB_SIGNAL_PHASE:-}" == root-create ]]; then printf 'signal:root-create\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi exit 0 fi if [[ "${1:-}" == mkdir && "${2:-}" == -m && "${3:-}" == 0700 \ && "${4:-}" == -- ]]; then printf 'sudo:root-create\n' >> "$COMMAND_LOG" /usr/bin/mkdir -m 0700 -- "$5" if [[ "${STUB_SIGNAL_PHASE:-}" == root-create ]]; then printf 'signal:root-create\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi exit 0 fi if [[ "${1:-}" == test && "${2:-}" == -d ]]; then [[ -d "${3:-}" ]] exit fi if [[ "${1:-}" == test && "${2:-}" == '!' && "${3:-}" == -L ]]; then [[ ! -L "${4:-}" ]] exit fi if [[ "${1:-}" == stat && "${2:-}" == -c && "${3:-}" == '%u:%a' \ && "${4:-}" == -- ]]; then if [[ -d "$5" && ! -L "$5" ]]; then printf '0:%s\n' "$(/usr/bin/stat -c %a "$5")" elif [[ -f "$5" && ! -L "$5" ]]; then printf '0:%s\n' "$(/usr/bin/stat -c %a "$5")" else exit 1 fi exit 0 fi if [[ "${1:-}" == chmod && "${2:-}" == 0700 ]]; then printf 'sudo:root-private\n' >> "$COMMAND_LOG" exec /usr/bin/chmod 0700 "$3" fi if [[ "${1:-}" == chmod && "${2:-}" == 0711 ]]; then printf 'sudo:root-reviewable\n' >> "$COMMAND_LOG" exec /usr/bin/chmod 0711 "$3" fi if [[ "${1:-}" == sha256sum && "${2:-}" == -- ]]; then printf 'sudo:root-verify:%s\n' "$(basename -- "$3")" >> "$COMMAND_LOG" source_file="$3" [[ "$source_file" != /etc/* ]] || source_file="$STUB_ETC${source_file#/etc}" exec sha256sum -- "$source_file" fi if [[ "${1:-}" == install ]]; then shift [[ "${1:-}" == -m ]] || exit 67 mode="$2" source_file="$3" destination="$4" if [[ "$mode" == 0444 || "$mode" == 444 \ || "$mode" == 0600 || "$mode" == 600 ]]; then [[ "$destination" == "$PRIVILEGED_TMPDIR"/panama-install.*/"$(basename -- "$destination")" ]] \ || exit 67 printf 'sudo:root-stage:%s\n' "$(basename -- "$destination")" >> "$COMMAND_LOG" [[ "$source_file" != /etc/* ]] || source_file="$STUB_ETC${source_file#/etc}" if [[ "${STUB_TOGGLE_KEY_VERIFY:-}" == "$(basename -- "$source_file")" \ && "$source_file" != "$PRIVILEGED_TMPDIR"/panama-install.*/* ]]; then printf 'attacker key bytes\n' > "$source_file" printf '%s\n' "$source_file" > "$STUB_SWAP_MARKER" fi /usr/bin/install -m "$mode" "$source_file" "$destination" if [[ "${STUB_SWAP_AFTER_ROOT_STAGE:-}" == "$(basename -- "$destination")" ]]; then printf 'swapped after privileged copy\n' > "$source_file" printf '%s\n' "$source_file" > "$STUB_SWAP_MARKER" fi exit 0 fi [[ "$mode" == 0644 || "$mode" == 644 ]] || exit 67 [[ "$source_file" == "$PRIVILEGED_TMPDIR"/panama-install.*/* ]] || exit 67 printf 'sudo:install:%s:%s\n' "$(basename "$source_file")" "$destination" >> "$COMMAND_LOG" mapped="$STUB_ETC${destination#/etc}" mkdir -p "$(dirname "$mapped")" /usr/bin/install -m 0644 "$source_file" "$mapped" count=0 [[ ! -f "$STUB_INSTALL_COUNTER" ]] || read -r count < "$STUB_INSTALL_COUNTER" count=$((count + 1)) printf '%s\n' "$count" > "$STUB_INSTALL_COUNTER" if [[ "${STUB_SIGNAL_PAIR_AFTER_FIRST:-0}" == 1 && "$count" == 1 ]]; then printf 'signal:repository-pair\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ "${STUB_SIGNAL_PAIR_TWICE:-0}" == 1 && "$count" == 2 ]]; then printf 'signal:repository-pair-second\n' >> "$COMMAND_LOG" pgid="$(ps -o pgid= -p $$ | tr -d ' ')" kill -TERM -- "-$pgid" sleep 2 fi if [[ -n "${STUB_INSTALL_FAIL_AT:-}" && "$count" == "$STUB_INSTALL_FAIL_AT" ]]; then exit 67 fi exit 0 fi if [[ "${1:-}" == rm && "${2:-}" == -rf && "${3:-}" == -- ]]; then [[ "$4" == "$PRIVILEGED_TMPDIR"/panama-install.* ]] || exit 67 printf 'sudo:root-cleanup\n' >> "$COMMAND_LOG" [[ "${STUB_ROOT_CLEANUP_FAIL:-0}" != 1 ]] || exit 79 exec /usr/bin/rm -rf -- "$4" fi if [[ "${1:-}" == rm && "${2:-}" == -f && "${3:-}" == -- ]]; then destination="$4" printf 'sudo:rm:%s\n' "$destination" >> "$COMMAND_LOG" [[ "${STUB_ROLLBACK_FAIL:-0}" != 1 ]] || exit 79 rm -f -- "$STUB_ETC${destination#/etc}" exit 0 fi if [[ "${1:-}" == rpm && "${2:-}" == --import ]]; then key="${3:-}" [[ "$key" == "$PRIVILEGED_TMPDIR"/panama-install.*/*.asc && -f "$key" ]] \ || exit 76 printf 'sudo:rpm-import:%s\n' "$(basename -- "$key")" >> "$COMMAND_LOG" printf '%s\n' "$(basename -- "$key")" >> "$STUB_SYSTEM_KEYRING" exit 0 fi original="$*" logged=() for argument in "$@"; do if [[ "$argument" == --gpg-import=*/flathub-key.asc ]]; then logged+=(--gpg-import=FLATHUB_KEY) continue fi if [[ "$argument" == --setopt=panama-bound-*.gpgkey=file://*/panama-bound-*.asc ]]; then key="${argument#*=file://}" [[ "$key" == "$PRIVILEGED_TMPDIR"/panama-install.*/panama-bound-*.asc \ && -f "$key" ]] || exit 76 logged+=("${argument%%=file://*}=file://BOUND_KEY") continue fi if [[ "$argument" == --setopt=panama-claude-desktop.gpgkey=file://*/claude-desktop.asc ]]; then key="${argument#*=file://}" [[ "$key" == "$PRIVILEGED_TMPDIR"/panama-install.*/claude-desktop.asc \ && -f "$key" ]] || exit 76 logged+=(--setopt=panama-claude-desktop.gpgkey=file://CLAUDE_DESKTOP_KEY) continue fi case "$(basename "$argument")" in rpmfusion-free-release.rpm) logged+=(RPMFUSION_FREE) ;; rpmfusion-nonfree-release.rpm) logged+=(RPMFUSION_NONFREE) ;; rustdesk.rpm) logged+=(RUSTDESK_LOCAL) ;; flathub-key.asc) logged+=(FLATHUB_KEY) ;; *) logged+=("$argument") ;; esac done printf 'sudo:%s\n' "${logged[*]}" >> "$COMMAND_LOG" if [[ "$original" == *'/rustdesk.rpm'* ]]; then [[ "$original" == *'--setopt=localpkg_gpgcheck=0'* \ && "$original" != *'--setopt=localpkg_gpgcheck=1'* ]] || exit 70 rustdesk_path='' for argument in "$@"; do [[ "$(basename -- "$argument")" != rustdesk.rpm ]] || rustdesk_path="$argument" done [[ "$rustdesk_path" == "$PRIVILEGED_TMPDIR"/panama-install.*/rustdesk.rpm ]] \ && cmp -s "$rustdesk_path" "$UNSIGNED_RPM" || exit 72 /usr/bin/rpm -qp --queryformat '%{NAME}\n' "$rustdesk_path" >/dev/null || exit 73 signature_status="$(/usr/bin/rpmkeys --checksig --verbose "$rustdesk_path")" || exit 74 [[ "$signature_status" == *'Header SHA256 digest: OK'* \ && "$signature_status" == *'Payload SHA256 digest: OK'* \ && "${signature_status,,}" != *signature* ]] || exit 75 fi if [[ "$original" == *'/rpmfusion-free-release.rpm'* \ || "$original" == *'/rpmfusion-nonfree-release.rpm'* ]]; then if [[ "${STUB_REQUIRE_RPMFUSION_SYSTEM_KEYS:-0}" == 1 ]]; then grep -qFx rpmfusion-free.asc "$STUB_SYSTEM_KEYRING" || exit 77 grep -qFx rpmfusion-nonfree.asc "$STUB_SYSTEM_KEYRING" || exit 77 fi for argument in "$@"; do case "$(basename -- "$argument")" in rpmfusion-free-release.rpm|rpmfusion-nonfree-release.rpm) [[ "$argument" == "$PRIVILEGED_TMPDIR"/panama-install.*/* \ && -f "$argument" ]] || exit 76 cmp -s -- "$argument" "$SIGNED_RPM" || exit 76 ;; esac done fi if [[ "$original" == *' pnpm' || "$original" == *' claude-code' \ || "$original" == *' claude-desktop-extra' ]]; then case "$original" in 'dnf install -y --repo=fedora --repo=updates --from-repo=fedora,updates pnpm'|\ dnf\ install\ -y\ --repofrompath\ panama-bound-claude-code,https://downloads.claude.ai/claude-code/rpm/stable\ --repo=panama-bound-claude-code\ --repo=fedora\ --repo=updates\ --from-repo=panama-bound-claude-code\ --setopt=panama-bound-claude-code.gpgcheck=1\ --setopt=panama-bound-claude-code.repo_gpgcheck=1\ --setopt=panama-bound-claude-code.gpgkey=file://*/panama-bound-claude-code.asc\ claude-code|\ dnf\ install\ -y\ --repofrompath\ panama-claude-desktop,https://patrickjaja.github.io/claude-desktop-extra/rpm/\ --repo=panama-claude-desktop\ --repo=fedora\ --repo=updates\ --from-repo=panama-claude-desktop\ --setopt=panama-claude-desktop.gpgcheck=1\ --setopt=panama-claude-desktop.repo_gpgcheck=1\ --setopt=panama-claude-desktop.gpgkey=file://*/claude-desktop.asc\ claude-desktop-extra) ;; *) exit 71 ;; esac fi if [[ -n "${STUB_DNF_FAIL_MATCH:-}" && "$original" == *"$STUB_DNF_FAIL_MATCH"* ]]; then if [[ "${STUB_TERRA_MUTATE_THEN_FAIL:-0}" == 1 ]]; then printf '[terra]\nenabled=1\ngpgcheck=0\nbaseurl=https://evil.invalid/\n' \ > "$STUB_ETC/yum.repos.d/terra.repo" fi exit 68 fi if [[ "${STUB_TERRA_MUTATE_ON_SUCCESS:-0}" == 1 \ && "$original" == *' terra-release' ]]; then printf '[terra]\nenabled=1\ngpgcheck=0\nbaseurl=https://evil.invalid/\n' \ > "$STUB_ETC/yum.repos.d/terra.repo" printf 'terra-release:generated-repo\n' >> "$COMMAND_LOG" fi if [[ "$original" == *' terra-release' ]]; then printf 'installed\n' > "$STUB_TERRA_RPM_STATE" fi if [[ "${1:-}" == flatpak && "${2:-}" == remote-add ]]; then if [[ ! -f "$STUB_FLATPAK_REPO/config" ]] \ || ! grep -q '^\[remote "flathub"\]$' "$STUB_FLATPAK_REPO/config"; then key='' url="${!#}" for argument in "$@"; do [[ "$argument" != --gpg-import=* ]] || key="${argument#--gpg-import=}" done [[ -n "$key" ]] || exit 69 printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=%s\ngpg-verify=true\ngpg-verify-summary=true\n' \ "$url" > "$STUB_FLATPAK_REPO/config" cp "$key" "$STUB_FLATPAK_REPO/flathub.trustedkeys.gpg" printf 'mutated\n' > "$STUB_FLATPAK_STATE" fi fi STUB cat > "$case_root/bin/dnf" <<'STUB' #!/usr/bin/env bash set -euo pipefail query='' if [[ "$*" == '--quiet --no-plugins --dump-repo-config=terra*' ]]; then query=filtered printf 'dnf:dump-terra\n' >> "$COMMAND_LOG" elif [[ "$*" == '--quiet --no-plugins --dump-repo-config=*' ]]; then query=full printf 'dnf:dump-all:locale=%s\n' "${LC_ALL:-unset}" >> "$COMMAND_LOG" fi if [[ -n "$query" ]]; then mode="${STUB_TERRA_EFFECTIVE_MODE:-auto}" if [[ "$mode" == auto ]]; then case "${STUB_TERRA_REPO_MODE:-absent}" in trusted|wrong-key) mode=trusted ;; nogpg) mode=legacy ;; wrong-url) mode=override-url ;; esac if [[ "$mode" == auto && -f "$STUB_ETC/yum.repos.d/terra.repo" ]] \ && grep -q '^baseurl=https://repos.fyralabs.com/terra44$' "$STUB_ETC/yum.repos.d/terra.repo"; then mode=trusted elif [[ "$mode" == auto ]]; then mode=absent fi fi if [[ "$query" == full ]]; then printf '======== "fedora" repository configuration: ========\n' printf 'baseurl = \nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-44-primary\n' printf 'metalink = https://mirrors.fedoraproject.org/metalink\nmirrorlist\n' printf 'pkg_gpgcheck = 0\nrepo_gpgcheck = 0\n' fi case "$mode" in absent) exit 0 ;; trusted) printf '======== "terra" repository configuration: ========\n' printf 'baseurl = https://repos.fyralabs.com/terra44\n' printf 'enabled = 1\n' printf 'gpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' ;; legacy) printf '======== "terra" repository configuration: ========\n' printf 'baseurl = \nenabled = 1\ngpgcheck = 0\n' printf 'gpgkey = https://repos.fyralabs.com/terra44.key\n' printf 'metalink = https://tetsudou.fyralabs.com/terra44\nmirrorlist = \n' printf 'pkg_gpgcheck = 0\nrepo_gpgcheck = 0\n' ;; override-url) printf '======== "terra" repository configuration: ========\n' printf 'baseurl = https://evil.invalid/terra44\nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' ;; override-gpg) printf '======== "terra" repository configuration: ========\n' printf 'baseurl = https://repos.fyralabs.com/terra44\nenabled = 1\ngpgcheck = 0\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 0\nrepo_gpgcheck = 0\n' ;; duplicate) for id in terra terra; do printf '======== "%s" repository configuration: ========\n' "$id" printf 'baseurl = https://repos.fyralabs.com/terra44\nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' done ;; alternate) for id in terra terra-legacy; do printf '======== "%s" repository configuration: ========\n' "$id" printf 'baseurl = https://repos.fyralabs.com/terra44\nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' done ;; locale-unsafe) if [[ "${LC_ALL:-}" == C ]]; then printf '======== "terra" repository configuration: ========\n' else printf '======== "terra" Repository-Konfiguration: ========\n' fi printf 'baseurl = \nenabled = 1\ngpgcheck = 0\n' printf 'gpgkey = https://repos.fyralabs.com/terra44.key\n' printf 'metalink = https://tetsudou.fyralabs.com/terra44\nmirrorlist = \n' printf 'pkg_gpgcheck = 0\nrepo_gpgcheck = 0\n' ;; localized-output) printf '======== "terra" Repository-Konfiguration: ========\n' printf 'baseurl = \nenabled = 1\ngpgcheck = 0\n' ;; uppercase|mixed-case) [[ "$query" == full ]] || exit 0 [[ "$mode" == uppercase ]] && id=TERRA || id=TeRrA printf '======== "%s" repository configuration: ========\n' "$id" printf 'baseurl = https://repos.fyralabs.com/terra44\nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' ;; mixed-alternate) ids=(terra) [[ "$query" == filtered ]] || ids+=(TeRrA-legacy) for id in "${ids[@]}"; do printf '======== "%s" repository configuration: ========\n' "$id" printf 'baseurl = https://repos.fyralabs.com/terra44\nenabled = 1\ngpgcheck = 1\n' printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n' done ;; esac exit 0 fi exit 69 STUB cat > "$case_root/bin/flatpak" <<'STUB' #!/usr/bin/env bash exit 69 STUB chmod +x "$case_root/bin"/* } reset_installer_fixture() { cp "$config" "$installer_fixture/setup/provenance/installers.conf" cp "$repo_dir"/setup/provenance/keys/*.asc "$installer_fixture/setup/provenance/keys/" } write_flathub_descriptor() { local destination="$1" key="$2" verify_line="${3:-}" url="${4:-https://dl.flathub.org/repo/}" local encoded encoded="$(base64 -w 0 "$key")" printf '[Flatpak Repo]\nTitle=Flathub\nUrl=%s\nGPGKey=%s\n%s\n' \ "$url" "$encoded" "$verify_line" > "$destination" } run_installer_function() { local name="$1" function_name="$2" case_root shift 2 local -a function_args=("$@") case_root="$test_tmp/cases/$name" if [[ "${STUB_REUSE_CASE:-}" != 1 ]]; then rm -rf -- "$case_root" fi make_stub_commands "$case_root" if [[ "${STUB_OPENH264_REPO:-}" == 1 ]]; then printf '[fedora-cisco-openh264]\nenabled=1\n' \ > "$case_root/etc/yum.repos.d/fedora-cisco-openh264.repo" fi : > "$case_root/commands.log" printf '0\n' > "$case_root/install-counter" : > "$case_root/verified-rustdesk-inode" : > "$case_root/system-keyring" [[ -e "$case_root/terra-rpm-state" ]] || : > "$case_root/terra-rpm-state" printf 'preserved\n' > "$case_root/flatpak-state" : > "$case_root/softly-failed" case "${STUB_SEED_OLD:-}" in Node) mkdir -p "$case_root/home/.nvm/versions/node/v23.0.0/bin" printf '#!/usr/bin/env bash\nprintf "v23.0.0\\n"\n' \ > "$case_root/home/.nvm/versions/node/v23.0.0/bin/node" chmod +x "$case_root/home/.nvm/versions/node/v23.0.0/bin/node" ;; Bun) mkdir -p "$case_root/home/.bun/versions/1.3.0/bin" "$case_root/home/.bun/bin" printf '#!/usr/bin/env bash\nprintf "1.3.0\\n"\n' \ > "$case_root/home/.bun/versions/1.3.0/bin/bun" chmod +x "$case_root/home/.bun/versions/1.3.0/bin/bun" ln -s "$case_root/home/.bun/versions/1.3.0/bin/bun" "$case_root/home/.bun/bin/bun" ;; Codex) mkdir -p "$case_root/home/.local/lib/panama/codex/0.149.0" "$case_root/home/.local/bin" printf '#!/usr/bin/env bash\nprintf "codex-cli 0.149.0\\n"\n' \ > "$case_root/home/.local/lib/panama/codex/0.149.0/codex" chmod +x "$case_root/home/.local/lib/panama/codex/0.149.0/codex" ln -s "$case_root/home/.local/lib/panama/codex/0.149.0/codex" \ "$case_root/home/.local/bin/codex" ;; esac case "${STUB_SEED_LEGACY:-}" in Node) mkdir -p "$case_root/home/.nvm/versions/node/v24.20.0/bin" printf '#!/usr/bin/env bash\nprintf "v24.20.0\\n"\n' \ > "$case_root/home/.nvm/versions/node/v24.20.0/bin/node" chmod +x "$case_root/home/.nvm/versions/node/v24.20.0/bin/node" ;; Bun) mkdir -p "$case_root/home/.bun/versions/1.4.0/bin" "$case_root/home/.bun/bin" printf '#!/usr/bin/env bash\nprintf "1.4.0\\n"\n' \ > "$case_root/home/.bun/versions/1.4.0/bin/bun" chmod +x "$case_root/home/.bun/versions/1.4.0/bin/bun" ln -s "$case_root/home/.bun/versions/1.4.0/bin/bun" "$case_root/home/.bun/bin/bun" ;; Codex) mkdir -p "$case_root/home/.local/lib/panama/codex/0.150.1" "$case_root/home/.local/bin" printf '#!/usr/bin/env bash\nprintf "codex-cli 0.150.1\\n"\n' \ > "$case_root/home/.local/lib/panama/codex/0.150.1/codex" chmod +x "$case_root/home/.local/lib/panama/codex/0.150.1/codex" ln -s "$case_root/home/.local/lib/panama/codex/0.150.1/codex" \ "$case_root/home/.local/bin/codex" ;; esac case "${STUB_SEED_COLLISION:-}" in Node) mkdir -p "$case_root/home/.nvm/versions/node/v24.20.0/bin" printf '#!/usr/bin/env bash\nprintf "v0.0.0\\n"\n' \ > "$case_root/home/.nvm/versions/node/v24.20.0/bin/node" chmod +x "$case_root/home/.nvm/versions/node/v24.20.0/bin/node" ;; Bun) mkdir -p "$case_root/home/.bun/versions/1.4.0/bin" printf '#!/usr/bin/env bash\nprintf "0.0.0\\n"\n' \ > "$case_root/home/.bun/versions/1.4.0/bin/bun" chmod +x "$case_root/home/.bun/versions/1.4.0/bin/bun" ;; Codex) mkdir -p "$case_root/home/.local/lib/panama/codex/0.150.1" printf '#!/usr/bin/env bash\nprintf "codex-cli 10.150.10\\n"\n' \ > "$case_root/home/.local/lib/panama/codex/0.150.1/codex" chmod +x "$case_root/home/.local/lib/panama/codex/0.150.1/codex" ;; esac write_flathub_descriptor "$case_root/flathub.flatpakrepo" \ "${STUB_FLATHUB_KEY_FILE:-$installer_fixture/setup/provenance/keys/flathub.asc}" \ "${STUB_FLATHUB_VERIFY_LINE:-}" "${STUB_FLATHUB_URL:-https://dl.flathub.org/repo/}" if [[ "${STUB_EXISTING_REPOSITORY:-}" == hyprland \ || "${STUB_PAIR_PRIOR:-}" == present && "${STUB_PAIR_NAME:-}" == hyprland ]]; then printf 'known key\n' > "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" printf 'known repo\n' > "$case_root/etc/yum.repos.d/panama-hyprland.repo" fi if [[ "${STUB_PAIR_PRIOR:-}" == present && "${STUB_PAIR_NAME:-}" == claude-code ]]; then printf 'known key\n' > "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" printf 'known repo\n' > "$case_root/etc/yum.repos.d/claude-code.repo" fi case "${STUB_TERRA_REPO_MODE:-absent}" in trusted) cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ > "$case_root/etc/yum.repos.d/terra.repo" ;; trusted-key-symlink) cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$case_root/home/terra44.asc" ln -s "$case_root/home/terra44.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ > "$case_root/etc/yum.repos.d/terra.repo" ;; trusted-repo-symlink) cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ > "$case_root/home/terra.repo" ln -s "$case_root/home/terra.repo" \ "$case_root/etc/yum.repos.d/terra.repo" ;; nogpg) printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=0\nrepo_gpgcheck=0\ngpgkey=https://repos.fyralabs.com/terra44.key\n' \ > "$case_root/etc/yum.repos.d/terra.repo" ;; wrong-url) cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" printf '[terra]\nbaseurl=https://evil.invalid/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ > "$case_root/etc/yum.repos.d/terra.repo" ;; wrong-key) cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ > "$case_root/etc/yum.repos.d/terra.repo" ;; esac case "${STUB_FLATPAK_REMOTE_MODE:-absent}" in trusted) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; trusted-config-symlink) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/home/flathub-config" ln -s "$case_root/home/flathub-config" "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; trusted-key-symlink) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/home/flathub.trustedkeys.gpg" ln -s "$case_root/home/flathub.trustedkeys.gpg" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; wrong-url) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://evil.invalid/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; wrong-key) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; no-gpg) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=false\ngpg-verify-summary=false\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; alternate-key) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\ngpgkeypath=/unreviewed/keyring.gpg\n' \ > "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; empty-alternate-key|duplicate-alternate-key|malformed-alternate-key) printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ > "$case_root/flatpak-repo/config" case "$STUB_FLATPAK_REMOTE_MODE" in empty-alternate-key) printf 'gpgkeypath=\n' ;; duplicate-alternate-key) printf 'gpgkeypath=\ngpgkeypath=/unreviewed/keyring.gpg\n' ;; malformed-alternate-key) printf 'gpgkeypath /unreviewed/keyring.gpg\n' ;; esac >> "$case_root/flatpak-repo/config" cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ "$case_root/flatpak-repo/flathub.trustedkeys.gpg" ;; esac case "${STUB_CLAUDE_DESKTOP_REPO_MODE:-absent}" in trusted) cp "$installer_fixture/setup/provenance/keys/claude-desktop.asc" \ "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-desktop-panama" printf '[claude-desktop]\nbaseurl=https://patrickjaja.github.io/claude-desktop-extra/rpm/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-claude-desktop-panama\n' \ > "$case_root/etc/yum.repos.d/claude-desktop.repo" ;; home-key|symlink-repo|metalink|mirrorlist) key_target="$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-desktop-panama" key_url='file:///etc/pki/rpm-gpg/RPM-GPG-KEY-claude-desktop-panama' if [[ "$STUB_CLAUDE_DESKTOP_REPO_MODE" == home-key ]]; then key_target="$case_root/home/claude-desktop.asc" key_url="file://$key_target" fi cp "$installer_fixture/setup/provenance/keys/claude-desktop.asc" "$key_target" repo_target="$case_root/etc/yum.repos.d/claude-desktop.repo" if [[ "$STUB_CLAUDE_DESKTOP_REPO_MODE" == symlink-repo ]]; then repo_target="$case_root/home/claude-desktop.repo" fi printf '[claude-desktop]\nbaseurl=https://patrickjaja.github.io/claude-desktop-extra/rpm/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file://%s\n' \ "${key_url#file://}" > "$repo_target" case "$STUB_CLAUDE_DESKTOP_REPO_MODE" in metalink) printf 'metalink=https://evil.invalid/metadata\n' >> "$repo_target" ;; mirrorlist) printf 'mirrorlist=https://evil.invalid/mirrors\n' >> "$repo_target" ;; symlink-repo) ln -s "$repo_target" "$case_root/etc/yum.repos.d/claude-desktop.repo" ;; esac ;; untrusted) cp "$installer_fixture/setup/provenance/keys/claude-desktop.asc" \ "$case_root/etc/pki/rpm-gpg/claude-desktop-local.asc" printf '[claude-desktop]\nbaseurl=https://evil.invalid/rpm/\nenabled=1\ngpgcheck=0\nrepo_gpgcheck=0\ngpgkey=file://%s\n' \ "$case_root/etc/pki/rpm-gpg/claude-desktop-local.asc" \ > "$case_root/etc/yum.repos.d/claude-desktop.repo" ;; esac ( COMMAND_LOG="$case_root/commands.log" \ SOFT_LOG="$case_root/softly-failed" \ ARTIFACT_ROOT="$artifact_root" \ OUTSIDE_EXECUTED="$case_root/outside-executed" \ FIXTURE_ROOT="$installer_fixture" \ REVIEWED_KEYS="$repo_dir/setup/provenance/keys" \ SIGNED_RPM="$test_tmp/signed-fixture.rpm" \ FLATHUB_DESCRIPTOR="$case_root/flathub.flatpakrepo" \ STUB_ETC="$case_root/etc" \ STUB_FLATPAK_STATE="$case_root/flatpak-state" \ STUB_FLATPAK_REPO="$case_root/flatpak-repo" \ STUB_INSTALL_COUNTER="$case_root/install-counter" \ STUB_SYSTEM_KEYRING="$case_root/system-keyring" \ STUB_TERRA_RPM_STATE="$case_root/terra-rpm-state" \ STUB_SWAP_MARKER="$case_root/swap-marker" \ VERIFIED_RUSTDESK_INODE="$case_root/verified-rustdesk-inode" \ UNSIGNED_RPM="$test_tmp/unsigned-fixture.rpm" \ LC_ALL="${STUB_CALLER_LOCALE:-C}" \ HOME="$case_root/home" \ NVM_DIR="$case_root/home/.nvm" \ TMPDIR="$case_root/tmp" \ STUB_PRIVILEGED_TMPDIR="$case_root/root-staging" \ PANAMA_PATH="$installer_fixture" \ PATH="$case_root/bin:/usr/bin:/bin" \ setsid bash -c 'source "$PANAMA_PATH/setup/scripts/install-packages"; PANAMA_SYSTEM_ETC="$STUB_ETC"; PANAMA_SYSTEM_FLATPAK_REPO="$STUB_FLATPAK_REPO"; export PRIVILEGED_TMPDIR="$STUB_PRIVILEGED_TMPDIR"; function_name="$1"; shift; declare -F "$function_name" >/dev/null; status=0; "$function_name" "$@" || status=$?; (( ${#softly_failed[@]} == 0 )) || printf "%s\n" "${softly_failed[@]}" > "$SOFT_LOG"; exit "$status"' \ bash "$function_name" "${function_args[@]}" ) > "$case_root/output" 2>&1 } assert_log() { local name="$1" expected="$2" path path="$test_tmp/cases/$name/commands.log" [[ "$(<"$path")" == "$expected" ]] || { printf 'package provenance contract: unexpected %s command log\n' "$name" >&2 diff -u <(printf '%s\n' "$expected") "$path" >&2 || true exit 1 } } assert_soft_failure() { local name="$1" component="$2" [[ "$(<"$test_tmp/cases/$name/softly-failed")" == "$component" ]] \ || fail "$name did not record exactly one $component soft failure" } assert_root_snapshot_logged() { local name="$1" file="$2" [[ "$(<"$test_tmp/cases/$name/commands.log")" == *"sudo:root-stage:$file"* \ && "$(<"$test_tmp/cases/$name/commands.log")" == *"sudo:root-verify:$file"* ]] \ || fail "$name did not reverify privileged snapshot $file" } assert_user_source_swapped() { local name="$1" [[ -s "$test_tmp/cases/$name/swap-marker" ]] \ || fail "$name did not exercise the post-snapshot source replacement adapter" } assert_no_download() { local name="$1" [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'curl:'* ]] \ || fail "$name reached curl" } assert_no_runtime_staging() { local name="$1" [[ -z "$(find "$test_tmp/cases/$name/home" \ \( -name '*.part.*' -o -name '*.stage.*' -o -name '*.link.*' \) -print -quit)" \ && -z "$(find "$test_tmp/cases/$name/tmp" -mindepth 1 -print -quit)" \ && -z "$(find "$test_tmp/cases/$name/root-staging" -mindepth 1 -print -quit)" ]] \ || fail "$name left private runtime staging behind" } assert_old_runtime_preserved() { local name="$1" component="$2" home="$test_tmp/cases/$name/home" case "$component" in Node) [[ "$($home/.nvm/versions/node/v23.0.0/bin/node --version)" == v23.0.0 ]] \ || fail "$name changed the known-good Node" ;; Bun) [[ "$(readlink "$home/.bun/bin/bun")" == \ "$home/.bun/versions/1.3.0/bin/bun" ]] \ || fail "$name changed the active Bun link" [[ "$($home/.bun/bin/bun --version)" == 1.3.0 ]] \ || fail "$name changed the known-good Bun" ;; Codex) [[ "$(readlink "$home/.local/bin/codex")" == \ "$home/.local/lib/panama/codex/0.149.0/codex" ]] \ || fail "$name changed the active Codex link" [[ "$($home/.local/bin/codex --version)" == 'codex-cli 0.149.0' ]] \ || fail "$name changed the known-good Codex" ;; esac } # Each supported architecture selects its own reviewed URL, digest and archive # layout. Successful activation leaves no private download or extraction tree. for runtime_case in \ 'node-x86_64 x86_64 install_node Node https://nodejs.org/dist/v24.20.0/node-v24.20.0-linux-x64.tar.xz 67108864' \ 'node-aarch64 aarch64 install_node Node https://nodejs.org/dist/v24.20.0/node-v24.20.0-linux-arm64.tar.xz 67108864' \ 'bun-x86_64 x86_64 install_bun Bun https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-linux-x64.zip 67108864' \ 'bun-aarch64 aarch64 install_bun Bun https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-linux-aarch64.zip 67108864' \ 'codex-x86_64 x86_64 install_codex Codex https://github.com/openai/codex/releases/download/rust-v0.150.1/codex-package-x86_64-unknown-linux-musl.tar.gz 134217728' \ 'codex-aarch64 aarch64 install_codex Codex https://github.com/openai/codex/releases/download/rust-v0.150.1/codex-package-aarch64-unknown-linux-musl.tar.gz 134217728'; do read -r name arch function_name component url max_bytes <<<"$runtime_case" reset_installer_fixture if ! STUB_ARCH="$arch" run_installer_function "$name" "$function_name"; then tail -n 120 "$test_tmp/cases/$name/output" >&2 fail "expected successful $name activation" fi grep -qFx "curl:$url:max=$max_bytes:output=artifact" \ "$test_tmp/cases/$name/commands.log" \ || { sed -n '1,80p' "$test_tmp/cases/$name/output" >&2; sed -n '1,80p' "$test_tmp/cases/$name/commands.log" >&2; fail "$name did not select its reviewed artifact"; } [[ ! -s "$test_tmp/cases/$name/softly-failed" ]] \ || fail "$name recorded a soft failure after successful activation" assert_no_runtime_staging "$name" done for receipt_spec in \ 'node-x86_64 .nvm/versions/node/v24.20.0 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2 89af8424dd53e560b1933f87ba650d8bf57c83ca5a04600eefb31f416aabbae7' \ 'node-aarch64 .nvm/versions/node/v24.20.0 5f4ddab610c1ab2016b3c227cebdbf6d9495161487e4739c7b90090595f465f7 23a5637c2470fde09fcc1acc77c1b92e04e3d7e3e6e80ff7df6f5831958d1477' \ 'bun-x86_64 .bun/versions/1.4.0 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452 33d56b070be6a9e3da0ab013038b43d1645d0534ca811ecdba4472599117eb4b' \ 'bun-aarch64 .bun/versions/1.4.0 4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e 086c4121c8738a8e0f5ed730e8a461bc3973b4444e372ddb77aef9a747fa2ae9' \ 'codex-x86_64 .local/lib/panama/codex/0.150.1 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17 abf1bb1643a79f73aa78ee627e111e02d4f8c98f25813a0cf6ce277709664386' \ 'codex-aarch64 .local/lib/panama/codex/0.150.1 1ecac3f87823efb98153233b076ea3d6e34a7a8cebe43c5285dc5f79e1514639 7a49aabe11fd95a1c968d79e16b5f1b17c3219002c5f7129d467f415f8460feb'; do read -r name target_relative artifact_digest binary_digest <<<"$receipt_spec" assert_file_bytes "$test_tmp/cases/$name/home/$target_relative/.panama-provenance" \ "$(printf 'schema=1\nartifact_sha256=%s\nbinary_sha256=%s\n' \ "$artifact_digest" "$binary_digest")" done # No-op trust comes only from directories produced and attested by a successful # installer run, never from a handcrafted executable that prints the version. for exact_spec in \ 'node-x86_64 x86_64 install_node' \ 'node-aarch64 aarch64 install_node' \ 'bun-x86_64 x86_64 install_bun' \ 'bun-aarch64 aarch64 install_bun' \ 'codex-x86_64 x86_64 install_codex' \ 'codex-aarch64 aarch64 install_codex'; do read -r name arch function_name <<<"$exact_spec" reset_installer_fixture STUB_REUSE_CASE=1 STUB_ARCH="$arch" expect_success \ run_installer_function "$name" "$function_name" assert_no_download "$name" [[ ! -s "$test_tmp/cases/$name/softly-failed" ]] \ || fail "$name rejected its installer-produced provenance receipt" done [[ "$($test_tmp/cases/node-x86_64/home/.nvm/versions/node/v24.20.0/bin/node --version)" \ == v24.20.0 ]] || fail 'x86_64 Node activation has the wrong version' grep -qFx 'nvm:alias default 24.20.0' "$test_tmp/cases/node-x86_64/commands.log" \ || fail 'Node did not set the exact nvm default alias' [[ "$($test_tmp/cases/bun-x86_64/home/.bun/bin/bun --version)" == 1.4.0 ]] \ || fail 'x86_64 Bun activation has the wrong version' [[ "$(readlink "$test_tmp/cases/bun-x86_64/home/.bun/bin/bun")" == \ "$test_tmp/cases/bun-x86_64/home/.bun/versions/1.4.0/bin/bun" ]] \ || fail 'Bun did not atomically activate the reviewed version path' [[ "$($test_tmp/cases/codex-x86_64/home/.local/bin/codex --version)" == \ 'codex-cli 0.150.1' ]] || fail 'x86_64 Codex activation has the wrong version' [[ "$(readlink "$test_tmp/cases/codex-x86_64/home/.local/bin/codex")" == \ "$test_tmp/cases/codex-x86_64/home/.local/lib/panama/codex/0.150.1/codex" ]] \ || fail 'Codex did not atomically activate the reviewed version path' # Unsupported CPUs stop before curl. RustDesk's reviewed RPM is x86_64-only, # so aarch64 is also an intentional, recorded soft failure without a download. for unsupported_case in \ 'node-unsupported install_node Node riscv64' \ 'bun-unsupported install_bun Bun riscv64' \ 'codex-unsupported install_codex Codex riscv64' \ 'rustdesk-unsupported install_rustdesk RustDesk riscv64' \ 'rustdesk-aarch64 install_rustdesk RustDesk aarch64'; do read -r name function_name component arch <<<"$unsupported_case" reset_installer_fixture STUB_ARCH="$arch" expect_failure run_installer_function "$name" "$function_name" assert_no_download "$name" assert_soft_failure "$name" "$component" done # A target can appear after the initial absence check. Activation must not # replace it or move the staged directory inside it, and active older tools # must stay selected. for component_spec in \ 'Node install_node .nvm/versions/node/v24.20.0' \ 'Bun install_bun .bun/versions/1.4.0' \ 'Codex install_codex .local/lib/panama/codex/0.150.1'; do read -r component function_name target_relative <<<"$component_spec" name="${component,,}-late-collision" reset_installer_fixture STUB_SEED_OLD="$component" STUB_LATE_COLLISION="$component" \ expect_failure run_installer_function "$name" "$function_name" target="$test_tmp/cases/$name/home/$target_relative" assert_file_bytes "$target/collision-marker" 'preserved collision' [[ -z "$(find "$target" -mindepth 1 ! -name collision-marker -print -quit)" ]] \ || fail "$name nested verified staging into the late collision" assert_old_runtime_preserved "$name" "$component" assert_soft_failure "$name" "$component" assert_no_runtime_staging "$name" done # A staged Node must prove that links stay inside the verified tree before its # executable can run. The hard-link adapter changes the extracted inode at the # filesystem boundary, which catches checks that inspect tar names only. for escape_kind in symlink hardlink; do name="node-$escape_kind-escape" reset_installer_fixture STUB_NODE_ESCAPE="$escape_kind" expect_failure \ run_installer_function "$name" install_node [[ ! -e "$test_tmp/cases/$name/outside-executed" ]] \ || fail "$name executed a Node target outside the staged tree" assert_soft_failure "$name" Node assert_no_runtime_staging "$name" done # A traversal command that cannot read/describe the staged tree is itself a # trust failure. Node traverses an unreadable subtree and Codex emits malformed # output before failing; scanners that lose the status execute the outside tool. for traversal_spec in 'Node install_node' 'Codex install_codex'; do read -r component function_name <<<"$traversal_spec" name="${component,,}-traversal-error" reset_installer_fixture STUB_TRAVERSAL_ERROR="$component" expect_failure \ run_installer_function "$name" "$function_name" grep -qFx "find:traversal-error:$component" \ "$test_tmp/cases/$name/commands.log" \ || fail "$name did not exercise the traversal error" [[ ! -e "$test_tmp/cases/$name/outside-executed" ]] \ || fail "$name executed a target hidden by a failed traversal" assert_soft_failure "$name" "$component" assert_no_runtime_staging "$name" done # A bad digest or interrupted transfer cannot replace the previously active # tool and cannot leave reusable bytes behind. for failure_mode in digest interrupted; do for component_spec in \ 'Node install_node' \ 'Bun install_bun' \ 'Codex install_codex'; do read -r component function_name <<<"$component_spec" name="${component,,}-$failure_mode" reset_installer_fixture if [[ "$failure_mode" == digest ]]; then STUB_SEED_OLD="$component" STUB_DIGEST_MISMATCH=1 \ expect_failure run_installer_function "$name" "$function_name" else STUB_SEED_OLD="$component" STUB_DOWNLOAD_INTERRUPT=1 \ expect_failure run_installer_function "$name" "$function_name" fi assert_soft_failure "$name" "$component" assert_old_runtime_preserved "$name" "$component" assert_no_runtime_staging "$name" done name="rustdesk-$failure_mode" reset_installer_fixture if [[ "$failure_mode" == digest ]]; then STUB_RUSTDESK_VERSION=1.4.8 STUB_DIGEST_MISMATCH=1 \ expect_failure run_installer_function "$name" install_rustdesk else STUB_RUSTDESK_VERSION=1.4.8 STUB_DOWNLOAD_INTERRUPT=1 \ expect_failure run_installer_function "$name" install_rustdesk fi assert_soft_failure "$name" RustDesk [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "$name reached DNF with an unverified RPM" assert_no_runtime_staging "$name" done # Deliver a real signal to each isolated installer process group while private # state exists. Cleanup must run for download, extraction, directory activation, # and active-link replacement without changing an older selected runtime. for signal_spec in \ 'node-signal-extract install_node Node extract' \ 'bun-signal-activation install_bun Bun activation' \ 'codex-signal-download install_codex Codex download' \ 'rustdesk-signal-download install_rustdesk RustDesk download' \ 'bun-signal-link install_bun Bun link' \ 'codex-signal-link install_codex Codex link'; do read -r name function_name component phase <<<"$signal_spec" reset_installer_fixture if [[ "$component" == RustDesk ]]; then STUB_RUSTDESK_VERSION=1.4.8 STUB_SIGNAL_PHASE="$phase" \ expect_failure run_installer_function "$name" "$function_name" 2>/dev/null else STUB_SEED_OLD="$component" STUB_SIGNAL_PHASE="$phase" \ expect_failure run_installer_function "$name" "$function_name" 2>/dev/null assert_old_runtime_preserved "$name" "$component" fi grep -qFx "signal:$phase" "$test_tmp/cases/$name/commands.log" \ || fail "$name did not deliver its real process-group signal" assert_no_runtime_staging "$name" done # RPM key verification owns a private RPM database. A real signal delivered # after that database exists must remove it before the isolated process exits. reset_installer_fixture STUB_SIGNAL_PHASE=rpmdb expect_failure run_installer_function rpmdb-signal \ rpm_signature_matches "$test_tmp/signed-fixture.rpm" \ "$installer_fixture/setup/provenance/keys/rpmfusion-free.asc" \ E9A491A3DE247814E7E067EAE06F8ECDD651FF2E 2>/dev/null grep -qFx 'signal:rpmdb' "$test_tmp/cases/rpmdb-signal/commands.log" \ || fail 'rpmdb-signal did not deliver its real process-group signal' assert_no_runtime_staging rpmdb-signal reset_installer_fixture STUB_SIGNAL_PHASE=gpg-home expect_failure run_installer_function gpg-home-signal \ key_fingerprint_matches \ "$installer_fixture/setup/provenance/keys/rpmfusion-free.asc" \ E9A491A3DE247814E7E067EAE06F8ECDD651FF2E 2>/dev/null grep -qFx 'signal:gpg-home' "$test_tmp/cases/gpg-home-signal/commands.log" \ || fail 'gpg-home-signal did not deliver its real process-group signal' assert_no_runtime_staging gpg-home-signal # Root staging owns cleanup before the privileged directory exists and until # reviewed snapshots have been returned to their caller. Signals in either # window must not strand a root-owned panama-install directory. reset_installer_fixture STUB_RUSTDESK_VERSION=1.4.8 STUB_SIGNAL_PHASE=root-create \ expect_failure run_installer_function root-create-signal install_rustdesk 2>/dev/null grep -qFx 'signal:root-create' "$test_tmp/cases/root-create-signal/commands.log" \ || fail 'root-create-signal did not deliver its real process-group signal' assert_no_runtime_staging root-create-signal reset_installer_fixture STUB_SIGNAL_PHASE=root-review \ expect_failure run_installer_function root-review-signal install_rpmfusion_repositories 2>/dev/null grep -qFx 'signal:root-review' "$test_tmp/cases/root-review-signal/commands.log" \ || fail 'root-review-signal did not deliver its real process-group signal' assert_no_runtime_staging root-review-signal reset_installer_fixture STUB_SIGNAL_PHASE=repo-work \ expect_failure run_installer_function repo-work-signal \ install_rpmfusion_repositories 2>/dev/null grep -qFx 'signal:repo-work' "$test_tmp/cases/repo-work-signal/commands.log" \ || fail 'repo-work-signal did not deliver its real process-group signal' assert_no_runtime_staging repo-work-signal # If privileged cleanup itself fails, do not erase the only recovery evidence. # The caller returns failure and reports the exact retained private directory. reset_installer_fixture STUB_RUSTDESK_VERSION=1.4.8 STUB_ROOT_CLEANUP_FAIL=1 \ expect_failure run_installer_function root-cleanup-failure install_rustdesk grep -qF 'Installer staging cleanup failed. Retained artifact:' \ "$test_tmp/cases/root-cleanup-failure/output" \ || fail 'root cleanup failure did not report retained evidence' [[ -n "$(find "$test_tmp/cases/root-cleanup-failure/root-staging" \ -mindepth 1 -print -quit)" ]] \ || fail 'root cleanup failure discarded its reported recovery evidence' # Successful updates keep the old version directory and switch only the active # symlink after the replacement binary has passed its version check. for component_spec in 'Bun install_bun .bun/bin/bun .bun/versions/1.4.0/bin/bun' \ 'Codex install_codex .local/bin/codex .local/lib/panama/codex/0.150.1/codex'; do read -r component function_name active_relative target_relative <<<"$component_spec" name="${component,,}-atomic-update" reset_installer_fixture STUB_SEED_OLD="$component" expect_success run_installer_function "$name" "$function_name" [[ "$(readlink "$test_tmp/cases/$name/home/$active_relative")" == \ "$test_tmp/cases/$name/home/$target_relative" ]] \ || fail "$name did not atomically replace the active symlink" if [[ "$component" == Bun ]]; then [[ "$($test_tmp/cases/$name/home/.bun/versions/1.3.0/bin/bun --version)" == 1.3.0 ]] \ || fail "$name removed the prior version directory" else [[ "$($test_tmp/cases/$name/home/.local/lib/panama/codex/0.149.0/codex --version)" \ == 'codex-cli 0.149.0' ]] || fail "$name removed the prior version directory" fi done # Receipt or executable tampering preserves the directory and leaves an older # active link unchanged. A matching version string is not an attestation. for tamper_spec in \ 'Node install_node .nvm/versions/node/v24.20.0 bin/node v24.20.0' \ 'Bun install_bun .bun/versions/1.4.0 bin/bun 1.4.0' \ 'Codex install_codex .local/lib/panama/codex/0.150.1 codex codex-cli_0.150.1'; do read -r component function_name target_relative binary_relative version_text <<<"$tamper_spec" for tamper_kind in receipt binary; do name="${component,,}-$tamper_kind-tamper" reset_installer_fixture STUB_SEED_OLD="$component" expect_success \ run_installer_function "$name" "$function_name" home="$test_tmp/cases/$name/home" target="$home/$target_relative" if [[ "$component" == Bun ]]; then ln -sfn "$home/.bun/versions/1.3.0/bin/bun" "$home/.bun/bin/bun" elif [[ "$component" == Codex ]]; then ln -sfn "$home/.local/lib/panama/codex/0.149.0/codex" "$home/.local/bin/codex" fi if [[ "$tamper_kind" == receipt ]]; then sed -i 's/^artifact_sha256=.*/artifact_sha256=0000000000000000000000000000000000000000000000000000000000000000/' \ "$target/.panama-provenance" else version_text="${version_text//_/ }" printf '#!/usr/bin/env bash\nprintf "%s\\n"\n' "$version_text" \ > "$target/$binary_relative" chmod +x "$target/$binary_relative" fi STUB_REUSE_CASE=1 expect_failure \ run_installer_function "$name" "$function_name" assert_no_download "$name" assert_soft_failure "$name" "$component" if [[ "$component" != Node ]]; then assert_old_runtime_preserved "$name" "$component" fi assert_no_runtime_staging "$name" done done # A reviewed digest does not excuse a malformed archive. Reject the wrong top # level or any extra member before a version path or active link appears. for layout_case in \ 'node-layout install_node Node .nvm/versions/node/v24.20.0' \ 'bun-layout install_bun Bun .bun/versions/1.4.0' \ 'codex-layout install_codex Codex .local/lib/panama/codex/0.150.1'; do read -r name function_name component relative_target <<<"$layout_case" reset_installer_fixture STUB_BAD_LAYOUT=1 expect_failure run_installer_function "$name" "$function_name" assert_soft_failure "$name" "$component" [[ ! -e "$test_tmp/cases/$name/home/$relative_target" ]] \ || fail "$name activated an archive with an unexpected layout" assert_no_runtime_staging "$name" done # ZIP names alone do not establish entry type. The extraction adapter # deliberately materializes all crafted entries as regular executable files, # so only central-directory attribute validation can reject them pre-extraction. for attribute_kind in symlink special directory; do name="bun-$attribute_kind-attribute" reset_installer_fixture STUB_BUN_ATTRIBUTE="$attribute_kind" expect_failure \ run_installer_function "$name" install_bun assert_soft_failure "$name" Bun [[ ! -e "$test_tmp/cases/$name/home/.bun/versions/1.4.0" ]] \ || fail "$name activated a ZIP entry with non-regular metadata" assert_no_runtime_staging "$name" done # Legacy version-only and mismatched collisions are preserved and reported; # neither can be deleted and recreated or treated as installer provenance. for collision_mode in legacy collision; do for component_spec in \ 'Node install_node' \ 'Bun install_bun' \ 'Codex install_codex'; do read -r component function_name <<<"$component_spec" name="${component,,}-$collision_mode" reset_installer_fixture if [[ "$collision_mode" == legacy ]]; then STUB_SEED_LEGACY="$component" expect_failure \ run_installer_function "$name" "$function_name" assert_soft_failure "$name" "$component" else STUB_SEED_COLLISION="$component" expect_failure \ run_installer_function "$name" "$function_name" assert_soft_failure "$name" "$component" fi assert_no_download "$name" done done reset_installer_fixture STUB_ARCH=x86_64 STUB_RUSTDESK_VERSION=1.4.8 \ expect_success run_installer_function rustdesk-x86_64 install_rustdesk assert_log rustdesk-x86_64 "$(cat <<'EXPECTED' rpm:query:rustdesk curl:https://github.com/rustdesk/rustdesk/releases/download/1.4.9/rustdesk-1.4.9-0.x86_64.rpm:max=134217728:output=rustdesk.rpm sudo:root-create sudo:root-private sudo:root-stage:rustdesk.rpm sudo:root-verify:rustdesk.rpm sudo:dnf install -y --repo=fedora --repo=updates --setopt=localpkg_gpgcheck=0 RUSTDESK_LOCAL sudo:root-cleanup EXPECTED )" [[ "$(<"$test_tmp/cases/rustdesk-x86_64/commands.log")" == *'sudo:root-stage:rustdesk.rpm'* ]] \ || fail 'RustDesk did not cross a reverified private privileged snapshot' assert_no_runtime_staging rustdesk-x86_64 reset_installer_fixture STUB_ARCH=x86_64 STUB_RUSTDESK_VERSION=1.4.8 \ STUB_SWAP_AFTER_ROOT_STAGE=rustdesk.rpm \ expect_success run_installer_function rustdesk-root-bound install_rustdesk assert_user_source_swapped rustdesk-root-bound assert_no_runtime_staging rustdesk-root-bound reset_installer_fixture STUB_ARCH=x86_64 STUB_RUSTDESK_VERSION=1.4.9 \ expect_success run_installer_function rustdesk-exact install_rustdesk assert_log rustdesk-exact 'rpm:query:rustdesk' reset_installer_fixture expect_success run_installer_function pnpm install_pnpm assert_log pnpm "$(cat <<'EXPECTED' rpm:release sudo:dnf install -y --repo=fedora --repo=updates --from-repo=fedora,updates pnpm EXPECTED )" reset_installer_fixture mkdir -p "$installer_fixture/setup/packages" printf 'fixture-package\n' >"$installer_fixture/setup/packages/base-fixture" expect_success run_installer_function base-list install_list base-fixture Base assert_log base-list "$(cat <<'EXPECTED' sudo:dnf install -y --repo=fedora --repo=updates --from-repo=fedora,updates --skip-unavailable fixture-package rpm:query:fixture-package EXPECTED )" # Mixed package lists are split before DNF sees them. Publisher-exclusive names # are singularly bound to that publisher, while ordinary Fedora/RPM Fusion # packages never admit Terra, the Hyprland COPR, or Cisco as alternate sources. reset_installer_fixture cat > "$installer_fixture/setup/packages/desktop-source-fixture" <<'FIXTURE' NetworkManager cascadiamono-nerd-fonts espanso-wayland firamono-nerd-fonts ghostty jetbrainsmono-nerd-fonts nautilus-open-any-terminal victormono-nerd-fonts gstreamer1-plugin-openh264 mozilla-openh264 FIXTURE STUB_OPENH264_REPO=1 expect_success run_installer_function desktop-source-split \ install_desktop_package_file \ "$installer_fixture/setup/packages/desktop-source-fixture" assert_log desktop-source-split "$(cat <<'EXPECTED' sudo:dnf install -y --repo=fedora --repo=updates --repo=rpmfusion-free --repo=rpmfusion-free-updates --repo=rpmfusion-nonfree --repo=rpmfusion-nonfree-updates --repo=fedora-cisco-openh264 --from-repo=fedora,updates --skip-unavailable NetworkManager gstreamer1-plugin-openh264 sudo:root-create sudo:root-private sudo:root-stage:panama-bound-terra.asc sudo:root-verify:panama-bound-terra.asc sudo:root-reviewable gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:root-private sudo:dnf install -y --repofrompath panama-bound-terra,https://repos.fyralabs.com/terra44 --repo=panama-bound-terra --repo=fedora --repo=updates --from-repo=panama-bound-terra --setopt=panama-bound-terra.gpgcheck=1 --setopt=panama-bound-terra.repo_gpgcheck=1 --setopt=panama-bound-terra.gpgkey=file://BOUND_KEY --skip-unavailable cascadiamono-nerd-fonts espanso-wayland firamono-nerd-fonts ghostty jetbrainsmono-nerd-fonts nautilus-open-any-terminal victormono-nerd-fonts sudo:root-cleanup sudo:dnf install -y --repo=fedora --repo=updates --repo=fedora-cisco-openh264 --from-repo=fedora-cisco-openh264 --skip-unavailable mozilla-openh264 rpm:query:NetworkManager rpm:query:cascadiamono-nerd-fonts rpm:query:espanso-wayland rpm:query:firamono-nerd-fonts rpm:query:ghostty rpm:query:jetbrainsmono-nerd-fonts rpm:query:nautilus-open-any-terminal rpm:query:victormono-nerd-fonts rpm:query:gstreamer1-plugin-openh264 rpm:query:mozilla-openh264 EXPECTED )" reset_installer_fixture cat > "$installer_fixture/setup/packages/hyprland-source-fixture" <<'FIXTURE' NetworkManager gpu-screen-recorder grimblast helium-browser-bin hypridle hyprland hyprland-guiutils hyprland-uwsm hyprlock hyprpaper hyprpicker hyprpolkitagent hyprpwcenter hyprshutdown hyprsunset hyprsysteminfo mpvpaper quickshell satty uwsm vicinae xdg-desktop-portal-hyprland FIXTURE expect_success run_installer_function hyprland-source-split \ install_hyprland_package_file \ "$installer_fixture/setup/packages/hyprland-source-fixture" assert_log hyprland-source-split "$(cat <<'EXPECTED' sudo:dnf install -y --repo=fedora --repo=updates --repo=rpmfusion-free --repo=rpmfusion-free-updates --repo=rpmfusion-nonfree --repo=rpmfusion-nonfree-updates --from-repo=fedora,updates --setopt=install_weak_deps=False NetworkManager sudo:root-create sudo:root-private sudo:root-stage:panama-bound-hyprland.asc sudo:root-verify:panama-bound-hyprland.asc sudo:root-reviewable gpg:fingerprint:97E23476C89635135407C7D5E9BA41342C4B2995 sudo:root-private sudo:dnf install -y --repofrompath panama-bound-hyprland,https://download.copr.fedorainfracloud.org/results/lionheartp/Hyprland/fedora-$releasever-$basearch/ --repo=panama-bound-hyprland --repo=fedora --repo=updates --from-repo=panama-bound-hyprland --setopt=panama-bound-hyprland.gpgcheck=1 --setopt=panama-bound-hyprland.repo_gpgcheck=0 --setopt=panama-bound-hyprland.gpgkey=file://BOUND_KEY --setopt=install_weak_deps=False gpu-screen-recorder grimblast hypridle hyprland hyprland-guiutils hyprland-uwsm hyprlock hyprpaper hyprpicker hyprpolkitagent hyprpwcenter hyprshutdown hyprsunset hyprsysteminfo quickshell uwsm xdg-desktop-portal-hyprland sudo:root-cleanup sudo:root-create sudo:root-private sudo:root-stage:panama-bound-terra.asc sudo:root-verify:panama-bound-terra.asc sudo:root-reviewable gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:root-private sudo:dnf install -y --repofrompath panama-bound-terra,https://repos.fyralabs.com/terra44 --repo=panama-bound-terra --repo=fedora --repo=updates --from-repo=panama-bound-terra --setopt=panama-bound-terra.gpgcheck=1 --setopt=panama-bound-terra.repo_gpgcheck=1 --setopt=panama-bound-terra.gpgkey=file://BOUND_KEY --setopt=install_weak_deps=False helium-browser-bin mpvpaper satty vicinae sudo:root-cleanup rpm:query:NetworkManager rpm:query:gpu-screen-recorder rpm:query:grimblast rpm:query:helium-browser-bin rpm:query:hypridle rpm:query:hyprland rpm:query:hyprland-guiutils rpm:query:hyprland-uwsm rpm:query:hyprlock rpm:query:hyprpaper rpm:query:hyprpicker rpm:query:hyprpolkitagent rpm:query:hyprpwcenter rpm:query:hyprshutdown rpm:query:hyprsunset rpm:query:hyprsysteminfo rpm:query:mpvpaper rpm:query:quickshell rpm:query:satty rpm:query:uwsm rpm:query:vicinae rpm:query:xdg-desktop-portal-hyprland EXPECTED )" for bound_repo in panama-bound-hyprland panama-bound-terra; do grep -q -- "--repofrompath $bound_repo," \ "$test_tmp/cases/hyprland-source-split/commands.log" \ || fail "Hyprland source split did not bind $bound_repo to its reviewed URL" done reset_installer_fixture STUB_DNF_FAIL_MATCH=pnpm expect_failure run_installer_function pnpm-failure install_pnpm assert_soft_failure pnpm-failure pnpm reset_installer_fixture expect_success run_installer_function rpmfusion install_rpmfusion_repositories assert_log rpmfusion "$(cat <<'EXPECTED' rpm:release curl:https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-44.noarch.rpm:max=4194304:output=rpmfusion-free-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-free-release.rpm sudo:root-verify:rpmfusion-free-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-free.asc sudo:root-verify:rpmfusion-free.asc sudo:root-reviewable sudo:root-reviewable gpg:fingerprint:E9A491A3DE247814E7E067EAE06F8ECDD651FF2E rpmkeys:import:rpmfusion-free.asc rpmkeys:checksig:rpmfusion-free-release.rpm sudo:root-private sudo:root-private curl:https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-44.noarch.rpm:max=4194304:output=rpmfusion-nonfree-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-nonfree-release.rpm sudo:root-verify:rpmfusion-nonfree-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-nonfree.asc sudo:root-verify:rpmfusion-nonfree.asc sudo:root-reviewable sudo:root-reviewable gpg:fingerprint:79BDB88F9BBF73910FD4095B6A2AF96194843C65 rpmkeys:import:rpmfusion-nonfree.asc rpmkeys:checksig:rpmfusion-nonfree-release.rpm sudo:root-private sudo:root-private sudo:rpm-import:rpmfusion-free.asc sudo:rpm-import:rpmfusion-nonfree.asc sudo:dnf install -y --repo=fedora --repo=updates --setopt=localpkg_gpgcheck=1 RPMFUSION_FREE RPMFUSION_NONFREE sudo:root-cleanup sudo:root-cleanup sudo:root-cleanup sudo:root-cleanup EXPECTED )" assert_root_snapshot_logged rpmfusion rpmfusion-free-release.rpm assert_root_snapshot_logged rpmfusion rpmfusion-nonfree-release.rpm # DNF checks command-line RPMs against the system RPM keyring, not the private # verification database. A fresh Fedora keyring therefore needs the two # already-reviewed root snapshots imported before localpkg_gpgcheck runs. reset_installer_fixture STUB_REQUIRE_RPMFUSION_SYSTEM_KEYS=1 \ expect_success run_installer_function rpmfusion-fresh-keyring \ install_rpmfusion_repositories reset_installer_fixture STUB_SWAP_AFTER_ROOT_STAGE=rpmfusion-free-release.rpm \ expect_success run_installer_function rpmfusion-root-bound install_rpmfusion_repositories assert_user_source_swapped rpmfusion-root-bound assert_no_runtime_staging rpmfusion-root-bound reset_installer_fixture STUB_TOGGLE_KEY_VERIFY=rpmfusion-free.asc \ expect_failure run_installer_function rpmfusion-key-toggle install_rpmfusion_repositories assert_user_source_swapped rpmfusion-key-toggle [[ "$(<"$test_tmp/cases/rpmfusion-key-toggle/commands.log")" != *'sudo:dnf'* ]] \ || fail 'same-UID RPM Fusion key toggle reached package activation' assert_no_runtime_staging rpmfusion-key-toggle reset_installer_fixture expect_success run_installer_function terra install_terra_repository assert_log terra "$(cat <<'EXPECTED' rpm:release dnf:dump-all:locale=C sudo:root-create sudo:root-private sudo:root-stage:terra44.asc sudo:root-verify:terra44.asc sudo:root-reviewable gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:root-private sudo:root-verify:terra44.asc sudo:root-create sudo:root-private sudo:root-stage:terra.repo sudo:root-verify:terra.repo sudo:install:terra44.asc:/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama sudo:install:terra.repo:/etc/yum.repos.d/terra.repo dnf:dump-all:locale=C gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:root-cleanup sudo:root-cleanup EXPECTED )" cmp -s "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$test_tmp/cases/terra/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" \ || fail 'Terra privileged install did not preserve the fully staged reviewed key' assert_file_bytes "$test_tmp/cases/terra/etc/yum.repos.d/terra.repo" "$(cat <<'EXPECTED' [terra] name=Panama reviewed Terra 44 baseurl=https://repos.fyralabs.com/terra44 enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama EXPECTED )" assert_root_snapshot_logged terra terra44.asc assert_root_snapshot_logged terra terra.repo for terra_snapshot in terra44.asc terra.repo; do reset_installer_fixture name="terra-root-bound-${terra_snapshot%.*}" STUB_SWAP_AFTER_ROOT_STAGE="$terra_snapshot" \ expect_success run_installer_function "$name" install_terra_repository assert_user_source_swapped "$name" assert_no_runtime_staging "$name" done reset_installer_fixture expect_success run_installer_function hyprland configure_hyprland_repository assert_log hyprland "$(cat <<'EXPECTED' rpm:release sudo:root-create sudo:root-private sudo:root-stage:hyprland-copr.asc sudo:root-verify:hyprland-copr.asc sudo:root-reviewable gpg:fingerprint:97E23476C89635135407C7D5E9BA41342C4B2995 sudo:root-private sudo:root-verify:hyprland-copr.asc sudo:root-create sudo:root-private sudo:root-stage:panama-hyprland.repo sudo:root-verify:panama-hyprland.repo sudo:install:hyprland-copr.asc:/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland sudo:install:panama-hyprland.repo:/etc/yum.repos.d/panama-hyprland.repo sudo:root-cleanup sudo:root-cleanup EXPECTED )" cmp -s "$installer_fixture/setup/provenance/keys/hyprland-copr.asc" \ "$test_tmp/cases/hyprland/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ || fail 'Hyprland privileged install did not preserve the fully staged reviewed key' assert_file_bytes "$test_tmp/cases/hyprland/etc/yum.repos.d/panama-hyprland.repo" "$(cat <<'EXPECTED' [panama-hyprland] name=Panama reviewed Hyprland COPR baseurl=https://download.copr.fedorainfracloud.org/results/lionheartp/Hyprland/fedora-$releasever-$basearch/ enabled=1 gpgcheck=1 repo_gpgcheck=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland EXPECTED )" assert_root_snapshot_logged hyprland hyprland-copr.asc assert_root_snapshot_logged hyprland panama-hyprland.repo # The expected root-snapshot digests must already be bound to the fingerprinted # key and the script-authored repository stream. A swap before root staging may # not become the new expected digest. for pre_snapshot_spec in \ 'authored-repo STUB_SWAP_AFTER_REPO_WRITE panama-hyprland.repo'; do read -r suffix swap_name swap_target <<<"$pre_snapshot_spec" reset_installer_fixture name="hyprland-pre-snapshot-$suffix" printf -v "$swap_name" '%s' "$swap_target" export "$swap_name" expect_failure run_installer_function "$name" configure_hyprland_repository unset "$swap_name" assert_user_source_swapped "$name" [[ ! -e "$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ && ! -e "$test_tmp/cases/$name/etc/yum.repos.d/panama-hyprland.repo" ]] \ || fail "$name published bytes swapped before privileged staging" assert_no_runtime_staging "$name" done # A same-UID attacker can present malicious bytes to both digest reads while # presenting reviewed bytes only to GPG. Verification must therefore inspect # an immutable root-owned snapshot, not a toggled user pathname. reset_installer_fixture STUB_TOGGLE_KEY_VERIFY=hyprland-copr.asc \ expect_failure run_installer_function hyprland-key-toggle configure_hyprland_repository assert_user_source_swapped hyprland-key-toggle [[ ! -e "$test_tmp/cases/hyprland-key-toggle/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ && ! -e "$test_tmp/cases/hyprland-key-toggle/etc/yum.repos.d/panama-hyprland.repo" ]] \ || fail 'same-UID key toggle published attacker-controlled bytes' assert_no_runtime_staging hyprland-key-toggle for pair_snapshot in hyprland-copr.asc panama-hyprland.repo; do reset_installer_fixture name="hyprland-root-bound-${pair_snapshot%.*}" STUB_SWAP_AFTER_ROOT_STAGE="$pair_snapshot" \ expect_success run_installer_function "$name" configure_hyprland_repository assert_user_source_swapped "$name" cmp -s "$test_tmp/cases/hyprland/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ "$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ || fail "$name privileged key bytes differed after the source swap" cmp -s "$test_tmp/cases/hyprland/etc/yum.repos.d/panama-hyprland.repo" \ "$test_tmp/cases/$name/etc/yum.repos.d/panama-hyprland.repo" \ || fail "$name privileged repository bytes differed after the source swap" assert_no_runtime_staging "$name" done reset_installer_fixture expect_success run_installer_function flathub ensure_flathub_remote assert_log flathub "$(cat <<'EXPECTED' rpm:release curl:https://flathub.org/repo/flathub.flatpakrepo:max=1048576:output=flathub.flatpakrepo sudo:root-create sudo:root-private sudo:root-stage:flathub-key.asc sudo:root-verify:flathub-key.asc sudo:root-reviewable gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE sudo:root-private sudo:flatpak remote-add --if-not-exists --gpg-import=FLATHUB_KEY flathub https://dl.flathub.org/repo/ gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE sudo:root-cleanup EXPECTED )" assert_root_snapshot_logged flathub flathub-key.asc reset_installer_fixture STUB_SWAP_AFTER_ROOT_STAGE=flathub-key.asc \ expect_success run_installer_function flathub-root-bound ensure_flathub_remote assert_user_source_swapped flathub-root-bound assert_no_runtime_staging flathub-root-bound reset_installer_fixture STUB_TOGGLE_KEY_VERIFY=flathub-key.asc \ expect_failure run_installer_function flathub-key-toggle ensure_flathub_remote assert_user_source_swapped flathub-key-toggle assert_file_bytes "$test_tmp/cases/flathub-key-toggle/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/flathub-key-toggle/commands.log")" != *'sudo:flatpak'* ]] \ || fail 'same-UID Flathub key toggle reached remote activation' assert_no_runtime_staging flathub-key-toggle reset_installer_fixture expect_success run_installer_function claude-code install_claude_code assert_log claude-code "$(cat <<'EXPECTED' rpm:release sudo:root-create sudo:root-private sudo:root-stage:claude-code.asc sudo:root-verify:claude-code.asc sudo:root-reviewable gpg:fingerprint:31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE sudo:root-private sudo:root-verify:claude-code.asc sudo:root-create sudo:root-private sudo:root-stage:claude-code.repo sudo:root-verify:claude-code.repo sudo:install:claude-code.asc:/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama sudo:install:claude-code.repo:/etc/yum.repos.d/claude-code.repo sudo:root-cleanup sudo:root-cleanup sudo:root-create sudo:root-private sudo:root-stage:panama-bound-claude-code.asc sudo:root-verify:panama-bound-claude-code.asc sudo:root-reviewable gpg:fingerprint:31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE sudo:root-private sudo:dnf install -y --repofrompath panama-bound-claude-code,https://downloads.claude.ai/claude-code/rpm/stable --repo=panama-bound-claude-code --repo=fedora --repo=updates --from-repo=panama-bound-claude-code --setopt=panama-bound-claude-code.gpgcheck=1 --setopt=panama-bound-claude-code.repo_gpgcheck=1 --setopt=panama-bound-claude-code.gpgkey=file://BOUND_KEY claude-code sudo:root-cleanup EXPECTED )" cmp -s "$installer_fixture/setup/provenance/keys/claude-code.asc" \ "$test_tmp/cases/claude-code/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" \ || fail 'Claude Code privileged install did not preserve the fully staged reviewed key' assert_file_bytes "$test_tmp/cases/claude-code/etc/yum.repos.d/claude-code.repo" "$(cat <<'EXPECTED' [claude-code] name=Claude Code baseurl=https://downloads.claude.ai/claude-code/rpm/stable enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama EXPECTED )" assert_root_snapshot_logged claude-code claude-code.asc assert_root_snapshot_logged claude-code claude-code.repo grep -q -- '--repofrompath panama-bound-claude-code,' \ "$test_tmp/cases/claude-code/commands.log" \ || fail 'Claude Code install did not bind the reviewed repository URL' for pair_snapshot in claude-code.asc claude-code.repo; do reset_installer_fixture name="claude-code-root-bound-${pair_snapshot%.*}" STUB_SWAP_AFTER_ROOT_STAGE="$pair_snapshot" \ expect_success run_installer_function "$name" install_claude_code assert_user_source_swapped "$name" cmp -s "$test_tmp/cases/claude-code/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" \ "$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" \ || fail "$name privileged key bytes differed after the source swap" cmp -s "$test_tmp/cases/claude-code/etc/yum.repos.d/claude-code.repo" \ "$test_tmp/cases/$name/etc/yum.repos.d/claude-code.repo" \ || fail "$name privileged repository bytes differed after the source swap" assert_no_runtime_staging "$name" done reset_installer_fixture expect_success run_installer_function claude-desktop-absent install_claude_desktop_if_trusted assert_log claude-desktop-absent 'rpm:release' [[ "$(grep -c 'Claude Desktop is optional; configure its reviewed local-key repository manually' \ "$test_tmp/cases/claude-desktop-absent/output")" -eq 1 ]] \ || fail 'absent Claude Desktop repository did not produce exactly one manual message' # Manual configuration is only an operator-consent gate. User-owned keys, # symlinked repo files, and alternate metadata sources must never become the # privileged DNF trust source even when their visible values look reviewed. for mode in home-key symlink-repo metalink mirrorlist; do reset_installer_fixture name="claude-desktop-$mode" STUB_CLAUDE_DESKTOP_REPO_MODE="$mode" \ expect_success run_installer_function "$name" install_claude_desktop_if_trusted [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:dnf'* ]] \ || fail "Claude Desktop $mode configuration reached package activation" done reset_installer_fixture STUB_CLAUDE_DESKTOP_REPO_MODE=trusted \ expect_success run_installer_function claude-desktop-trusted install_claude_desktop_if_trusted assert_log claude-desktop-trusted "$(cat <<'EXPECTED' rpm:release gpg:fingerprint:825A7D15D78BABE45646D5DF382409F597908867 sudo:root-create sudo:root-private sudo:root-stage:claude-desktop.asc sudo:root-verify:claude-desktop.asc sudo:root-reviewable gpg:fingerprint:825A7D15D78BABE45646D5DF382409F597908867 sudo:root-private sudo:dnf install -y --repofrompath panama-claude-desktop,https://patrickjaja.github.io/claude-desktop-extra/rpm/ --repo=panama-claude-desktop --repo=fedora --repo=updates --from-repo=panama-claude-desktop --setopt=panama-claude-desktop.gpgcheck=1 --setopt=panama-claude-desktop.repo_gpgcheck=1 --setopt=panama-claude-desktop.gpgkey=file://CLAUDE_DESKTOP_KEY claude-desktop-extra sudo:root-cleanup EXPECTED )" assert_root_snapshot_logged claude-desktop-trusted claude-desktop.asc assert_no_runtime_staging claude-desktop-trusted # Existing repository state is part of the trust boundary. Idempotency is only # success when the already-active repository matches the reviewed policy. reset_installer_fixture STUB_FLATPAK_REMOTE_MODE=trusted \ expect_success run_installer_function flathub-existing-trusted ensure_flathub_remote assert_log flathub-existing-trusted "$(cat <<'EXPECTED' rpm:release gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE EXPECTED )" assert_file_bytes "$test_tmp/cases/flathub-existing-trusted/flatpak-state" 'preserved' for mode in wrong-url wrong-key no-gpg alternate-key empty-alternate-key \ duplicate-alternate-key malformed-alternate-key trusted-config-symlink \ trusted-key-symlink; do reset_installer_fixture name="flathub-existing-$mode" STUB_FLATPAK_REMOTE_MODE="$mode" \ expect_failure run_installer_function "$name" ensure_flathub_remote assert_file_bytes "$test_tmp/cases/$name/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'curl:'* \ && "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "untrusted existing Flathub $mode state was changed" done reset_installer_fixture STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE=trusted \ expect_success run_installer_function terra-existing-trusted install_terra_repository assert_log terra-existing-trusted "$(cat <<'EXPECTED' rpm:release dnf:dump-all:locale=C gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F EXPECTED )" for mode in nogpg wrong-url wrong-key trusted-key-symlink trusted-repo-symlink; do reset_installer_fixture name="terra-existing-$mode" STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE="$mode" \ expect_failure run_installer_function "$name" install_terra_repository [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "untrusted existing Terra $mode state reached a mutation" done # A historical terra-release package without an active repo is recoverable: # direct reviewed-pair publication does not depend on or mutate package state. reset_installer_fixture STUB_TERRA_INSTALLED=1 \ expect_success run_installer_function terra-existing-package-only \ install_terra_repository [[ "$(<"$test_tmp/cases/terra-existing-package-only/commands.log")" != *'sudo:dnf'* ]] \ || fail 'historical terra-release state triggered a bootstrap DNF transaction' # An optional security field may be absent, but duplicates are malformed even # when one copy looks safe. These cases catch the absent/duplicate conflation. for duplicate_case in \ $'duplicate-no-gpg NoGPGVerify=false\nNoGPGVerify=true' \ $'duplicate-gpg-verify GPGVerify=true\nGPGVerify=false' \ 'alternate-gpg-key-path GPGKeyPath=/unreviewed/keyring.gpg' \ 'empty-gpg-key-path GPGKeyPath=' \ $'duplicate-gpg-key-path GPGKeyPath=\nGPGKeyPath=/unreviewed/keyring.gpg' \ 'malformed-gpg-key-path GPGKeyPath /unreviewed/keyring.gpg'; do name="${duplicate_case%% *}" lines="${duplicate_case#* }" reset_installer_fixture STUB_FLATHUB_VERIFY_LINE="$lines" \ expect_failure run_installer_function "$name" ensure_flathub_remote assert_file_bytes "$test_tmp/cases/$name/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "$name descriptor reached remote activation" done # DNF's non-networking effective configuration dump, rather than any one repo # file, decides whether Terra is absent, trusted, overridden, or duplicated. reset_installer_fixture STUB_TERRA_EFFECTIVE_MODE=absent \ expect_success run_installer_function terra-effective-absent preflight_terra_trust assert_log terra-effective-absent 'dnf:dump-all:locale=C' reset_installer_fixture STUB_TERRA_EFFECTIVE_MODE=trusted STUB_TERRA_REPO_MODE=trusted \ expect_success run_installer_function terra-effective-trusted preflight_terra_trust assert_log terra-effective-trusted "$(cat <<'EXPECTED' dnf:dump-all:locale=C gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F EXPECTED )" for mode in legacy override-url override-gpg duplicate alternate; do reset_installer_fixture name="terra-effective-$mode" status=0 STUB_TERRA_EFFECTIVE_MODE="$mode" STUB_TERRA_REPO_MODE=trusted \ run_installer_function "$name" preflight_terra_trust || status=$? [[ "$status" -eq 78 ]] \ || fail "effective Terra $mode returned $status instead of hard trust status 78" [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "effective Terra $mode reached a transaction" done # The full effective configuration must reveal case variants and mixed-case # alternate identities that a lowercase terra* selector omits. for identity_case in uppercase mixed-case mixed-alternate; do reset_installer_fixture name="terra-effective-$identity_case" status=0 STUB_TERRA_EFFECTIVE_MODE="$identity_case" STUB_TERRA_REPO_MODE=trusted \ run_installer_function "$name" preflight_terra_trust || status=$? [[ "$status" -eq 78 ]] \ || fail "effective Terra $identity_case returned $status instead of hard trust status 78" done # DNF output must be locale-stable, and nonempty output that does not match the # machine format is unsafe rather than equivalent to a fresh host. for locale_case in locale-unsafe localized-output; do reset_installer_fixture name="terra-effective-$locale_case" status=0 STUB_CALLER_LOCALE=C.UTF-8 STUB_TERRA_EFFECTIVE_MODE="$locale_case" \ run_installer_function "$name" preflight_terra_trust || status=$? [[ "$status" -eq 78 ]] \ || fail "effective Terra $locale_case returned $status instead of hard trust status 78" assert_log "$name" 'dnf:dump-all:locale=C' done assert_pair_rollback() { local name="$1" pair="$2" prior="$3" key repo case "$pair" in hyprland) key="$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" repo="$test_tmp/cases/$name/etc/yum.repos.d/panama-hyprland.repo" ;; claude-code) key="$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" repo="$test_tmp/cases/$name/etc/yum.repos.d/claude-code.repo" ;; esac if [[ "$prior" == present ]]; then assert_file_bytes "$key" 'known key' assert_file_bytes "$repo" 'known repo' else [[ ! -e "$key" && ! -e "$repo" ]] \ || fail "$pair activation failure left part of an absent pair" fi [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:dnf'* ]] \ || fail "$pair activation failure reached DNF" } # Both activation writes can fail after changing their target. Each repository # must restore known-good pairs and return prior-absent pairs to full absence. for pair_spec in \ 'hyprland configure_hyprland_repository' \ 'claude-code install_claude_code'; do read -r pair function_name <<<"$pair_spec" for prior in absent present; do for fail_at in 1 2; do reset_installer_fixture name="$pair-$prior-activation-$fail_at" STUB_PAIR_NAME="$pair" STUB_PAIR_PRIOR="$prior" STUB_INSTALL_FAIL_AT="$fail_at" \ expect_failure run_installer_function "$name" "$function_name" assert_pair_rollback "$name" "$pair" "$prior" done done done # Rollback must be uninterruptible once a signal starts it. Deliver a second # TERM while the first prior file is being restored and require the complete # prior pair, not a half-restored trust root. reset_installer_fixture STUB_PAIR_NAME=claude-code STUB_PAIR_PRIOR=present \ STUB_SIGNAL_PAIR_AFTER_FIRST=1 STUB_SIGNAL_PAIR_TWICE=1 \ expect_failure run_installer_function claude-code-present-double-signal \ install_claude_code 2>/dev/null grep -qFx 'signal:repository-pair-second' \ "$test_tmp/cases/claude-code-present-double-signal/commands.log" \ || fail 'double-signal case did not deliver the rollback signal' assert_pair_rollback claude-code-present-double-signal claude-code present assert_no_runtime_staging claude-code-present-double-signal # Recovery evidence is intentionally retained when rollback itself fails, and # that trust-root failure must cross the public optional-install wrapper as 78. # Converting it to an ordinary soft failure would let the package stage keep # running DNF transactions after a repository pair was left indeterminate. reset_installer_fixture rollback_failure_status=0 STUB_PAIR_NAME=claude-code STUB_PAIR_PRIOR=absent STUB_INSTALL_FAIL_AT=2 \ STUB_ROLLBACK_FAIL=1 \ run_installer_function claude-code-rollback-failure install_claude_code \ || rollback_failure_status=$? [[ "$rollback_failure_status" -eq 78 ]] \ || fail "Claude Code rollback failure returned $rollback_failure_status instead of 78" [[ -z "$(find "$test_tmp/cases/claude-code-rollback-failure/root-staging" \ -mindepth 1 -print -quit)" ]] \ && fail 'Claude Code rollback failure discarded its recovery evidence' [[ ! -s "$test_tmp/cases/claude-code-rollback-failure/softly-failed" ]] \ || fail 'Claude Code rollback failure was downgraded to a soft failure' agent_boundary_status=0 run_installer_function agent-trust-boundary exercise_agent_install_boundary \ || agent_boundary_status=$? [[ "$agent_boundary_status" -eq 78 ]] \ || fail "agent install trust boundary returned $agent_boundary_status instead of 78" assert_log agent-trust-boundary "$(cat <<'EXPECTED' agent:node agent:pnpm agent:bun agent:claude:78 EXPECTED )" # A real signal between the two activation writes follows the same rollback # path as an ordinary failure, for both prior-present and prior-absent pairs. for pair_spec in \ 'hyprland configure_hyprland_repository' \ 'claude-code install_claude_code'; do read -r pair function_name <<<"$pair_spec" for prior in absent present; do reset_installer_fixture name="$pair-$prior-signal-after-first" STUB_PAIR_NAME="$pair" STUB_PAIR_PRIOR="$prior" \ STUB_SIGNAL_PAIR_AFTER_FIRST=1 \ expect_failure run_installer_function "$name" "$function_name" 2>/dev/null grep -qFx 'signal:repository-pair' "$test_tmp/cases/$name/commands.log" \ || fail "$name did not deliver its real process-group signal" assert_pair_rollback "$name" "$pair" "$prior" assert_no_runtime_staging "$name" done done # A Fedora version outside the reviewed policy stops every public transaction # before curl, sudo, Flatpak, or repository inspection can act. for function_name in install_rpmfusion_repositories install_terra_repository \ configure_hyprland_repository ensure_flathub_remote install_pnpm install_claude_code \ install_claude_desktop_if_trusted; do reset_installer_fixture name="wrong-fedora-${function_name}" STUB_FEDORA_RELEASE=45 expect_failure run_installer_function "$name" "$function_name" assert_log "$name" 'rpm:release' if [[ "$function_name" == install_claude_code ]]; then assert_soft_failure "$name" 'Claude Code' elif [[ "$function_name" == install_pnpm ]]; then assert_soft_failure "$name" pnpm fi done reset_installer_fixture sed -i 's#^RPMFUSION_FREE_RELEASE_URL=.*#RPMFUSION_FREE_RELEASE_URL=https://evil.invalid/free.rpm#' \ "$installer_fixture/setup/provenance/installers.conf" expect_failure run_installer_function rpmfusion-wrong-url install_rpmfusion_repositories assert_log rpmfusion-wrong-url 'rpm:release' for policy_case in \ 'terra-wrong-url TERRA_BASEURL install_terra_repository' \ 'hyprland-wrong-url HYPRLAND_COPR_BASEURL configure_hyprland_repository' \ 'flathub-wrong-url FLATHUB_DESCRIPTOR_URL ensure_flathub_remote' \ 'claude-code-wrong-url CLAUDE_CODE_BASEURL install_claude_code' \ 'claude-desktop-wrong-url CLAUDE_DESKTOP_BASEURL install_claude_desktop_if_trusted'; do read -r name config_name function_name <<<"$policy_case" reset_installer_fixture sed -i "s#^${config_name}=.*#${config_name}=https://evil.invalid/#" \ "$installer_fixture/setup/provenance/installers.conf" expect_failure run_installer_function "$name" "$function_name" [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'curl:'* \ && "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ || fail "$config_name mismatch reached a download or mutation" done reset_installer_fixture cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$installer_fixture/setup/provenance/keys/rpmfusion-free.asc" expect_failure run_installer_function rpmfusion-wrong-key install_rpmfusion_repositories assert_log rpmfusion-wrong-key "$(cat <<'EXPECTED' rpm:release curl:https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-44.noarch.rpm:max=4194304:output=rpmfusion-free-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-free-release.rpm sudo:root-verify:rpmfusion-free-release.rpm sudo:root-create sudo:root-private sudo:root-stage:rpmfusion-free.asc sudo:root-verify:rpmfusion-free.asc sudo:root-reviewable sudo:root-reviewable gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:root-private sudo:root-private sudo:root-cleanup sudo:root-cleanup EXPECTED )" reset_installer_fixture STUB_RPM_SIGNATURE_FAIL=rpmfusion-free-release.rpm \ expect_failure run_installer_function rpmfusion-bad-signature install_rpmfusion_repositories [[ "$(<"$test_tmp/cases/rpmfusion-bad-signature/commands.log")" != *'rpmfusion-nonfree'* ]] \ || fail 'RPM Fusion signature failure did not stop the dependent download' [[ "$(<"$test_tmp/cases/rpmfusion-bad-signature/commands.log")" != *'sudo:dnf'* ]] \ || fail 'RPM Fusion signature failure reached package activation' assert_no_runtime_staging rpmfusion-bad-signature reset_installer_fixture cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$installer_fixture/setup/provenance/keys/hyprland-copr.asc" STUB_EXISTING_REPOSITORY=hyprland \ expect_failure run_installer_function hyprland-wrong-key configure_hyprland_repository assert_file_bytes "$test_tmp/cases/hyprland-wrong-key/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" \ 'known key' assert_file_bytes "$test_tmp/cases/hyprland-wrong-key/etc/yum.repos.d/panama-hyprland.repo" \ 'known repo' [[ "$(<"$test_tmp/cases/hyprland-wrong-key/commands.log")" != *'sudo:install:'* ]] \ || fail 'Hyprland key mismatch replaced known-good repository files' assert_no_runtime_staging hyprland-wrong-key reset_installer_fixture STUB_FLATHUB_VERIFY_LINE='NoGPGVerify=true' \ expect_failure run_installer_function flathub-no-gpg ensure_flathub_remote assert_file_bytes "$test_tmp/cases/flathub-no-gpg/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/flathub-no-gpg/commands.log")" != *'sudo:'* ]] \ || fail 'Flathub disabled-GPG descriptor mutated a remote' reset_installer_fixture STUB_FLATHUB_KEY_FILE="$installer_fixture/setup/provenance/keys/terra44.asc" \ expect_failure run_installer_function flathub-wrong-key ensure_flathub_remote assert_file_bytes "$test_tmp/cases/flathub-wrong-key/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/flathub-wrong-key/commands.log")" != *'sudo:flatpak'* ]] \ || fail 'Flathub key mismatch mutated an existing remote' assert_no_runtime_staging flathub-wrong-key reset_installer_fixture STUB_FLATHUB_URL='https://evil.invalid/repo/' \ expect_failure run_installer_function flathub-wrong-repo-url ensure_flathub_remote assert_file_bytes "$test_tmp/cases/flathub-wrong-repo-url/flatpak-state" 'preserved' [[ "$(<"$test_tmp/cases/flathub-wrong-repo-url/commands.log")" != *'sudo:'* ]] \ || fail 'Flathub repository URL mismatch mutated an existing remote' reset_installer_fixture STUB_CLAUDE_DESKTOP_REPO_MODE=untrusted \ expect_success run_installer_function claude-desktop-untrusted install_claude_desktop_if_trusted assert_log claude-desktop-untrusted 'rpm:release' [[ "$(grep -c 'Claude Desktop is optional; configure its reviewed local-key repository manually' \ "$test_tmp/cases/claude-desktop-untrusted/output")" -eq 1 ]] \ || fail 'untrusted Claude Desktop repository did not produce one manual message' reset_installer_fixture terra_failure_status=0 STUB_INSTALL_FAIL_AT=2 \ run_installer_function terra-publication-failure install_terra_repository \ || terra_failure_status=$? [[ "$terra_failure_status" -eq 78 ]] \ || fail "Terra publication failure returned $terra_failure_status instead of 78" [[ "$(<"$test_tmp/cases/terra-publication-failure/commands.log")" != *'terra-release'* \ && ! -s "$test_tmp/cases/terra-publication-failure/terra-rpm-state" ]] \ || fail 'Terra direct publication changed terra-release package state' [[ ! -e "$test_tmp/cases/terra-publication-failure/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" \ && ! -e "$test_tmp/cases/terra-publication-failure/etc/yum.repos.d/terra.repo" ]] \ || fail 'Terra publication failure did not remove its partial repository pair' assert_no_runtime_staging terra-publication-failure STUB_REUSE_CASE=1 \ expect_success run_installer_function terra-publication-failure \ install_terra_repository [[ "$before_gnupg" == "$(snapshot "$host_gnupg")" ]] || fail 'repository cases changed host GPG state' [[ "$before_gpg_files" == "$(snapshot_gpg_state "$host_gnupg")" ]] \ || fail 'repository cases changed host GPG files' [[ "$before_rpmdb" == "$(snapshot "$host_rpmdb")" ]] || fail 'repository cases changed host RPM database' [[ "$before_repo_files" == "$(snapshot_gpg_state /etc/yum.repos.d)" ]] \ || fail 'repository cases changed host repository files' [[ "$before_rpm_key_files" == "$(snapshot_gpg_state /etc/pki/rpm-gpg)" ]] \ || fail 'repository cases changed host RPM key files' [[ "$before_system_flatpak" == "$(snapshot_file_state /var/lib/flatpak/repo/config)" ]] \ || fail 'repository cases changed the system Flatpak remote' [[ "$before_system_flathub_key" == "$(snapshot_file_state /var/lib/flatpak/repo/flathub.trustedkeys.gpg)" ]] \ || fail 'repository cases changed the system Flathub trusted key' [[ "$before_user_flatpak" == "$(snapshot_file_state "$HOME/.local/share/flatpak/repo/config")" ]] \ || fail 'repository cases changed the user Flatpak remote' [[ "$before_bashrc" == "$(snapshot_file_state "$HOME/.bashrc")" ]] \ || fail 'repository cases changed the protected bashrc' printf 'package provenance contract: PASS\n'