62 lines
1.9 KiB
Python
62 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Reject common credential material before it reaches Git history."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
PATTERNS = {
|
|
"private key": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
|
|
"GitHub-style token": re.compile(r"\bgh[pousr]_[A-Za-z0-9]{30,}\b"),
|
|
"OpenAI-style key": re.compile(r"\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b"),
|
|
"AWS access key": re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
|
|
"credential in URL": re.compile(r"https?://[^\s/:@]+:[^\s/@]+@"),
|
|
}
|
|
|
|
SKIP_PARTS = {".git", "work", "build", ".vs", ".idea", ".vscode"}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
root = args.root.resolve()
|
|
errors: list[str] = []
|
|
scanned = 0
|
|
|
|
for directory, subdirectories, filenames in os.walk(root):
|
|
subdirectories[:] = sorted(
|
|
name
|
|
for name in subdirectories
|
|
if name not in SKIP_PARTS and not name.startswith("build-")
|
|
)
|
|
for filename in sorted(filenames):
|
|
path = Path(directory, filename)
|
|
relative = path.relative_to(root)
|
|
if path.stat().st_size > 2_000_000:
|
|
continue
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
continue
|
|
scanned += 1
|
|
for label, pattern in PATTERNS.items():
|
|
if pattern.search(text):
|
|
errors.append(f"{relative}: possible {label}")
|
|
|
|
if errors:
|
|
for error in errors:
|
|
print(f"secret scan failed: {error}")
|
|
return 1
|
|
print(f"secret scan passed across {scanned} text files")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|