From e3778892636457264749f310b4f64587c19e2f1f Mon Sep 17 00:00:00 2001 From: Jens Caers Date: Wed, 26 Aug 2026 23:55:49 +0200 Subject: [PATCH] fix(deploy): consume signed approval evidence exactly once --- examples/server/forgeflow-deploy | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/server/forgeflow-deploy b/examples/server/forgeflow-deploy index 7da8af7..4218671 100644 --- a/examples/server/forgeflow-deploy +++ b/examples/server/forgeflow-deploy @@ -6,10 +6,12 @@ umask 027 # the Gitea runner. Targets are read from the root-owned data file below. # Approved machine deployments additionally verify an AppOps Ed25519 signature # using the root-controlled public key; the Actions runner never receives that -# trust anchor's private key. +# trust anchor's private key. Every verified approval id is consumed exactly +# once in a root-owned replay journal before target lookup or mutation. readonly CONFIG_FILE="/etc/forgeflow/targets.conf" readonly EVIDENCE_PUBLIC_KEY_FILE="/etc/forgeflow/evidence.pub" +readonly EVIDENCE_REPLAY_DIR="/var/lib/forgeflow-status/approved-requests" readonly REPOSITORY="${1:-}" readonly ENVIRONMENT="${2:-}" readonly SHA="${3:-}" @@ -81,6 +83,15 @@ if (( $# == 8 )); then echo "Deployment evidence signature verification failed" >&2 exit 65 } + + # Consume the verified approval before any target lookup. mkdir is atomic, + # making this a cross-process replay fence. A failed first deployment still + # requires a fresh human approval, matching AppOps' terminal execution model. + install -d -o root -g root -m 0700 "$EVIDENCE_REPLAY_DIR" + if ! mkdir -m 0700 "$EVIDENCE_REPLAY_DIR/$APPROVAL_ID" 2>/dev/null; then + echo "Approved deployment evidence was already consumed" >&2 + exit 65 + fi EVIDENCE_VERIFIED=true fi