Files
Panama/tests/server/compose-secrets-contract
T
Gabriel Brown 913a57f8a6 Snapshot both servers: the VPS catalog moves to server/vps, the home server joins as server/home
The catalog in server/containers had drifted from the VPS. Eleven compose
files and twelve units no longer matched what runs, and two running
services, Gbrown_Agent and BillTracker, were missing. server/vps is now a
copy of the live files, and panama-server reads from it.

server/home holds the home server's services in the same shape. They still
run under Docker, so the podman units there are drafts for a later move, and
nothing installs from that tree. PostHog, Sentry and Plausible are checkouts
of upstream repositories. Only their local files are kept, next to an
UPSTREAM file naming the repository and the exact commit.

Every secret in both trees is a ${VAR} reference named in .env.example, or
CHANGE_ME in files that are not compose files. The live servers still hold
some of those values inline, so a live file can differ from its copy on
those lines alone. Existing .env.example files keep their comments, with new
keys appended at the end.

The ignore rules and the secrets contract now cover both trees, plus the
volume/ and volumes/ directories the home server uses.
2026-10-06 10:52:47 -04:00

99 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# This repository is public, and server/ describes real infrastructure. The
# rule that makes that safe has three parts, and each is pinned here because
# each fails silently:
#
# 1. No tracked file under server/ carries a secret. Compose files reference
# secrets as ${VAR} interpolations resolved from the .env beside them on
# the machine; .env.example names the variables with CHANGE_ME in place
# of every value that matters.
# 2. .gitignore keeps .env and data/ out of server/vps/ and server/home/ even when a
# cutover or a mistake puts one there. The live files belong in
# ~/Server/<Name>/, outside the checkout entirely -- the ignore is a
# seatbelt, and a seatbelt that got deleted should fail loudly.
# 3. Nothing named .env is tracked, full stop.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
server_dir="$repo_dir/server"
findings=()
note() { findings+=("$1"); }
[[ -d "$server_dir" ]] || { printf 'compose secrets contract: no server/ directory\n' >&2; exit 1; }
# ── 1. Scanner fixtures and tracked content ──────────────────────────────────
scanner="$repo_dir/tests/server/scan-tracked-secrets.py"
fixtures_dir="$repo_dir/tests/server/fixtures/secrets"
if ! python3 "$scanner" "$fixtures_dir/clean" \
compose.yml .env.example README.md signature-near-misses.txt; then
note 'the clean secret-scanning fixture was rejected'
fi
expect_leak() {
local fixture="$1"
local expected="$2"
local output
local status
output="$(python3 "$scanner" "$fixtures_dir/leaked" "$fixture" 2>&1)"
status=$?
if (( status != 1 )); then
note "the leaked $fixture fixture exited $status instead of 1"
elif [[ "$output" != "$expected" ]]; then
note "the leaked $fixture fixture reported '$output' instead of '$expected'"
fi
}
expect_leak compose.yml 'compose.yml:4: POSTGRES_PASSWORD'
expect_leak .env.example '.env.example:1: API_TOKEN'
expect_leak plain-list.yml 'plain-list.yml:4: API_TOKEN'
expect_leak quoted-mapping.yml 'quoted-mapping.yml:4: API_TOKEN'
expect_leak quoted-list.yml 'quoted-list.yml:4: API_TOKEN'
expect_leak pem-private-key.txt 'pem-private-key.txt:1: private key'
expect_leak anthropic-token.txt 'anthropic-token.txt:1: provider token'
expect_leak github-token.txt 'github-token.txt:1: provider token'
expect_leak slack-token.txt 'slack-token.txt:1: provider token'
mapfile -t tracked_server_files < <(git -C "$repo_dir" ls-files 'server/**' 'server/*')
if ! output="$(python3 "$scanner" "$repo_dir" "${tracked_server_files[@]}" 2>&1)"; then
while IFS= read -r hit; do
[[ -n "$hit" ]] && note "$hit"
done <<< "$output"
fi
# ── 2. The ignore still stands ───────────────────────────────────────────────
#
# check-ignore consults the real gitignore for a path that need not exist, so
# this asserts the rule rather than the current absence of violations.
for tree in vps home; do
git -C "$repo_dir" check-ignore -q "server/$tree/AnyService/.env" \
|| note ".gitignore no longer covers .env under server/$tree/"
git -C "$repo_dir" check-ignore -q "server/$tree/AnyService/data/dump.sql" \
|| note ".gitignore no longer covers data/ under server/$tree/"
git -C "$repo_dir" check-ignore -q "server/$tree/Nested/Service/volumes/db/file" \
|| note ".gitignore no longer covers volumes/ under server/$tree/"
done
# ── 3. No .env is tracked ────────────────────────────────────────────────────
while IFS= read -r tracked; do
note "a live .env is tracked: $tracked"
done < <(git -C "$repo_dir" ls-files 'server/**/.env' 'server/.env')
# ── Report ───────────────────────────────────────────────────────────────────
if (( ${#findings[@]} > 0 )); then
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
printf 'compose secrets contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'compose secrets contract: PASS\n'