mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 17:57:02 +01:00
The leak this repository had was not carelessness with a secret. It was somebody reaching for a real name because it was the example in front of them -- a fixture built from a live database, an envelope pasted out of a running container, a help text written while looking at the actual group. Three different people-shaped mistakes, none of which a rule about "be careful" would have stopped. So it is a gate. The hard part is that the thing being checked must not contain the list of what to refuse, or the check *is* the leak. It reads the names at run time from outside: the photo pack's manifest, the message database, the local .env. It never prints the word it matched, because this output reaches CI logs. On a machine with none of that private data it says "shapes only" out loud rather than passing quietly, and CI enforces the half that needs nothing -- phone numbers, uuids and Signal group ids, compared against an allowlist of values invented for the fixtures. It runs on staged files, on the commit message -- where two of the leaks actually travelled -- and over the whole tree in CI. It paid for itself twice before it was wired up. The live test group's id was in tests/test_config.py in two spellings, and truncated in three more files, after four rounds of grepping by hand had pronounced the tree clean -- nobody thinks to grep for base64. Then it refused the commit that introduced it, because DOCS-privacy.md had used a real name as the example of a name. An allowlist line may be scoped to one file, which is how the given names that are also popes stay legal in popes.json and refused in every fixture that has no business carrying them. The threshold is three characters, because one of this group's given names is three characters long. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w1HZ3cZ7neL3rFdQ6VMBA
74 lines
2.1 KiB
Bash
Executable File
74 lines
2.1 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Refuse to commit anything that identifies a real person.
|
|
#
|
|
# The logic is check_personal_data.py; this half exists to list what git
|
|
# tracks, which keeps the Python free of a subprocess call it would otherwise
|
|
# need a lint suppression for.
|
|
#
|
|
# scripts/check_personal_data.sh --all every tracked file
|
|
# scripts/check_personal_data.sh a.py b.md named files
|
|
# scripts/check_personal_data.sh --message .git/COMMIT_EDITMSG
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly SCRIPT_DIR
|
|
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
readonly REPO_ROOT
|
|
readonly CHECKER="${SCRIPT_DIR}/check_personal_data.py"
|
|
|
|
# The repo's own interpreter when there is one, so the check runs the same
|
|
# way under pre-commit, in CI and by hand.
|
|
PYTHON="${PYTHON:-${REPO_ROOT}/.venv/bin/python}"
|
|
if [[ ! -x "$PYTHON" ]]; then
|
|
PYTHON="$(command -v python3)"
|
|
fi
|
|
readonly PYTHON
|
|
|
|
usage() {
|
|
echo "Usage: $(basename "$0") [--all | --message FILE | FILE...]"
|
|
exit 0
|
|
}
|
|
|
|
main() {
|
|
if [[ $# -eq 0 ]]; then
|
|
usage
|
|
fi
|
|
|
|
case "$1" in
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
--message)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "Error: --message needs a file" >&2
|
|
exit 1
|
|
fi
|
|
"$PYTHON" "$CHECKER" --message "$2"
|
|
;;
|
|
--all)
|
|
local files=()
|
|
mapfile -t files < <(git -C "$REPO_ROOT" ls-files)
|
|
if [[ ${#files[@]} -eq 0 ]]; then
|
|
echo "Error: git tracks no files here" >&2
|
|
exit 1
|
|
fi
|
|
# Prefixed with the repo root so this works from any directory.
|
|
local paths=()
|
|
local file
|
|
for file in "${files[@]}"; do
|
|
paths+=("${REPO_ROOT}/${file}")
|
|
done
|
|
"$PYTHON" "$CHECKER" --files "${paths[@]}"
|
|
;;
|
|
*)
|
|
"$PYTHON" "$CHECKER" --files "$@"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|