scripts/scan_git_history_secrets.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
import re
import subprocess
import sys
from collections import OrderedDict
from dataclasses import dataclass
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
MAX_BLOB_SIZE = 2_000_000
@dataclass(frozen=True)
class Pattern:
name: str
regex: re.Pattern[bytes]
PATTERNS = [
Pattern("private_key", re.compile(rb"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----")),
Pattern("aws_access_key", re.compile(rb"\b(?:AKIA|ASIA)[0-9A-Z]{16}\b")),
Pattern(
"aws_secret_key",
re.compile(rb"aws_secret_access_key\s*[=:]\s*[\"']?[A-Za-z0-9/+=]{40}"),
),
Pattern("github_pat", re.compile(rb"github_pat_[A-Za-z0-9_]{20,}")),
Pattern("github_token", re.compile(rb"\bgh[pousr]_[A-Za-z0-9]{20,}\b")),
Pattern("slack_token", re.compile(rb"\bxox[baprs]-[A-Za-z0-9-]{10,}\b")),
Pattern("openai_key", re.compile(rb"\bsk-[A-Za-z0-9]{20,}\b")),
Pattern("google_api_key", re.compile(rb"\bAIza[0-9A-Za-z\-_]{35}\b")),
Pattern("stripe_live", re.compile(rb"\b(?:sk|rk)_live_[0-9A-Za-z]{16,}\b")),
Pattern(
"jwt",
re.compile(rb"\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b"),
),
Pattern("bearer_token", re.compile(rb"Bearer\s+[A-Za-z0-9._\-=]{24,}")),
Pattern("cookie_header", re.compile(rb"Cookie:\s*[^\n]{20,}")),
Pattern(
"session_assignment",
re.compile(
rb"\b(?:session|csrf|token)\s*[=:]\s*[\"'][A-Za-z0-9._\-=%]{20,}[\"']"
),
),
Pattern(
"bcrypt_hash",
re.compile(rb'password_hash\s*=\s*"\$2[aby]\$[0-9]{2}\$[./A-Za-z0-9]{53}"'),
),
]
def run_git(*args: str, input_text: str | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", *args],
cwd=REPO_ROOT,
input=input_text,
text=True,
capture_output=True,
check=True,
)
def iter_reachable_blobs() -> list[tuple[str, int, str]]:
rev_list = run_git("rev-list", "--objects", "--all").stdout
objects: OrderedDict[str, str] = OrderedDict()
for line in rev_list.splitlines():
if not line.strip():
continue
parts = line.split(" ", 1)
sha = parts[0]
path = parts[1] if len(parts) > 1 else ""
objects.setdefault(sha, path)
batch_input = "".join(sha + "\n" for sha in objects)
batch = run_git(
"cat-file",
"--batch-check=%(objectname) %(objecttype) %(objectsize)",
input_text=batch_input,
).stdout
blobs: list[tuple[str, int, str]] = []
for line in batch.splitlines():
sha, object_type, size_text = line.split()
if object_type != "blob":
continue
size = int(size_text)
if size > MAX_BLOB_SIZE:
continue
blobs.append((sha, size, objects.get(sha, "")))
return blobs
def scan_blobs(blobs: list[tuple[str, int, str]]) -> tuple[int, list[tuple[str, str, str, str]]]:
findings: list[tuple[str, str, str, str]] = []
text_blob_count = 0
proc = subprocess.Popen(
["git", "cat-file", "--batch"],
cwd=REPO_ROOT,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
assert proc.stdin is not None
assert proc.stdout is not None
for sha, _, _ in blobs:
proc.stdin.write((sha + "\n").encode("utf-8"))
proc.stdin.close()
for sha, _, path in blobs:
header = proc.stdout.readline().decode("utf-8", "replace").strip()
if not header:
continue
_, object_type, size_text = header.split()
if object_type != "blob":
continue
size = int(size_text)
data = proc.stdout.read(size)
proc.stdout.read(1)
if b"\x00" in data:
continue
text_blob_count += 1
for pattern in PATTERNS:
match = pattern.regex.search(data)
if not match:
continue
sample = match.group(0)[:120].decode("utf-8", "replace")
findings.append((pattern.name, path, sha[:12], sample))
break
proc.wait()
return text_blob_count, findings
def main() -> int:
try:
blobs = iter_reachable_blobs()
text_blob_count, findings = scan_blobs(blobs)
except subprocess.CalledProcessError as exc:
sys.stderr.write(exc.stderr)
return exc.returncode
print(f"Scanned {len(blobs)} reachable blobs, {text_blob_count} text blobs.")
if not findings:
print("No matches for targeted secret patterns in reachable git history.")
return 0
print("Potential secret findings:")
for name, path, sha, sample in findings:
print(f"- {name}: {path} [{sha}] -> {sample}")
return 1
if __name__ == "__main__":
raise SystemExit(main())