#!/usr/bin/env python3 """开源敏感词审计:扫描 git 跟踪的文本文件,命中即失败(exit 1)。 三用:迁移后本地把关 / CI 守门 / 推送后生成移交报告。 用法: python scripts/audit_sensitive.py python scripts/audit_sensitive.py . python scripts/audit_sensitive.py --report audit-report.md """ import argparse import os import re import subprocess import sys from pathlib import Path FORBIDDEN = [ r"demate", r"德玛特", r"佛山", r"8\.163\.250\.\d+", r"192\.168\.0\.\d+", r"aliyun_demate", r"海漫思", r"超聚变|FusionXpark", r"Leonxu6/datamanagement", r"qwable", r"om-root-2026|DemateU8|demate-kg", ] TEXT_EXT_SKIP = {".png", ".jpg", ".jpeg", ".gif", ".ico", ".woff", ".woff2", ".jar", ".pyc"} _GIT_TIMEOUT = 10 def tracked_files(root: Path) -> list[Path]: out = subprocess.run( ["git", "ls-files", "-z"], cwd=root, capture_output=True, text=True, encoding="utf-8", check=True, timeout=_GIT_TIMEOUT, ).stdout return [root / p for p in out.split("\0") if p and (root / p).suffix.lower() not in TEXT_EXT_SKIP] def _read_tracked_text(path: Path) -> str | None: """Read repository text without dereferencing a tracked symlink outside the worktree.""" try: if path.is_symlink(): return os.readlink(path) if not path.is_file(): return None return path.read_text(encoding="utf-8") except (UnicodeDecodeError, OSError): return None def scan(root: Path) -> list[tuple[str, int, str, str]]: pats = [(p, re.compile(p, re.IGNORECASE)) for p in FORBIDDEN] hits = [] me = Path(__file__).resolve() for f in tracked_files(root): if not f.is_symlink() and f.resolve() == me: continue text = _read_tracked_text(f) if text is None: continue for ln, line in enumerate(text.splitlines(), 1): for raw, rx in pats: if rx.search(line): hits.append((str(f.relative_to(root)), ln, raw, line.strip()[:120])) return hits def _repository_root(value: str | None) -> Path: if value is not None: root = Path(value).expanduser().resolve() if not root.is_dir(): raise ValueError("repository root must be an existing directory") return root return Path(subprocess.run( ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, encoding="utf-8", check=True, timeout=_GIT_TIMEOUT, ).stdout.strip()) def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser() ap.add_argument("root", nargs="?", help="repository root (defaults to current Git worktree)") ap.add_argument("--report", help="另存 markdown 报告的路径") args = ap.parse_args(argv) try: root = _repository_root(args.root) except (ValueError, subprocess.SubprocessError) as exc: ap.error(str(exc)) hits = scan(root) lines = ["# 敏感词审计报告", "", f"- 扫描根:`{root}`", f"- 模式数:{len(FORBIDDEN)}(大小写不敏感,零容忍)", f"- 结果:**{'❌ ' + str(len(hits)) + ' 处命中' if hits else '✅ 零命中'}**", ""] if hits: lines += ["| 文件 | 行 | 模式 | 内容 |", "|---|---|---|---|"] lines += [f"| {f} | {ln} | `{p}` | `{t}` |" for f, ln, p, t in hits] report = "\n".join(lines) print(report) if args.report: Path(args.report).write_text(report + "\n", encoding="utf-8") print(f"\n报告已写入 {args.report}") return 1 if hits else 0 if __name__ == "__main__": sys.exit(main())