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.
102 lines
4.1 KiB
Bash
Executable File
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
|