From 42b02c47be9b285099203e44a2570636d4ca6f03 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Fri, 11 Sep 2026 14:47:10 +0200 Subject: krino: foundation — sexp reader, config language, init/new/check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/hooks/pre-commit | 4 +++ scripts/leak-check | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100755 scripts/hooks/pre-commit create mode 100755 scripts/leak-check (limited to 'scripts') diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 0000000..ec9f507 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# Installed by `make install-hooks`: refuse commits that carry personal data. +exec "$(git rev-parse --show-toplevel)/scripts/leak-check" diff --git a/scripts/leak-check b/scripts/leak-check new file mode 100755 index 0000000..fbbd5d5 --- /dev/null +++ b/scripts/leak-check @@ -0,0 +1,70 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# +# leak-check: refuse personal data in the files git would commit. +# +# Scans every staged file (the index, not the working tree), and the names +# of staged files against the private list, for: +# - the current user's home directory, e.g. /home/alice; +# - email addresses (LICENSE is exempt); +# - a private pattern list: one extended regular expression per line, +# blank lines and lines starting with # ignored, matched ignoring case. +# Its path is set per clone with: git config krino.leakpatterns FILE +# Keep that file outside the repository so the list is never published. +# +# A line matching the regex in `git config krino.leakallow` is never +# reported (for example a public contact address in the README). +# +# Prints file:line for each hit, never the matched text, so a hit on a +# private pattern does not echo the secret. Exits 0 when clean, 1 when +# something matched, 2 when the private list is configured but missing. + +set -eu + +cd "$(git rev-parse --show-toplevel)" + +allow=$(git config --get krino.leakallow || true) +hits=0 + +# check LABEL GREP-ARGS...: report staged lines that match, as file:line. +check() { + label=$1 + shift + out=$(git grep --cached -n -I "$@" | { if [ -n "$allow" ]; then grep -v -E -i -e "$allow"; else cat; fi; } | cut -d: -f1,2 || true) + if [ -n "$out" ]; then + printf 'leak-check: %s:\n%s\n' "$label" "$out" | sed '2,$s/^/ /' >&2 + hits=1 + fi +} + +home=${HOME%/} # a HOME of / becomes empty and is skipped +if [ -n "$home" ]; then + check "your home directory ($home)" -F -e "$home" -- . +fi + +check "an email address" -E -e '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' -- . ':(exclude)LICENSE' + +list=$(git config --type=path --get krino.leakpatterns || true) +if [ -z "$list" ]; then + echo "leak-check: no private pattern list (git config krino.leakpatterns FILE); built-in checks only" >&2 +elif [ ! -r "$list" ]; then + echo "leak-check: private pattern list $list is configured but cannot be read" >&2 + exit 2 +else + patterns=$(mktemp) + trap 'rm -f "$patterns"' EXIT + grep -v -e '^[[:space:]]*#' -e '^[[:space:]]*$' "$list" > "$patterns" || true + if [ -s "$patterns" ]; then + check "a private pattern from $list" -i -E -f "$patterns" -- . + named=$(git ls-files --cached | grep -i -E -f "$patterns" || true) + if [ -n "$named" ]; then + printf 'leak-check: a file name matching a private pattern:\n%s\n' "$named" | sed '2,$s/^/ /' >&2 + hits=1 + fi + fi +fi + +if [ "$hits" -ne 0 ]; then + echo "leak-check: remove the data above before committing; for a false positive, narrow the pattern or set krino.leakallow" >&2 + exit 1 +fi -- cgit v1.3