From 51b8572d985240d054fb1f71afe4b9ef562b2637 Mon Sep 17 00:00:00 2001 From: boban Date: Mon, 22 Jun 2026 22:16:23 +0200 Subject: [PATCH] fix(release): leak guard + export-ignore + env default (review fixes) The actual file changes for the leak-safety hardening described in a94efb0 (that commit only captured the art/ asset move): - promote.sh: leak guard refusing to publish a tree carrying a private identifier (git.bave.dev / boban/clusev / clusev-staging) or an internal doc; negative test. - .gitattributes: export-ignore kickoff-prompt.md (leaked the VM IP). - README: reference the moved art/ SVGs (docs/ is export-ignored). - .env.example: comment out CLUSEV_REPOSITORY so the public-repo config default applies (a present-but-empty value would disable the update check). Co-Authored-By: Claude Opus 4.8 --- .env.example | 9 +++++---- .gitattributes | 1 + README.md | 4 ++-- scripts/promote.sh | 17 +++++++++++++++++ scripts/promote.test.sh | 11 +++++++++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 0940896..52dce04 100644 --- a/.env.example +++ b/.env.example @@ -85,10 +85,11 @@ ACME_EMAIL= # .git (the prod image ships none) — leave empty; in dev the page reads .git directly. CLUSEV_BUILD_SHA= CLUSEV_BUILD_BRANCH= -# Update source for the in-app version check. install.sh derives this from the clone origin -# (git remote get-url origin). Leave empty to disable the check. Never put a private repo URL in a -# tracked file — only here in the per-deployment .env. -CLUSEV_REPOSITORY= +# Update source for the in-app version check. install.sh sets this from the clone origin +# (git remote get-url origin). Left UNSET (keep this commented), it falls back to the public repo +# default in config/clusev.php — an empty value here would instead disable the check, so leave the +# key absent rather than blank. Never put a private repo URL in a tracked file — only in the .env. +# CLUSEV_REPOSITORY=https://github.com/clusev/clusev # Display timezone for dashboard times (DB stays UTC). install.sh sets this from the host; UTC default. APP_TIMEZONE=UTC # External reverse-proxy TLS: set to the upstream proxy's address/CIDR so Caddy trusts its diff --git a/.gitattributes b/.gitattributes index d4ca542..1101091 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,4 +15,5 @@ /CLAUDE.md export-ignore /rules.md export-ignore /handoff.md export-ignore +/kickoff-prompt.md export-ignore /docs export-ignore diff --git a/README.md b/README.md index 9d4d826..a99d83d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-Clusev — self-hosted control plane for a fleet of Linux servers +Clusev — self-hosted control plane for a fleet of Linux servers
@@ -61,7 +61,7 @@ colour, dots and pills — never emoji. ## Architecture
-Operator → Clusev control plane → your fleet +Operator → Clusev control plane → your fleet
Laravel is the **control plane**, not a re-implementation of your servers. The browser talks to it diff --git a/scripts/promote.sh b/scripts/promote.sh index 8c3800c..56c0f18 100755 --- a/scripts/promote.sh +++ b/scripts/promote.sh @@ -28,6 +28,23 @@ git -C "$src" archive --format=tar "$tag" | tar -x -C "$pub" # Private CI never goes public. rm -rf "${pub:?}/.github" +# Leak guard — never publish a tree that still carries a private identifier or an internal doc. This +# makes promote.sh safe even for a tag cut BEFORE the export-ignore / URL-neutralisation fixes: such +# a tree is refused (the public repo is left untouched) instead of leaking the dev/staging repos. +leak=0 +for f in CLAUDE.md rules.md handoff.md kickoff-prompt.md docs; do + [ -e "$pub/$f" ] && { echo "leak-guard: internal path present in the tree: $f" >&2; leak=1; } +done +if grep -rIl -e 'git\.bave\.dev' -e 'boban/clusev' -e 'clusev-staging' "$pub" >/dev/null 2>&1; then + echo "leak-guard: private repo identifier in the tree to be published:" >&2 + grep -rIl -e 'git\.bave\.dev' -e 'boban/clusev' -e 'clusev-staging' "$pub" >&2 + leak=1 +fi +if [ "$leak" -ne 0 ]; then + echo "leak-guard: REFUSING to publish $tag — promote a tag cut after the leak-safety fixes (>= v0.9.58)." >&2 + exit 1 +fi + 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/scripts/promote.test.sh b/scripts/promote.test.sh index b8da7fb..6eab364 100755 --- a/scripts/promote.test.sh +++ b/scripts/promote.test.sh @@ -42,4 +42,15 @@ git -C "$work/src" tag v1.0.1 v1.0.0 bash "$here/promote.sh" "$work/src" v1.0.1 "$work/pub" "Release v1.0.1" git -C "$work/pub" rev-parse v1.0.1 >/dev/null 2>&1 || { echo "FAIL: identical-tree tag missing"; exit 1; } git -C "$work/pub" log -1 --pretty=%s | grep -qx 'Release v1.0.1' || { echo "FAIL: identical-tree commit message"; exit 1; } + +# Leak guard: a tree carrying a private identifier in a PUBLISHED file must be REFUSED, with no tag +# created (protects against promoting an old tag cut before the leak-safety fixes). +git init -q "$work/leaky" +printf 'home: https://git.bave.dev/boban/clusev\n' > "$work/leaky/README.md" +git -C "$work/leaky" add -A && git -C "$work/leaky" commit -qm init && git -C "$work/leaky" tag v9.9.9 +git init -q "$work/pub2" +if bash "$here/promote.sh" "$work/leaky" v9.9.9 "$work/pub2" "Release v9.9.9" 2>/dev/null; then + echo "FAIL: leak guard did not refuse a private-identifier tree"; exit 1 +fi +git -C "$work/pub2" rev-parse v9.9.9 >/dev/null 2>&1 && { echo "FAIL: tag created despite leak"; exit 1; } echo "PASS"