From 54518c1f30567d4d356cf8394a14c7ad9921112b Mon Sep 17 00:00:00 2001 From: boban Date: Fri, 3 Jul 2026 19:49:31 +0200 Subject: [PATCH] feat(release): string leak-guard in promote.sh + shell test --- scripts/promote.sh | 14 +++++++++++ tests/scripts/promote-guard.test.sh | 39 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/scripts/promote-guard.test.sh diff --git a/scripts/promote.sh b/scripts/promote.sh index 94750bb..6ea2884 100755 --- a/scripts/promote.sh +++ b/scripts/promote.sh @@ -40,6 +40,20 @@ for f in CLAUDE.md rules.md handoff.md kickoff-prompt.md docs; do fi done +# String leak-guard: refuse to publish a tree that mentions the private upstream by name. The +# patterns are the always-forbidden literals plus any passed in CLUSEV_PROMOTE_FORBIDDEN (the +# private host/slug — supplied from a CI secret so they never live in a tracked file). This script +# is itself export-ignored, so grepping the assembled tree never trips on the guard's own source. +forbidden=$'gitea\n.env.gitea' +[ -n "${CLUSEV_PROMOTE_FORBIDDEN:-}" ] && forbidden+=$'\n'"${CLUSEV_PROMOTE_FORBIDDEN// /$'\n'}" +while IFS= read -r pat; do + [ -n "$pat" ] || continue + if grep -rIiF --exclude-dir=.git -- "$pat" "$pub" >/dev/null 2>&1; then + echo "leak-guard: REFUSING to publish $tag — forbidden pattern present in the public tree." >&2 + exit 1 + fi +done <<< "$forbidden" + git -C "$pub" add -A # --allow-empty: stable promotion re-tags the same source commit as the last beta, so the # extracted tree is byte-identical and there is nothing to stage — still produce a release commit. diff --git a/tests/scripts/promote-guard.test.sh b/tests/scripts/promote-guard.test.sh new file mode 100644 index 0000000..7802863 --- /dev/null +++ b/tests/scripts/promote-guard.test.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Verifies scripts/promote.sh: (a) export-ignored dev-release paths never reach the public tree, +# (b) a planted forbidden string is caught by the leak-guard. Self-contained: builds throwaway +# src/pub git repos in a temp dir. Run from the repo root: bash tests/scripts/promote-guard.test.sh +set -euo pipefail +cd "$(dirname "$0")/../.." +ROOT="$(pwd)" +TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT + +# A minimal "src" repo whose .gitattributes mirrors ours (so export-ignore is honoured), containing +# one public file, one export-ignored dev-release file, and the CHANGELOG. +mk_src() { + git init -q "$TMP/src"; git -C "$TMP/src" config user.email t@t; git -C "$TMP/src" config user.name t + cp "$ROOT/.gitattributes" "$TMP/src/.gitattributes" + mkdir -p "$TMP/src/app/Livewire/Release" "$TMP/src/docker/release" + echo 'public content' > "$TMP/src/README.md" + echo 'DEV-RELEASE-BRIDGE' > "$TMP/src/app/Livewire/Release/Index.php" + echo '# changelog' > "$TMP/src/CHANGELOG.md" + git -C "$TMP/src" add -A; git -C "$TMP/src" commit -qm init; git -C "$TMP/src" tag v1.0.0 +} +mk_pub() { git init -q "$TMP/pub"; git -C "$TMP/pub" config user.email t@t; git -C "$TMP/pub" config user.name t + git -C "$TMP/pub" commit -q --allow-empty -m base; } + +# (a) Clean run: dev-release path dropped, promote succeeds. +mk_src; mk_pub +bash "$ROOT/scripts/promote.sh" "$TMP/src" v1.0.0 "$TMP/pub" "Release v1.0.0" +[ ! -e "$TMP/pub/app/Livewire/Release/Index.php" ] || { echo "FAIL: dev-release path leaked"; exit 1; } +[ -e "$TMP/pub/README.md" ] || { echo "FAIL: public file missing"; exit 1; } +echo "PASS: clean promote drops the dev-release path" + +# (b) Planted forbidden string in a PUBLIC file → guard must refuse. +rm -rf "$TMP/src" "$TMP/pub"; mk_src; mk_pub +echo 'clone from git.example-forbidden.test' > "$TMP/src/README.md" +git -C "$TMP/src" commit -qam plant; git -C "$TMP/src" tag -f v1.0.0 +if CLUSEV_PROMOTE_FORBIDDEN='example-forbidden.test' bash "$ROOT/scripts/promote.sh" "$TMP/src" v1.0.0 "$TMP/pub" "Release v1.0.0" 2>/dev/null; then + echo "FAIL: guard did not catch the planted forbidden string"; exit 1 +fi +echo "PASS: guard refuses a planted forbidden string" +echo "ALL PASS"