From dce00e45d197b02761ebceda2c7049739ea8b3fa Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Thu, 27 Aug 2026 10:14:55 -0400 Subject: [PATCH] Docs: Pin the verified Panama bootstrap --- .claude/skills/panama/SKILL.md | 16 ++++++++ README.md | 59 +++++++++++++++++++++-------- setup/provenance/README.md | 22 +++++++++++ skills/panama-desktop/SKILL.md | 7 ++++ tests/setup/readme-contract | 68 ++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 16 deletions(-) diff --git a/.claude/skills/panama/SKILL.md b/.claude/skills/panama/SKILL.md index ef6c6dc..12eba00 100644 --- a/.claude/skills/panama/SKILL.md +++ b/.claude/skills/panama/SKILL.md @@ -94,6 +94,22 @@ it against the real system. Never bare `sudo`. Load the `panama-sudo` skill first. Migrations already follow the rule. +## Initial bootstrap trust + +Use the complete command in `README.md` for a new machine. It downloads `boot` +from the documented commit URL into a private temporary directory, enforces the +documented curl time and byte limits, verifies the committed blob with +`sha256sum -c`, and passes both pins to the verified script. Desktop and server +bootstrap use the same commit and digest. Never substitute a branch URL, pipe a +response into Bash, or invent a newer pin from an uncommitted file. + +`boot` validates the full lowercase commit and SHA-256 before Git or install. A +fresh destination fetches only that revision and creates a tracked local `main` +after HEAD matches. An existing checkout must be clean and an ancestor of the +requested revision; it advances with fast-forward only. Dirty, divergent, +fetch-failed, or mismatched checkouts stop without reset or install. Once Panama +exists, use `panama update` for normal updates. + `boot --server` is the one exception where a new Fedora VPS may begin as root. Before it offers SSH hardening, it copies a safe root key when possible or verifies the target key. The target user's `.ssh` must be owned by that user at diff --git a/README.md b/README.md index 59757cb..17237ce 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,37 @@ # Panama Formerly Sunhat. A personal config for Fedora, with the intention of helping a -user set up their Fedora system with one command. +user set up their Fedora system with one checked command. ```sh -bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot) +( +set -euo pipefail +bootstrap_commit=0d1841cf86975d52485c94c0caa5d1b67e5c54a1 +bootstrap_sha=fd8dcd70d1266da109972df346e3d85d1037f6f6e1942b0157064412ab892372 +bootstrap_dir="$(umask 077; mktemp -d)" +trap 'rm -rf -- "$bootstrap_dir"' EXIT +bootstrap="$bootstrap_dir/boot" +curl --fail --location --connect-timeout 10 --max-time 30 --max-filesize 262144 \ + --output "$bootstrap" \ + "https://git.gbrown.org/gib/Panama/raw/commit/$bootstrap_commit/boot" +printf '%s %s\n' "$bootstrap_sha" "$bootstrap" >"$bootstrap_dir/boot.sha256" +sha256sum -c "$bootstrap_dir/boot.sha256" +PANAMA_BOOT_REVISION="$bootstrap_commit" PANAMA_BOOT_SHA256="$bootstrap_sha" \ + bash "$bootstrap" +) ``` -`boot` installs git if the machine lacks it, clones this repository to +`boot` installs git if the machine lacks it, creates a verified checkout at `~/.local/share/Panama` (or `$PANAMA_PATH`), and hands off to `install`. It is -deliberately small enough to read first, and the same two steps by hand work -identically: +deliberately small enough to read first. The command downloads one immutable +`boot` blob into a private temporary directory, checks its SHA-256, then asks +that verified script to fetch the same commit. A clean existing checkout may +fast-forward to that exact revision. A dirty or divergent checkout, a failed +fetch, or a revision mismatch stops before `install`; Panama never resets local +work or falls back to whatever checkout happens to be present. -```sh -git clone https://git.gbrown.org/gib/Panama.git ~/.local/share/Panama -~/.local/share/Panama/install -``` - -Both are safe to run again: an existing clone is fast-forwarded rather than -replaced. Once a machine exists, though, the command that keeps it current is -`panama update` — one command, and it never asks you anything: +Once a machine exists, the command that keeps it current is `panama update` — +one command, and it never asks you anything: ```sh panama update @@ -35,10 +47,25 @@ shell environment minus everything that needs a screen, plus rootless podman and the compose services in [`server/`](server/README.md). The answer is the one thing Panama records durably (`~/.local/state/panama/role`), because `panama update` asks nothing and still has to know. A fresh server skips the -question entirely: +question entirely. It uses the same immutable boot blob and pins as the desktop +command; only the final `--server` argument differs: ```sh -bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot) --server +( +set -euo pipefail +bootstrap_commit=0d1841cf86975d52485c94c0caa5d1b67e5c54a1 +bootstrap_sha=fd8dcd70d1266da109972df346e3d85d1037f6f6e1942b0157064412ab892372 +bootstrap_dir="$(umask 077; mktemp -d)" +trap 'rm -rf -- "$bootstrap_dir"' EXIT +bootstrap="$bootstrap_dir/boot" +curl --fail --location --connect-timeout 10 --max-time 30 --max-filesize 262144 \ + --output "$bootstrap" \ + "https://git.gbrown.org/gib/Panama/raw/commit/$bootstrap_commit/boot" +printf '%s %s\n' "$bootstrap_sha" "$bootstrap" >"$bootstrap_dir/boot.sha256" +sha256sum -c "$bootstrap_dir/boot.sha256" +PANAMA_BOOT_REVISION="$bootstrap_commit" PANAMA_BOOT_SHA256="$bootstrap_sha" \ + bash "$bootstrap" --server +) ``` That command also works from a brand-new VPS's **root** login. It creates or @@ -220,7 +247,7 @@ docs/ Settings reference, and the design specs behind the work ## Tests -187 of them, under `tests/`. `tests/contracts.manifest` classifies every +188 of them, under `tests/`. `tests/contracts.manifest` classifies every contract by the capabilities it needs. Run the hermetic set, or grant a specific external capability when automation needs it: diff --git a/setup/provenance/README.md b/setup/provenance/README.md index efe6eeb..866b025 100644 --- a/setup/provenance/README.md +++ b/setup/provenance/README.md @@ -72,6 +72,28 @@ record both the reviewed archive digest and this reviewed installed-byte digest in `.panama-provenance`; later runs require the receipt and re-hash the installed binary before treating an existing directory as trusted. +## Panama bootstrap + +The public desktop and server commands use the same committed `boot` blob: + +```text +commit 0d1841cf86975d52485c94c0caa5d1b67e5c54a1 +SHA-256 fd8dcd70d1266da109972df346e3d85d1037f6f6e1942b0157064412ab892372 +URL https://git.gbrown.org/gib/Panama/raw/commit/0d1841cf86975d52485c94c0caa5d1b67e5c54a1/boot +``` + +The digest was derived from the committed blob with: + +```bash +git show 0d1841cf86975d52485c94c0caa5d1b67e5c54a1:boot | sha256sum +``` + +That implementation commit contains only `boot` and its boot/root-server +contracts. The following documentation commit records the pin and does not +contribute bytes to it. Rotate the bootstrap pin only through the same two-step +sequence: commit the boot implementation first, derive its committed blob +digest, then update the command and this ledger in a second commit. + ## Rotation policy Do not replace a key on an automated update. A key rotation is a reviewed diff --git a/skills/panama-desktop/SKILL.md b/skills/panama-desktop/SKILL.md index 2dd029d..fcab34f 100644 --- a/skills/panama-desktop/SKILL.md +++ b/skills/panama-desktop/SKILL.md @@ -43,6 +43,13 @@ Other tools that beat raw commands: - `panama test --safe` — run only contracts classified as hermetic in `tests/contracts.manifest` +For a new machine, use the complete verified bootstrap command in `README.md`. +Desktop and server installs use the same commit-addressed `boot` file and +SHA-256; server adds only `--server`. Do not replace that URL with a branch or +pipe its response into Bash. `boot` refuses missing or malformed pins, a boot +digest mismatch, and any dirty, divergent, fetch-failed, or mismatched existing +checkout. Use `panama update` after installation. + A plain `panama test` prompts in a terminal before it starts non-hermetic contracts. Automation must grant each required capability with a repeatable `--allow`, such as `panama test --allow live-host updates`. Each contract has a 180-second outer timeout by default, diff --git a/tests/setup/readme-contract b/tests/setup/readme-contract index 2462d59..ed5643c 100755 --- a/tests/setup/readme-contract +++ b/tests/setup/readme-contract @@ -20,6 +20,74 @@ panama="$repo_dir/bin/panama" findings=() note() { findings+=("$1"); } +# ── Verified bootstrap command ─────────────────────────────────────────────── + +if grep -qE 'bash[[:space:]]+<\(curl[^)]*/raw/branch/main/boot' "$readme"; then + note 'the README still executes the mutable main-branch bootstrap' +fi +if grep -q '/raw/branch/main/boot' "$readme"; then + note 'the README still names the mutable main-branch boot URL' +fi + +require_bootstrap_occurrences() { + local pattern="$1" expected="$2" explanation="$3" actual + actual="$(grep -cE -- "$pattern" "$readme")" + (( actual == expected )) || note "$explanation" +} + +mapfile -t documented_commits < <( + sed -nE "s/^bootstrap_commit=['\"]?([0-9a-f]{40})['\"]?$/\1/p" "$readme" | sort -u +) +mapfile -t documented_shas < <( + sed -nE "s/^bootstrap_sha=['\"]?([0-9a-f]{64})['\"]?$/\1/p" "$readme" | sort -u +) + +if (( ${#documented_commits[@]} != 1 )); then + note 'the README does not declare one full lowercase 40-hex bootstrap commit' +else + documented_commit="${documented_commits[0]}" + if ! git -C "$repo_dir" cat-file -e "$documented_commit^{commit}" 2>/dev/null; then + note 'the documented bootstrap commit does not resolve to a repository commit' + fi +fi + +if (( ${#documented_shas[@]} != 1 )); then + note 'the README does not declare one lowercase 64-hex bootstrap SHA-256' +else + documented_sha="${documented_shas[0]}" +fi + +if [[ -n "${documented_commit:-}" && -n "${documented_sha:-}" ]] \ + && [[ "$(git -C "$repo_dir" show "$documented_commit:boot" 2>/dev/null | sha256sum | cut -d' ' -f1)" != "$documented_sha" ]]; then + note 'the documented SHA-256 does not match boot in the documented commit' +fi + +require_bootstrap_occurrences '^bootstrap_commit=[0-9a-f]{40}$' 2 \ + 'the desktop and server commands do not declare the same full bootstrap commit' +require_bootstrap_occurrences '^bootstrap_sha=[0-9a-f]{64}$' 2 \ + 'the desktop and server commands do not declare the same full bootstrap SHA-256' +require_bootstrap_occurrences 'https://git\.gbrown\.org/gib/Panama/raw/commit/\$bootstrap_commit/boot' 2 \ + 'the desktop and server commands do not both use the commit-addressed boot URL' +require_bootstrap_occurrences '--connect-timeout 10' 2 \ + 'the desktop and server commands do not both use the 10-second connect timeout' +require_bootstrap_occurrences '--max-time 30' 2 \ + 'the desktop and server commands do not both use the 30-second total timeout' +require_bootstrap_occurrences '--max-filesize 262144' 2 \ + 'the desktop and server commands do not both use the 256 KiB response limit' +require_bootstrap_occurrences 'mktemp[[:space:]]+-d' 2 \ + 'the desktop and server commands do not both use a private temporary directory' +require_bootstrap_occurrences 'sha256sum[[:space:]]+-c' 2 \ + 'the desktop and server commands do not both verify with sha256sum -c' +require_bootstrap_occurrences 'PANAMA_BOOT_REVISION="?\$bootstrap_commit"?[[:space:]]+PANAMA_BOOT_SHA256="?\$bootstrap_sha"?' 2 \ + 'the desktop and server commands do not both pass the verified pins to boot' +grep -qE 'bash[[:space:]]+"?\$bootstrap"?([[:space:]]|$)' "$readme" \ + || note 'the README bootstrap does not execute the verified temporary file' +grep -qE 'bash[[:space:]]+"?\$bootstrap"?[[:space:]]+--server' "$readme" \ + || note 'the server bootstrap does not reuse the verified temporary file' +if grep -qE 'curl[^|]*\|[[:space:]]*(bash|sh)|bash[[:space:]]+<\(curl' "$readme"; then + note 'the README pipes a network response into a shell' +fi + # ── The contract count ─────────────────────────────────────────────────────── # # Counted the way `panama test` collects the suite, so the README agrees with