Files
Panama/docs/superpowers/specs/2026-08-26-repository-audit-remediation-design.md
T

25 KiB

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 <capability>[,<capability>...] <repo-relative-path>, with # for whole-line comments:

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.