diff --git a/scripts/verify-no-secrets.sh b/scripts/verify-no-secrets.sh index 13e0c74..459666e 100755 --- a/scripts/verify-no-secrets.sh +++ b/scripts/verify-no-secrets.sh @@ -16,7 +16,13 @@ # --------------------------------------------------------------------------- set -uo pipefail -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# Resolve the repository root via git, not via BASH_SOURCE: when this script is +# invoked through the .git/hooks/pre-commit symlink, dirname(BASH_SOURCE)/.. +# resolves to .git/ rather than the work tree. +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { + echo "verify-no-secrets: not inside a git work tree" >&2 + exit 1 +} cd "$REPO_ROOT" || exit 1 MODE="${1:-staged}" @@ -40,14 +46,21 @@ fail() { } # --- Rule 1: filenames that must never be committed ------------------------ -FORBIDDEN_NAMES='(^|/)(models\.json|auth\.json|trust\.json|models-store\.json)$|(^|/)\.env$|\.env\.[^/]*$|(^|/)id_(ed25519|rsa)|\.(pem|p12)$|\.rendered(\..*)?$|(^|/)secrets/(?!.*\.(example|template)$)' +FORBIDDEN_NAMES='(^|/)(models\.json|auth\.json|trust\.json|models-store\.json)$|(^|/)\.env$|\.env\.[^/]*$|(^|/)id_(ed25519|rsa)|\.(pem|p12)$|\.rendered(\..*)?$' +# Inside secrets/ only documentation and placeholder scaffolding may be tracked. +SECRETS_ALLOWED='(^|/)secrets/(\.gitkeep|README\.md|.*\.(example|template))$' for f in "${FILES[@]}"; do - # .env.example / .env.template are allowed + # Templates and examples are the intended way to track credential-shaped files. case "$f" in *.env.example|*.env.template|*.example|*.template) continue ;; esac if printf '%s' "$f" | grep -qP "$FORBIDDEN_NAMES"; then fail "$f" "filename is on the never-commit list" + continue + fi + if printf '%s' "$f" | grep -qP '(^|/)secrets/' \ + && ! printf '%s' "$f" | grep -qP "$SECRETS_ALLOWED"; then + fail "$f" "files under secrets/ may only be .gitkeep, README.md, *.example or *.template" fi done