#!/usr/bin/env python3 """Check staged Git blobs, not working-tree copies. Does not alter the index.""" import json import re import subprocess import sys def git(*args): return subprocess.check_output(["git", *args]) def main(): errors, total, count = [], 0, 0 for row in git("ls-files", "--stage", "-z").split(b"\0"): if not row: continue meta, rawpath = row.split(b"\t", 1) mode, oid, stage = meta.decode().split() path = rawpath.decode("utf-8") count += 1 if mode == "160000" or path.startswith(("datasets/", "experiments/")) or any(p in path.split("/") for p in (".venv", ".git", "__pycache__")): errors.append({"path":path, "rule":"forbidden data/environment/submodule"}) continue size = int(git("cat-file", "-s", oid)) total += size if size > 25 * 1024 * 1024: errors.append({"path":path, "rule":"blob exceeds 25 MiB policy"}) continue if re.search(r"(^|/)(?:\.env(?:\..*)?|id_rsa|id_ed25519)$|\.(?:pem|key|p12)$", path, re.I): errors.append({"path":path, "rule":"credential filename"}) if path.endswith(".gz"): continue # generated hash-only inventory, validated separately data = git("cat-file", "blob", oid).decode("utf-8", errors="replace") rules = { "private key": r"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----", "access token": r"(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|AKIA[A-Z0-9]{16})", "credential URL": r"https?://[^\s/]+:[^\s/@]+@", "literal password": r"(?i)(?:password|passwd|api_key|secret_key)\s*[:=]\s*['\"][^'\"\n]{6,}['\"]", } for rule, pattern in rules.items(): if re.search(pattern, data): errors.append({"path":path, "rule":rule}) print(json.dumps({"pass":not errors, "files":count, "logical_bytes":total, "issues":errors}, indent=2)) return int(bool(errors)) if __name__ == "__main__": sys.exit(main())