From 0b528cbb5f0e4d5ab0b1224708a94989847a2d64 Mon Sep 17 00:00:00 2001 From: Doris Date: Fri, 22 May 2026 21:13:16 +0000 Subject: [PATCH] Add infra artifact secret-scan guardrail --- .githooks/pre-commit | 5 + docs/operations/SECRETS_MANAGEMENT.md | 31 +++++++ home/doris-dashboard/docs/baselines/README.md | 5 + scripts/scan-secret-bearing-artifacts.sh | 93 +++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100755 .githooks/pre-commit create mode 100755 scripts/scan-secret-bearing-artifacts.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..7d75b32 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +exec "$repo_root/scripts/scan-secret-bearing-artifacts.sh" --staged diff --git a/docs/operations/SECRETS_MANAGEMENT.md b/docs/operations/SECRETS_MANAGEMENT.md index d92fb11..888d358 100644 --- a/docs/operations/SECRETS_MANAGEMENT.md +++ b/docs/operations/SECRETS_MANAGEMENT.md @@ -239,6 +239,37 @@ See: - `docs/operations/INCIDENT_2026-05-22_UNIFI_BASELINE_SECRET_LEAK.md` - `home/doris-dashboard/docs/baselines/README.md` +### Pre-commit guardrail for infra artifacts + +The repo now includes a narrow pre-commit scanner aimed specifically at catching secret-bearing controller/export artifacts before push. + +Files: +- `scripts/scan-secret-bearing-artifacts.sh` +- `.githooks/pre-commit` + +Enable it once per clone: + +```bash +git config core.hooksPath .githooks +chmod +x .githooks/pre-commit scripts/scan-secret-bearing-artifacts.sh +``` + +Run it manually on staged changes: + +```bash +scripts/scan-secret-bearing-artifacts.sh --staged +``` + +What it is for: +- raw baselines +- exports +- snapshots +- dumps +- similar machine-generated infra artifacts + +What it is not: +- a full replacement for GitHub/GitGuardian or a generic secret scanner for every file type + ## Security Reminders - **Key backup**: `C:\Users\Fizzlepoof\Downloads\.git-crypt-secrets.key` — also store in password manager diff --git a/home/doris-dashboard/docs/baselines/README.md b/home/doris-dashboard/docs/baselines/README.md index fb1afc4..2ef67fc 100644 --- a/home/doris-dashboard/docs/baselines/README.md +++ b/home/doris-dashboard/docs/baselines/README.md @@ -29,3 +29,8 @@ See: 2. Inspect them for secret-bearing fields. 3. Commit only a markdown summary or an explicitly redacted artifact. 4. Prefer human-readable summaries over raw appliance dumps. +5. Enable the repo guardrail in each clone: + ```bash + git config core.hooksPath .githooks + chmod +x .githooks/pre-commit scripts/scan-secret-bearing-artifacts.sh + ``` diff --git a/scripts/scan-secret-bearing-artifacts.sh b/scripts/scan-secret-bearing-artifacts.sh new file mode 100755 index 0000000..c1a785b --- /dev/null +++ b/scripts/scan-secret-bearing-artifacts.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +mode="${1:---staged}" +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +if [[ "$mode" != "--staged" && "$mode" != "--files" ]]; then + echo "usage: $0 [--staged|--files ...]" >&2 + exit 2 +fi + +artifact_path_regex='(^|/)(docs/baselines/|baselines/|snapshots?/|exports?/|dumps?/|backups?/|diagnostics?/|artifacts?/|tmp/|temp/)|(^|/).*(baseline|snapshot|export|dump|diagnostic).*(\.json|\.ya?ml|\.txt|\.conf)?$' + +placeholder_regex='(REDACTED|CHANGE_ME||example|placeholder|your[_ -]?token|your[_ -]?key)' + +declare -a files=() +if [[ "$mode" == "--staged" ]]; then + while IFS= read -r f; do + [[ -n "$f" ]] && files+=("$f") + done < <(git diff --cached --name-only --diff-filter=ACMR) +else + shift + for f in "$@"; do + files+=("$f") + done +fi + +if [[ ${#files[@]} -eq 0 ]]; then + exit 0 +fi + +failures=0 + +scan_file() { + local file="$1" + [[ -f "$file" ]] || return 0 + [[ "$file" =~ $artifact_path_regex ]] || return 0 + + python3 - "$file" "$placeholder_regex" <<'PY' +from pathlib import Path +import re, sys + +path = Path(sys.argv[1]) +placeholder = re.compile(sys.argv[2], re.I) +text = path.read_text(errors='ignore') +lines = text.splitlines() + +patterns = [ + (re.compile(r'"x_wireguard_private_key"\s*:\s*"([^"]+)"', re.I), 'embedded WireGuard private key'), + (re.compile(r'"wireguard_client_configuration_file"\s*:\s*"([^"\\]|\\.)*PrivateKey = [A-Za-z0-9+/=]{20,}', re.I), 'embedded WireGuard client config with private key'), + (re.compile(r'PrivateKey\s*=\s*([A-Za-z0-9+/=]{20,})'), 'WireGuard private key line'), + (re.compile(r'Authorization\s*[:=]\s*(Bearer\s+)?[A-Za-z0-9._~+/=-]{16,}', re.I), 'authorization header/token material'), + (re.compile(r'"(token|api[_-]?key|client[_-]?secret|password|secret|cookie)"\s*:\s*"([^"]{12,})"', re.I), 'suspicious secret-like JSON field'), +] + +for idx, line in enumerate(lines, start=1): + for regex, label in patterns: + m = regex.search(line) + if not m: + continue + if placeholder.search(line): + continue + print(f'{path}:{idx}: {label}') + print(f' {line[:220]}') + sys.exit(1) +sys.exit(0) +PY +} + +for file in "${files[@]}"; do + if ! scan_file "$file"; then + failures=1 + fi +done + +if [[ "$failures" -ne 0 ]]; then + cat >&2 <<'EOF' + +Secret-bearing artifact scan failed. + +This guardrail is intentionally narrow: it is meant to stop raw controller/export/baseline artifacts +from being committed with embedded keys or tokens. + +Fix options: +- remove the raw artifact from the commit +- replace it with a sanitized markdown summary +- explicitly redact the secret-bearing fields + +If raw retention is required, store it outside the main repo or in the encrypted secrets repo. +EOF + exit 1 +fi