From 0d894afe3e91c3077bb617edc67f8945b1c9dbc1 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 26 Aug 2026 20:42:59 -0400 Subject: [PATCH] Plan repository audit remediation --- ...-26-repository-audit-remediation-design.md | 498 ++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-26-repository-audit-remediation-design.md diff --git a/docs/superpowers/specs/2026-08-26-repository-audit-remediation-design.md b/docs/superpowers/specs/2026-08-26-repository-audit-remediation-design.md new file mode 100644 index 0000000..276c8ed --- /dev/null +++ b/docs/superpowers/specs/2026-08-26-repository-audit-remediation-design.md @@ -0,0 +1,498 @@ +# Panama repository audit remediation + +Approved direction (2026-08-26): repair the complete repository audit in staged, +reviewable commits on one branch. Safety and test trust come first. No repair may +touch a real server, change a live firewall, or run a desktop-takeover check without +Gabriel explicitly approving that runtime step. + +## Why this is one program + +The audit found defects in four systems that depend on each other: + +- `boot`, `install`, and `panama update` decide which code runs with root access. +- The server catalog and updater decide which network services run unattended. +- Quickshell owns capture, dictation, privacy state, and other long-lived desktop work. +- The contract runner is the proof for all three, but its safe classification and two + contracts are currently wrong. + +Fixing a product defect while the gate is false-green or incorrectly labelled safe +would replace one uncertainty with another. This design therefore restores the gate +first, then fixes privileged and unattended paths, then the desktop. + +## Decisions + +| Decision | Choice | +|---|---| +| Delivery | One remediation branch with a small commit per behavior | +| Order | Test trust, bootstrap, updates and server lifecycle, desktop, remaining hardening | +| Test style | Behavioral fixtures at public command and QML interfaces | +| Live desktop | Excluded from the default safe gate; explicit capability and approval required | +| Live server | Never used during implementation; Podman, systemd, SSH, and firewall are stubbed | +| Production | Out of scope; repository changes only | +| Visual design | Unchanged; no mocks are needed because no layout or copy redesign is planned | +| Compatibility | Existing command names and normal successful flows remain intact | +| Failure policy | Privileged or destructive uncertainty fails closed with a recovery instruction | + +## Program structure + +The work is five repair packages. Each package has its own tests, implementation, +review, and commit sequence. A package may use a helper introduced by an earlier one, +but it may not reach forward into unfinished work. + +### Package 1: make the verification gate trustworthy + +#### Explicit contract capabilities + +`tests/contracts.manifest` becomes the source of truth for contract execution. +Every discovered executable contract has exactly one entry. Its format is +`[,...] `, with `#` for whole-line +comments: + +```text +hermetic tests/setup/boot-contract +live-compositor tests/hypr/keybind-categories-contract +live-desktop tests/quickshell/settings-pages-contract +network tests/quickshell/home-assistant-helper-contract +privileged tests/setup/root-server-bootstrap-contract +``` + +The vocabulary is deliberately small: + +- `hermetic`: temporary state and stubbed system commands only. +- `live-compositor`: reads or reloads the running compositor, without moving windows. +- `live-desktop`: maps surfaces, moves focus or windows, or changes desktop state. +- `network`: contacts a non-fixture network endpoint. +- `privileged`: needs root or changes system configuration. + +Capabilities may be combined. `panama test --safe` runs only `hermetic` contracts. +`hermetic` is exclusive and cannot be combined with another capability. Plain +`panama test` keeps its current all-contract meaning, but prints the capabilities +before each non-hermetic contract and requires a TTY confirmation. Automation must +grant each needed capability explicitly with repeatable flags such as +`--allow live-compositor --allow network`; there is no grant that means "anything." +Pattern selection does not bypass this rule. + +The existing `tests/desktop-hijacking` file is retired after its explanations move +beside the matching manifest entries as comments. A contract fails the manifest gate +when a discovered path is missing, a manifest path is stale, a path is duplicated, or +an unknown capability appears. The runner fails closed rather than inferring safety +from source patterns. + +#### Runner behavior + +Each contract gets a configurable outer timeout. The default is 180 seconds and +`PANAMA_TEST_TIMEOUT_SECONDS` may change it for local diagnosis. The runner captures +stdout and stderr separately. On failure it prints both. On success it prints a +warning and the captured stderr rather than discarding it. + +The runner creates its capture directory with `mktemp -d`, removes it on exit, and +reports a timeout as a normal failed contract. One stuck QML or IPC process cannot +block the remaining suite forever. + +#### Current red and false-green contracts + +- `agent-usage-contract` generates its session directory and timestamps from the + current local day. Production gains no test-only clock interface. +- `compose-secrets-contract` parses YAML and dotenv-shaped files as structured data. + Markdown is scanned only for private-key and known token signatures, not prose that + resembles an assignment. +- `update-command-contract` runs the real `panama update` command against a temporary + local bare remote, clone, temporary state, and stubbed installer/system commands. + Parser failures and fixture command failures must propagate. Successful stderr stays + visible through the runner. +- Contracts that currently write a probe into tracked or live-linked paths move the + probe into a complete temporary copy. Any unavoidable temporary mutation restores + from the EXIT trap, including interruption. + +#### Test interfaces + +- Contract discovery and execution: `panama test [--safe] [pattern]`. +- Update behavior: the real `panama update` command in disposable Git repositories. +- Secret policy: parsed Compose/env data plus explicit token fixtures. + +Tests do not assert that a source file contains a command when they can execute the +public command against stubs and inspect the outcome. + +### Package 2: secure bootstrap and privileged installation + +#### SSH hardening transaction + +Root server bootstrap may harden SSH only when all of these are true: + +1. The target account has a nonempty regular `authorized_keys` file. +2. The `.ssh` directory and file are owned by the target user and have safe modes. +3. A candidate Panama drop-in passes `sshd -t` as part of the complete active config. +4. Reloading the detected SSH unit succeeds. + +The drop-in is written to a same-directory temporary file and installed atomically. +If validation or reload fails, Panama restores the previous drop-in, validates the +restored configuration, and leaves root/password access unchanged. If the user has no +verified key, hardening is unavailable rather than merely defaulting to no. + +The public interface stays `boot --server`. A new root-bootstrap contract supplies +stubbed `id`, `passwd`, `sshd`, `systemctl`, account data, and filesystem state. It +proves the no-key refusal, successful transaction, invalid-config rollback, and failed- +reload rollback. + +#### Trusted installation inputs + +No network response may be executed as root unless Panama verifies an immutable digest +or package signature first. + +- Terra installation no longer uses `--nogpgcheck`. Before implementation, the plan + records the current official repository instructions, signing-key fingerprint, and + verification command in a provenance fixture. The contract pins that fingerprint. + If no verifiable path exists, the installer reports Terra unavailable and stops + before the desktop package transaction that depends on it. +- Claude Desktop's unpinned GitHub Pages installer is removed from the automatic path. + Panama may install the package when a trusted repository is already configured. It + otherwise reports a manual optional step and continues without Claude Desktop. +- Existing remote installers for Bun, Claude Code, and similar tools are audited under + the same rule. A tool that cannot offer a pinned or signed installation path moves to + an explicit optional action rather than remaining in the unattended base install. + +Package contracts test artifact provenance and failure behavior with local fixtures. +They never contact the real repositories. + +#### Server firewall transaction + +Server setup establishes the policy it documents: + +- firewalld must be installed, enabled, and active, or server setup fails. +- Ports 80 and 443 accept public traffic only from validated Cloudflare IPv4 and IPv6 + ranges held in dedicated firewalld ipsets. +- Port 81 is assigned only to the WireGuard interface or zone. +- No service-specific port is opened automatically. +- Updating Cloudflare ranges validates every CIDR before replacing the last known good + ipsets. An empty or malformed download changes nothing. +- The complete permanent configuration is applied and reloaded as one recoverable + transaction. A failure restores the previous Panama-owned rules. + +The repository carries the policy and updater, not a claim that an external cloud +firewall happens to compensate. Fixture tests cover inactive firewalld, zone selection, +bad CIDRs, rollback, and repeated setup. + +### Package 3: make updates, migrations, and server lifecycle recoverable + +#### Clean-revision machine updates + +`panama update` keeps local work stashed until the pulled checkout has completed +`install --upgrade` and migrations. It records the exact stash object it created and +does not assume `stash@{0}` still names it. + +An EXIT/INT/TERM recovery handler restores that stash only when the pulled update has +finished and the worktree is clean. If restoration conflicts or the tree is not clean, +the handler leaves the exact stash untouched and prints its object ID and recovery +command. A next-run check also reports an unfinished Panama update, which covers SIGKILL +and power loss that no trap can catch. + +Installer edits restored after the trusted update are not executed during that run. +The update contract proves dirty dotfiles, dirty installer code, pull failure, installer +failure, interruption, successful restore, and conflicting restore. + +A failed or unavailable pull remains visible in the final status and makes `panama +update` return nonzero after safe local repairs finish. Interactive updates preflight +administrator authentication once. A noninteractive update uses `sudo -n` and fails +immediately with a clear instruction instead of waiting on a hidden prompt. Documentation +states that routine updates ask no Panama questions but may require administrator +authentication. + +#### Exact installer state + +Before disabling idle behavior, `install` records whether each relevant gsettings key +exists and its exact serialized value. The EXIT handler restores only keys it changed, +using those exact values. A missing schema remains untouched. + +Every declared stage is required. A missing or non-executable stage is recorded as a +failed stage and makes the final result nonzero. + +Automatic migration baselining is removed. Shipped migrations are self-guarding and run +on both fresh and existing machines. `--baseline` remains an explicit development/admin +command but no install path infers freshness from an absent state directory. + +The secret relocation migration exits nonzero while both the old and new secret files +exist. It tightens the old file to mode `0600`, prints the two exact paths and required +manual reconciliation, and retries on the next migration run. It never marks unresolved +secret state complete. + +#### Collision-proof displacement + +A shared shell module owns backup displacement for installer, user-content, skills, and +server-definition paths. Its small interface accepts a source path and a backup category. +It guarantees: + +- Panama-owned symlinks are replaced without backup. +- Foreign symlinks are moved, never discarded. +- Existing backups are never overwritten. +- The destination is unique without relying on second-resolution timestamps alone. +- A failed move leaves the original object in place and returns nonzero. + +Callers no longer invent `.bak` or `.pre-panama` names independently. Behavioral tests +exercise repeated runs, foreign symlinks, collisions, and failure paths through the +public stage commands. + +#### Server catalog containment and portability + +`panama server` accepts only an exact service name returned by the catalog. Separators, +dot components, and aliases are rejected before any target path is created. + +Compose definitions use `${HOME}` and `${XDG_RUNTIME_DIR}` rather than `/home/gib` and +`/run/user/1000`. `RequiresMountsFor` is removed where unused and uses `%h` only for a +service that truly needs the media mount. Existing `.env` files must be regular, +non-symlink files and are corrected to mode `0600` on every enable. + +The nightly update unit executes a stable command linked under `~/.local/bin`, not a +hard-coded checkout path. Setup creates that link from the resolved `PANAMA_PATH`, so a +supported custom checkout receives the same timer behavior as the default location. + +`disable` stops the unit, verifies the compose containers are stopped, and only then +unlinks the unit. Failure leaves the management link installed and reports the commands +needed for diagnosis. + +Definition tracking separates `seen` from `applied` hashes. Relinking may update `seen`, +but only a verified successful restart updates `applied`, so an unapplied definition +keeps warning. + +#### Safe unattended container updates + +The updater takes a nonblocking `flock` for the complete run. It gets resolved images +from `podman compose config --images`; dotenv files are never sourced or evaluated as +shell code. + +Before pulling, the updater records every running container's image ID and tags enough +rollback references to recreate the old project. After `compose up -d`, it waits with a +bounded timeout until every expected container is running and each declared healthcheck +is healthy. A service without healthchecks still has to remain running for the complete +stability window. + +On failure, it restores the old image references, recreates the project, verifies the +rollback, records the service as failed, and keeps the rollback images. Image pruning +runs only after every changed service passed verification. A rollback failure is called +out separately and keeps all images. + +The updater's public interface and systemd timer stay the same. A fixture contract stubs +`podman`, `podman compose`, and `systemctl` to prove pull failure, unhealthy startup, +crash after startup, successful update, successful rollback, failed rollback, frozen and +stopped services, concurrency, and hostile dotenv values. + +#### Container privilege and secret policy + +Socket consumers are split by what they need: + +- Read-only monitoring uses an allowlisted socket proxy. A read-only bind mount is not + treated as an API permission boundary. +- Portainer retains administrative control only when pinned to an immutable image, + bound to WireGuard, excluded from automatic updates, and explicitly enabled as a + trusted administrator. +- Spoon's job worker runs under a dedicated Unix account and rootless Podman runtime so + a job cannot mount the main server account's SSH keys, service data, or `.env` files. +- Services that do not require the Podman API lose the socket mount. + +Every service receives only the environment variables it needs. Redis, MinIO, and other +sidecars do not inherit a stack-wide `.env`. The catalog contract inventories socket +mounts, floating tags, `label:disable`, `seccomp:unconfined`, published ports, and broad +`env_file` use. Intentional exceptions name their risk and update policy in a machine- +checked manifest. + +Required SMTP and mail settings live in each service's `.env.example`; Compose carries +only `${VAR}` references. `CHANGE_ME` never appears inline where `panama server enable` +cannot detect it. + +Publicly sourced images use immutable versions or digests. A service that deliberately +tracks a locally controlled `latest` tag is excluded from unattended pulls unless its +deployment pipeline supplies and records an immutable digest. + +### Package 4: fix desktop races and permanent work + +#### One monitor per capture transaction + +Capture snapshots the focused output name when a transaction opens and never reads live +focus again for that transaction. `shell.qml` creates one capture overlay per +`Quickshell.screens` entry. Only the overlay whose screen matches the snapshotted output +maps and accepts coordinates. Freeze, selection, capture, and commit all use the same +output name. + +A two-screen QML fixture proves that focus changes after open do not move the transaction +and that overlay-local coordinates resolve against the frozen output. + +Screenshot and recording destinations include millisecond precision plus an exclusive +collision suffix. The large frozen frame loads asynchronously while its overlay remains +unmapped; the overlay maps only after the image is ready or the fallback state is known. + +#### Owned dictation recorder + +The dictation helper serializes start, stop, cancel, and recovery with a lock. State is +written to a mode-`0600` temporary file and atomically replaced only after `pw-record` +starts. + +State carries PID, `/proc` start time, expected executable identity, recording path, and +creation time. Stop and cancel signal a process only after all identity fields match. +Stale state is removed without signalling an unknown PID. Concurrent start returns the +existing active recording instead of launching a second recorder. + +Behavioral tests use real short-lived fixture processes to prove concurrent start, stale +PID, PID mismatch, normal stop, cancel, and interrupted state write. + +#### Request-owned Screen Intelligence work + +Every analysis receives a monotonically increasing generation. Capture, OCR, and model +processes record that generation when started. Stream and exit callbacks ignore output +whose generation no longer matches the active request. `close()` invalidates the +generation before stopping any process. + +The public QML interface does not change. A fixture overlaps requests and closes during +OCR to prove an old callback cannot set `ready`, `error`, or replace the new result. + +#### Event-driven privacy state + +Privacy monitoring uses Quickshell's PipeWire registry, node, link, and property change +signals. It does not fork `pw-dump` on an idle timer. Construction may perform at most +one initial registry read; every later update reacts to graph changes. + +The existing curated-event and privacy interfaces remain intact. A contract runs an +isolated registry fixture long enough to prove no periodic process launches occur and +that camera, microphone, and screen-capture transitions still publish the same state. + +#### Owned wallpaper processes and atomic writes + +Video wallpaper cleanup signals only processes Panama launched. It tracks process IDs +and start times or uses a dedicated user-service cgroup. Global `pkill -x mpvpaper` and +global `pgrep` are removed. + +Helpers that rewrite Compose, settings, or generated configuration write a same-directory +temporary file, preserve ownership and mode, flush and fsync, validate the temporary +content, then atomically replace the destination. A failed validation or write leaves the +original byte-for-byte intact. + +#### Pinned speech assets + +The Whisper container uses an immutable digest. The model has a committed URL, expected +size, and SHA-256. Downloads go to a temporary file and become active only after all +checks pass. A mismatch removes the temporary file and preserves the installed model. + +### Package 5: remaining hardening and documentation + +#### Desktop entries and media outputs + +`panama-webapp` rejects NUL, newline, carriage return, and unsupported URL schemes. +It parses the URL, requires one logical argument, and escapes each Desktop Entry field +according to the specification. Browser switches cannot be introduced through the URL. + +`panama-transcode` reserves a unique temporary output in the destination directory, +writes only to that path, and atomically renames after success. Failure cleanup removes +only the inode it created. + +#### Documentation and policy alignment + +- README role behavior, contract count, safe-test meaning, and sudo requirements match + the implemented commands. +- The manual uses the current Settings route names and hidden-leaf taxonomy. +- Migration guidance describes the actual interactive and noninteractive privilege + policy. Tests validate each command invocation, not file-wide word presence. +- Server documentation distinguishes policy Panama enforces from external firewall or + hosting assumptions. +- Generated settings documentation remains `--check` clean. + +## Verification strategy + +Every behavior follows one red-green cycle through an agreed interface: + +| Repair | Interface under test | System adapter | +|---|---|---| +| Contract safety | `panama test` | temporary manifest and fixture contracts | +| Update safety | `panama update` | local Git remote and stub installer | +| Root bootstrap | `boot --server` | stub accounts, sshd, systemd, filesystem | +| Firewall | `setup-server` | stub firewall-cmd and systemctl | +| Installer state | `install` | temporary HOME/state and stub gsettings/stages | +| Server management | `panama server` | temporary catalog/HOME and stub systemctl/compose | +| Image updater | updater command | stateful Podman/systemd fixture | +| Capture | Capture QML interface | two-screen semantic harness | +| Dictation | helper CLI | fixture recorder processes and temporary runtime dir | +| Screen Intelligence | QML interface | delayed fixture processes | +| Privacy | privacy QML state | isolated PipeWire fixture | + +After each package: + +1. Run the focused contracts changed by that package. +2. Run `panama test --safe` under the new manifest. +3. Run syntax checks for every touched shell, Python, Lua, QML, YAML, and systemd file + using the tools available on the host. +4. Request independent code review and resolve Critical and Important findings. +5. Commit only that package. + +Final repository verification includes the complete hermetic suite, Hyprland config +validation, generated-document checks, Compose rendering with fixture env files, secret +scanning, and `git diff --check`. + +Live desktop checks are a separate, explicitly approved gate after all hermetic work is +green. Server cutover, service restarts, SSH reload, firewall mutation, and production +deployment are not part of this implementation branch. + +## Audit finding ledger + +This table prevents a smaller issue from disappearing behind the larger repairs. + +| Finding | Package | +|---|---| +| SSH hardening lockout | 2 | +| Unverified root installation inputs | 2 | +| Missing documented firewall policy | 2 | +| Update executes restored work-in-progress | 3 | +| Interrupted update recovery | 3 | +| Pull failure followed by success status | 3 | +| Hidden sudo prompt on noninteractive update | 3 | +| Hard-coded idle restoration | 3 | +| Automatic migration baseline inference | 3 | +| Unresolved secret migration marked complete | 3 | +| Missing required stage skipped | 3 | +| Backup collisions and foreign symlink loss | 3 | +| Dotenv sourced and evaluated as shell | 3 | +| Invalid container health gate and premature prune | 3 | +| Server disable false success | 3 | +| Hard-coded home and runtime UID | 3 | +| Service-name path components | 3 | +| Definition warning acknowledged before apply | 3 | +| Existing `.env` mode and symlink trust | 3 | +| Concurrent updater runs | 3 | +| Custom `PANAMA_PATH` update unit | 3 | +| Floating images with Podman socket access | 3 | +| Sidecar secret over-distribution | 3 | +| Inline SMTP placeholders outside `.env` | 3 | +| Multi-monitor capture mismatch | 4 | +| Dictation concurrent start and reusable PID | 4 | +| Stale Screen Intelligence callbacks | 4 | +| Permanent `pw-dump` polling | 4 | +| Global `mpvpaper` process cleanup | 4 | +| Non-atomic source/config rewrites | 4 | +| Mutable speech image and model | 4 | +| Capture filename collisions and synchronous frame load | 4 | +| Date-expiring agent-usage contract | 1 | +| Secret-scan prose false positive | 1 | +| False-green update contract | 1 | +| Incomplete safe-test classification | 1 | +| No runner timeout and hidden stderr | 1 | +| Contracts mutating tracked/live-linked paths | 1 | +| Contracts depending on real installed settings | 1 | +| Weak migration privilege policy test | 5 | +| Web-app Desktop Entry injection | 5 | +| Transcode output deletion race | 5 | +| Documentation drift | 5 | + +## Out of scope + +- Running the bootstrap on a real VPS. +- Applying firewalld rules to a real host. +- Restarting or migrating any existing server service. +- Activating a new Hyprland session or changing monitor state. +- Visual redesign of Settings, capture, notifications, or other shell surfaces. +- Replacing Podman, Quickshell, Hyprland, or the existing command vocabulary. +- Broad refactoring unrelated to an audited failure path. + +## Completion criteria + +The remediation is complete when every ledger row has a verified implementation. A row +may leave the ledger only when a failing behavioral test disproves the audit finding and +Gabriel approves that removal. The hermetic suite must be green with no hidden stderr; +no contract classified safe may touch live desktop, network, or privileged state; +generated documentation and configuration validators must pass; and an independent +final review must find no unresolved Critical or Important issue.