Files
Çağan Kiraz 85b4b27ab3 chore(tooling): add pre-push secret scan and fix pre-commit misses
pre-commit only sees the commit being made, so a key committed with
--no-verify or from a clone without the hooks still reached the
remote. pre-push applies the same rules to every commit being
pushed, not just the tip: a key removed in a later commit still
lands in the remote history.

pre-commit: `grep -q` exited early and, under pipefail, turned a
match into a pass when a long diff followed the key; a config.ini
without an X_KEY line made the hook fail silently.
2026-09-26 19:05:51 +03:00

102 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# pre-push — block pushing BFL API keys.
#
# pre-commit only sees the commit being made; a key can still reach the remote
# through a commit made with --no-verify, in a clone without the hooks, or
# before they were installed. This hook scans EVERY commit about to be pushed —
# not just the tip, since a key added in one commit and removed in the next
# still lands in the remote history — with the same rules as pre-commit:
# 1. config.ini — X_KEY must stay the `your-key` placeholder
# 2. workflows/*.json — FluxConfig_BFL nodes must not carry a real x_key
# 3. every added line — nothing matching `bfl_<16+ chars>` key material
#
# Installed via `git config core.hooksPath .githooks` (run once per clone).
# Do not bypass with --no-verify — rewrite the offending commit instead.
set -euo pipefail
EMPTY_TREE=$(git hash-object -t tree /dev/null)
fail=0
report() {
echo "pre-push: $1" >&2
fail=1
}
scan_commit() {
local commit=$1 parent label f value
label="$(git log -1 --format='%h "%s"' "$commit")"
# Diff against the first parent (the empty tree for a root commit), so merge
# commits are scanned too instead of showing an empty combined diff.
parent=$(git rev-parse --verify --quiet "$commit^1" || echo "$EMPTY_TREE")
# 1) config.ini: X_KEY must be empty or the placeholder.
while IFS= read -r f; do
value=$(git show "$commit:$f" | grep -iE '^[[:space:]]*X_KEY[[:space:]]*=' | head -1 \
| sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]+$//' || true)
if [ -n "$value" ] && [ "$value" != "your-key" ]; then
report "commit $label: $f has a real X_KEY (${value:0:6}...) — keep the \`your-key\` placeholder."
fi
done < <(git diff --name-only --diff-filter=ACMR "$parent" "$commit" | grep -E '(^|/)config\.ini$' || true)
# 2) workflow JSONs: FluxConfig_BFL x_key widget must be empty or a placeholder.
while IFS= read -r f; do
if ! git show "$commit:$f" | python3 -c "
import json, sys
try:
wf = json.load(sys.stdin)
except Exception:
sys.exit(0) # unparseable JSON — the generic scan below still applies
ok = True
for node in wf.get('nodes', []):
if node.get('type') == 'FluxConfig_BFL':
vals = node.get('widgets_values') or []
key = vals[0] if vals else ''
if key and key not in ('bfl_xxx', 'your-key'):
print('pre-push: commit %s: %s: FluxConfig_BFL node %s carries a real x_key (%s...).'
% (sys.argv[1], sys.argv[2], node.get('id'), key[:6]), file=sys.stderr)
ok = False
sys.exit(0 if ok else 1)
" "$label" "$f"; then
fail=1
fi
done < <(git diff --name-only --diff-filter=ACMR "$parent" "$commit" -- 'workflows/*.json')
# 3) generic scan: no BFL-style key material in any added line. Redirect instead of
# `grep -q`: its early exit would SIGPIPE the upstream grep and, under pipefail,
# turn a match into a pass whenever a long diff follows the matching line.
if git diff "$parent" "$commit" | grep -E '^\+' | grep -E 'bfl_[A-Za-z0-9_-]{16,}' >/dev/null; then
report "commit $label adds what looks like a BFL API key (bfl_...)."
fi
}
# stdin: one "<local ref> <local sha> <remote ref> <remote sha>" line per ref being pushed.
while read -r local_ref local_sha remote_ref remote_sha; do
if [[ $local_sha =~ ^0+$ ]]; then
continue # deleting a remote ref — nothing is uploaded
fi
if [[ $remote_sha =~ ^0+$ ]] || ! git cat-file -e "$remote_sha^{commit}" 2>/dev/null; then
# New remote ref, or its tip is unknown locally: every commit no remote-tracking ref has yet.
commits=$(git rev-list "$local_sha" --not --remotes)
else
commits=$(git rev-list "$remote_sha..$local_sha")
fi
for commit in $commits; do
scan_commit "$commit"
done
done
if [ "$fail" -ne 0 ]; then
cat >&2 <<'EOF'
Push blocked: a real BFL API key would reach the remote history.
Removing the key in a new commit is not enough — the old commit still carries it.
Rewrite the offending commit (git commit --amend for the tip, git rebase for older
ones) so no pushed commit contains the key, then push again.
If a key ever reached the public repo, rotate it in the BFL dashboard.
EOF
exit 1
fi
exit 0