# tools/check_sensitive.py —— 提交前扫描暂存区内容 # 不自动运行:作为 pre-commit 钩子在仓库根目录执行;加 --self-test 可以单独自检 import re import subprocess import sys from pathlib import Path PATTERNS = { "内网 IP": re.compile(r"\b10\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|\b192\.168\.\d{1,3}\.\d{1,3}\b|\b172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}\b"), "疑似密钥": re.compile(r"(?i)\b(?:sk|ak|api[_-]?key|secret|token)[\w-]*\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{16,}"), "私钥": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"), "root 绝对路径": re.compile(r"(? list[str]: if not WORDS_FILE.exists(): return [] return [w.strip() for w in WORDS_FILE.read_text(encoding="utf-8").splitlines() if w.strip() and not w.startswith("#")] def scan_text(name: str, text: str, words: list[str]) -> list[str]: problems = [] for no, line in enumerate(text.splitlines(), start=1): if ALLOW_MARK in line: continue for label, pat in PATTERNS.items(): if pat.search(line): problems.append(f"{name}:{no} {label}") for w in words: if w.lower() in line.lower(): problems.append(f"{name}:{no} 敏感词(见私有清单)") # 不打印敏感词本身 return problems def staged_files() -> list[str]: out = subprocess.run(["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"], capture_output=True, text=True, check=True).stdout return [f for f in out.splitlines() if f] def main() -> int: words = load_words() problems = [] for f in staged_files(): content = subprocess.run(["git", "show", f":{f}"], capture_output=True).stdout # 读暂存区版本 try: problems += scan_text(f, content.decode("utf-8"), words) except UnicodeDecodeError: continue # 跳过二进制文件 for p in problems: print("✗", p) if problems: print("发现疑似敏感内容,已阻止提交。确认无误的行可加注释 sensitive-ok。") return 1 if problems else 0 if __name__ == "__main__" and "--self-test" in sys.argv: sample = "url: http://192.168.1.20:8080\napi_key = 'abcdefghijklmnopqrstuvwxyz'\n正常的一行\n" # sensitive-ok found = scan_text("demo.md", sample, ["某客户"]) assert len(found) == 2, found assert scan_text("demo.md", "某客户的需求", ["某客户"]) == ["demo.md:1 敏感词(见私有清单)"] print("自检通过:", found) elif __name__ == "__main__": sys.exit(main())