Add private local OCR comparison package
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Check the staged file set before a private repository is pushed.
|
||||
|
||||
Print only rule names and paths, never matched document text or secret values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ROOT_FILES = {".gitignore", "README.md", "pyproject.toml", "config.example.env"}
|
||||
SUBDIR_SUFFIXES = {
|
||||
"ocr_compare": {".py", ".js", ".css", ".html"},
|
||||
"tests": {".py"},
|
||||
"scripts": {".py"},
|
||||
}
|
||||
SENSITIVE = {
|
||||
"local-user-path": re.compile(r"/(?:Users|home)/[^/\s]+"),
|
||||
"non-loopback-ipv4": re.compile(r"\b(?!127\.0\.0\.1\b)(?:\d{1,3}\.){3}\d{1,3}\b"),
|
||||
"named-organization": re.compile(r"\b(?:fair" + "gentur|A" + "TU|Kawa" + "saki)\b", re.I),
|
||||
"personal-name": re.compile(r"\bPa" + "trick\b", re.I),
|
||||
"email-address": re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.(?!invalid\b)[A-Z]{2,}\b", re.I),
|
||||
"private-key": re.compile(r"-----BEGIN (?:OPENSSH|RSA|EC) PRIVATE KEY-----"),
|
||||
}
|
||||
|
||||
|
||||
def allowed(name: str) -> bool:
|
||||
if name in ROOT_FILES:
|
||||
return True
|
||||
parts = name.split("/")
|
||||
return (len(parts) == 2 and parts[0] in SUBDIR_SUFFIXES and
|
||||
Path(parts[1]).suffix in SUBDIR_SUFFIXES[parts[0]])
|
||||
|
||||
|
||||
def main() -> int:
|
||||
listing = subprocess.run(["git", "ls-files", "--stage", "-z"], cwd=ROOT,
|
||||
capture_output=True, check=True).stdout
|
||||
entries = [entry for entry in listing.split(b"\0") if entry]
|
||||
if not entries:
|
||||
print("No staged or tracked files to audit")
|
||||
return 1
|
||||
findings = []
|
||||
for entry in entries:
|
||||
meta, raw_name = entry.split(b"\t", 1)
|
||||
mode, object_id, stage = meta.decode("ascii").split()
|
||||
name = raw_name.decode("utf-8", "surrogateescape")
|
||||
if not allowed(name):
|
||||
findings.append(("unexpected-file", name))
|
||||
continue
|
||||
if mode not in {"100644", "100755"} or stage != "0":
|
||||
findings.append(("non-regular-file", name))
|
||||
continue
|
||||
blob = subprocess.run(["git", "cat-file", "blob", object_id], cwd=ROOT,
|
||||
capture_output=True, check=True).stdout
|
||||
if len(blob) > 1_000_000:
|
||||
findings.append(("oversized-file", name))
|
||||
continue
|
||||
try:
|
||||
source = blob.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
findings.append(("binary-file", name))
|
||||
continue
|
||||
if name == "scripts/release_audit.py":
|
||||
continue # The rule definitions themselves contain example patterns.
|
||||
for label, pattern in SENSITIVE.items():
|
||||
if pattern.search(source):
|
||||
findings.append((label, name))
|
||||
for label, name in findings:
|
||||
print(f"{label}: {name}")
|
||||
if findings:
|
||||
return 1
|
||||
print(f"Release audit passed: {len(entries)} text files, no private-path or document artifacts")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user