#!/usr/bin/env python3 """Fail closed when a release tree contains secret or binary payloads.""" from __future__ import annotations import re import sys from pathlib import Path _FORBIDDEN_SUFFIXES = frozenset( {".der", ".jks", ".key", ".keystore", ".p12", ".pfx", ".pkcs12", ".pyc", ".pyo"} ) _CONFIG_SUFFIXES = frozenset({".conf", ".env", ".ini", ".json", ".toml", ".txt", ".yaml", ".yml"}) _PRIVATE_BLOCK = re.compile( "-----BEGIN " + r"(?:ENCRYPTED |RSA |EC |OPENSSH )?PRIVATE KEY-----", re.IGNORECASE, ) _API_SECRET = re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b") _ASSIGNMENT = re.compile( r"(?im)^\s*(?:[A-Za-z0-9_]*_)?" r"(?:credential|password|passwd|secret|token|api_key|private_key)" r"\s*[:=]\s*['\"]?([^'\"\s#;,]{8,})" ) _PLACEHOLDERS = ("${", "<", "change", "example", "placeholder", "replace-", "tbd") def _reject(message: str) -> int: print(f"release secret scan rejected: {message}", file=sys.stderr) return 1 def scan(root: Path) -> int: if not root.is_absolute() or not root.is_dir() or root.is_symlink(): return _reject("root must be an existing absolute directory") for path in sorted(root.rglob("*")): relative = path.relative_to(root) if path.is_symlink(): return _reject(f"symbolic link {relative}") if path.is_dir(): if path.name == "__pycache__": return _reject(f"cache directory {relative}") continue if not path.is_file() or path.suffix.casefold() in _FORBIDDEN_SUFFIXES: return _reject(f"forbidden file type {relative}") try: raw = path.read_bytes() if b"\x00" in raw: return _reject(f"binary file {relative}") text = raw.decode("utf-8") except (OSError, UnicodeDecodeError): return _reject(f"unreadable or non-UTF-8 file {relative}") if _PRIVATE_BLOCK.search(text) or _API_SECRET.search(text): return _reject(f"private key or API secret shape in {relative}") if path.suffix.casefold() in _CONFIG_SUFFIXES or path.name.startswith(".env"): for match in _ASSIGNMENT.finditer(text): value = match.group(1).casefold() if value in {"false", "none", "null", "true"}: continue if any(marker in value for marker in _PLACEHOLDERS): continue return _reject(f"literal credential shape in {relative}") return 0 def main() -> int: if len(sys.argv) != 2: print("usage: scan_release_secrets.py ABSOLUTE_RELEASE_ROOT", file=sys.stderr) return 2 return scan(Path(sys.argv[1])) if __name__ == "__main__": raise SystemExit(main())