cang-ying/scripts/secret_scan.py
Guanghu Domestic Migration a2fa7d57d8 chore: import sanitized domestic snapshot for REPO-005
Source snapshot: 23037fa3a2e9b0597162735755e92fc763bf4d94
2026-07-17 15:55:48 +08:00

83 lines
3.0 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""轻量、零依赖的 Git 暂存/推送密钥扫描器。"""
from __future__ import annotations
import argparse
import os
import re
import subprocess
import sys
from pathlib import Path
FORBIDDEN_PATH = re.compile(
r"(^|/)(\.env(?:\..*)?|secrets?/|.*\.(?:pem|key|p12|pfx))$", re.I
)
ASSIGNMENT = re.compile(
r"\b(?:[A-Z][A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY|ACCESS_KEY|CLIENT_SECRET)|"
r"(?:api[_-]?key|token|secret|password|private[_-]?key|access[_-]?key|client[_-]?secret))"
r"\s*[:=]\s*['\"]([^'\"\r\n]{16,})['\"]",
re.I,
)
BEARER = re.compile(r"\bBearer\s+([A-Za-z0-9_\-]{20,})", re.I)
PROVIDER = re.compile(r"\b(?:sk|rk|ghp|github_pat|AIza)[A-Za-z0-9_\-]{16,}\b", re.I)
PLACEHOLDER = re.compile(r"(?:^|[^a-z])(xxx|example|your[_-]?|replace|changeme|dummy|redacted|<[^>]+>|\.\.\.)(?:$|[^a-z])", re.I)
def run_git(*args: str) -> bytes:
return subprocess.check_output(["git", *args])
def looks_real(value: str) -> bool:
return not PLACEHOLDER.search(value)
def scan_text(text: str) -> list[tuple[int, str]]:
hits: list[tuple[int, str]] = []
for line_no, line in enumerate(text.splitlines(), 1):
if not line.startswith("+") or line.startswith("+++"):
continue
for pattern, kind in ((ASSIGNMENT, "疑似明文密钥赋值"), (BEARER, "疑似 Bearer 令牌"), (PROVIDER, "疑似服务商令牌")):
for match in pattern.finditer(line):
value = match.group(1) if match.lastindex else match.group(0)
if looks_real(value):
hits.append((line_no, kind))
break
return hits
def main() -> int:
os.chdir(Path(__file__).resolve().parents[1])
parser = argparse.ArgumentParser()
parser.add_argument("--staged", action="store_true")
parser.add_argument("--range")
args = parser.parse_args()
if args.staged:
raw_names = run_git("diff", "--staged", "--name-only", "-z")
names = [n.decode("utf-8", "replace") for n in raw_names.split(b"\0") if n]
patch = run_git("diff", "--staged", "--no-ext-diff", "--unified=0").decode("utf-8", "replace")
elif args.range:
names = run_git("diff", "--name-only", args.range).decode("utf-8", "replace").splitlines()
patch = run_git("diff", "--no-ext-diff", "--unified=0", args.range).decode("utf-8", "replace")
else:
parser.error("choose --staged or --range")
failures = []
for name in names:
if FORBIDDEN_PATH.search(name):
failures.append(f"禁止提交本地密钥路径: {name}")
for line_no, kind in scan_text(patch):
failures.append(f"{kind}(补丁行 {line_no}")
if failures:
print("\n密钥保护已阻止本次 Git 操作:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
print("移除密钥,改用苍耳本机仓库外的环境变量/密钥文件后再试。", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())