#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" python3 - "$@" <<'PY' import ipaddress import json import re import sys from pathlib import Path ROOT = Path.cwd() JSON_MODE = "--json" in sys.argv[1:] TEXT_SUFFIXES = { ".csv", ".html", ".htm", ".json", ".md", ".mjs", ".sql", ".txt", ".yaml", ".yml", } SAFE_IP_NETWORKS = [ ipaddress.ip_network("192.0.2.0/24"), ipaddress.ip_network("198.51.100.0/24"), ipaddress.ip_network("203.0.113.0/24"), ] SAFE_IPS = { ipaddress.ip_address("0.0.0.0"), ipaddress.ip_address("127.0.0.1"), } SAFE_EMAIL_DOMAINS = { "example.com", "example.net", "example.org", "invalid", "localhost", } SAFE_SECRET_VALUES = { "", "***", "****", "xxxxx", "xxxx", "change_me", "changeme", "redacted", "placeholder", "example", "demo", "demo-only", "", "", "", "", } IPV4_RE = re.compile(r"(? bool: try: ip = ipaddress.ip_address(value) except ValueError: return True return ip in SAFE_IPS or any(ip in network for network in SAFE_IP_NETWORKS) def is_safe_email(domain: str, email: str) -> bool: normalized = domain.lower() if normalized in SAFE_EMAIL_DOMAINS: return True if normalized.endswith(".example") or normalized.endswith(".invalid"): return True return "demo" in email.lower() def is_safe_secret_value(value: str) -> bool: cleaned = value.strip().strip("\"'").strip() lowered = cleaned.lower() if lowered in SAFE_SECRET_VALUES: return True if cleaned.startswith("<") and cleaned.endswith(">"): return True if set(cleaned) <= {"*", "x", "X", "-"}: return True return False def collect_scope() -> list[Path]: files = [ROOT / "README.md"] for path in (ROOT / "docs").rglob("*"): if not path.is_file(): continue rel = path.relative_to(ROOT).as_posix() name = path.name if rel.startswith(("docs/demo/", "docs/fixtures/", "docs/screenshots/", "docs/assets/screenshots/")): files.append(path) continue if any(marker in name for marker in ("DEMO", "PILOT", "CUSTOMER", "RELEASE_READINESS", "FREEZE")): files.append(path) return sorted(set(files)) def add_finding(findings: list[dict], path: Path, line_no: int, rule: str, value: str) -> None: findings.append( { "file": path.relative_to(ROOT).as_posix(), "line": line_no, "rule": rule, "value": value, } ) def scan_line(findings: list[dict], path: Path, line_no: int, line: str) -> None: stripped = line.strip() if stripped.startswith("#") and "grep -n" in stripped: return for match in IPV4_RE.finditer(line): value = match.group(0) if not is_safe_ip(value): add_finding(findings, path, line_no, "real_ipv4_not_in_demo_range", value) for match in EMAIL_RE.finditer(line): email = match.group(0) domain = match.group(1) if not is_safe_email(domain, email): add_finding(findings, path, line_no, "non_demo_email", email) for match in FORBIDDEN_DOMAIN_RE.finditer(line): add_finding(findings, path, line_no, "known_live_domain_or_codename", match.group(0)) for match in SECRET_ASSIGN_RE.finditer(line): value = match.group(2) if not is_safe_secret_value(value): add_finding(findings, path, line_no, "credential_like_assignment", match.group(0)) for match in BEARER_RE.finditer(line): value = match.group(1) if not is_safe_secret_value(value): add_finding(findings, path, line_no, "bearer_secret", "Authorization: Bearer ...") for match in WORKSTATION_RE.finditer(line): value = match.group(0) if "DEMO" not in value: add_finding(findings, path, line_no, "realistic_workstation_name", value) for match in CYRILLIC_FIO_RE.finditer(line): add_finding(findings, path, line_no, "possible_cyrillic_fio", match.group(0)) def scan_text_file(path: Path) -> list[dict]: findings: list[dict] = [] text = path.read_text(encoding="utf-8", errors="ignore") for line_no, line in enumerate(text.splitlines(), start=1): scan_line(findings, path, line_no, line) return findings def scan_png(path: Path) -> list[dict]: findings: list[dict] = [] data = path.read_bytes() png_signature = b"\x89PNG\r\n\x1a\n" if len(data) <= 1000 or not data.startswith(png_signature): add_finding(findings, path, 0, "screenshot_not_png_or_too_small", str(len(data))) return findings metadata_text = data.decode("latin-1", errors="ignore") for line_no, line in enumerate(metadata_text.splitlines(), start=1): scan_line(findings, path, line_no, line) return findings def main() -> int: files = collect_scope() findings: list[dict] = [] for path in files: if path.suffix.lower() == ".png": findings.extend(scan_png(path)) elif path.suffix.lower() in TEXT_SUFFIXES: findings.extend(scan_text_file(path)) result = { "ok": not findings, "scope_files": [path.relative_to(ROOT).as_posix() for path in files], "findings": findings, } if JSON_MODE: print(json.dumps(result, ensure_ascii=False, indent=2)) elif findings: print("Demo safety check failed:", file=sys.stderr) for finding in findings: print( f"{finding['file']}:{finding['line']}: {finding['rule']}: {finding['value']}", file=sys.stderr, ) else: print(f"Demo safety check passed ({len(files)} files scanned).") return 0 if not findings else 2 if __name__ == "__main__": raise SystemExit(main()) PY