Add infra artifact secret-scan guardrail
This commit is contained in:
93
scripts/scan-secret-bearing-artifacts.sh
Executable file
93
scripts/scan-secret-bearing-artifacts.sh
Executable file
@@ -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 <path> ...]" >&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|<redacted>|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
|
||||
Reference in New Issue
Block a user