refuse.py python
84 lines 3.2 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 23 hours ago
1 """Secret-pattern refuse helpers (§QR.6.3)."""
2
3 from __future__ import annotations
4
5 import re
6 from pathlib import Path
7
8 from tools.desktop_release.constants import PRIVATE_KEY_BASENAME_MARKERS, PUBLIC_KEY_ALLOWED_SUFFIXES
9
10 # Private-key PEM headers (workflow / tree lint).
11 _PEM_PRIVATE_RE = re.compile(
12 r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
13 )
14 # Hard-coded password assignments in YAML (crude but fail-closed for literals).
15 _HARDCODED_PASSWORD_RE = re.compile(
16 r"(?i)(password|passwd|secret)\s*[:=]\s*['\"][^'\"]{4,}['\"]",
17 )
18 # PFX / PKCS12 magic often appears as base64 of 0x30 0x82… — flag obvious literals.
19 _PFX_MAGIC_RE = re.compile(r"(?i)MII[A-Za-z0-9+/]{40,}={0,2}")
20
21
22 class RefuseError(ValueError):
23 """Raised when secret-like material is refused."""
24
25
26 def scan_text_for_secret_patterns(text: str) -> list[str]:
27 """Return list of matched secret-pattern labels (empty = clean)."""
28 hits: list[str] = []
29 if _PEM_PRIVATE_RE.search(text):
30 hits.append("pem_private_key")
31 if _HARDCODED_PASSWORD_RE.search(text):
32 hits.append("hardcoded_password")
33 # Avoid flagging `${{ secrets.NAME }}` — only large base64-looking blobs.
34 for match in _PFX_MAGIC_RE.finditer(text):
35 snippet = match.group(0)
36 if "secrets." in text[max(0, match.start() - 40) : match.start()]:
37 continue
38 if len(snippet) >= 64:
39 hits.append("pfx_like_blob")
40 break
41 return hits
42
43
44 def refuse_secret_write_to_repo(path: Path, content: bytes | str) -> None:
45 """Refuse writing secret-like content into a repo path.
46
47 Used by tests and helpers — not a runtime daemon. Raises :class:`RefuseError`
48 when content matches private-key / password / PFX patterns.
49 """
50 text = content.decode("utf-8", errors="replace") if isinstance(content, bytes) else content
51 hits = scan_text_for_secret_patterns(text)
52 suffix = path.suffix.lower()
53 name = path.name.lower()
54 if suffix in {".p12", ".pfx"} or name.endswith(".p12") or name.endswith(".pfx"):
55 hits.append("cert_file_extension")
56 if hits:
57 raise RefuseError(f"refuse writing secrets to {path}: {', '.join(hits)}")
58
59
60 def refuse_private_key_under_desktop_keys(path: Path) -> None:
61 """Refuse private-key style filenames under ``desktop/keys/``.
62
63 Public material (``.pub``, ``.asc``, ``.minisign.pub``, ``README.md``) is allowed.
64 """
65 parts = Path(path).as_posix().replace("\\", "/").split("/")
66 try:
67 desktop_idx = parts.index("desktop")
68 keys_idx = parts.index("keys", desktop_idx + 1)
69 except ValueError:
70 return
71 if keys_idx != desktop_idx + 1:
72 return
73 if keys_idx + 1 >= len(parts):
74 return
75 basename = parts[-1]
76 if basename in {"README.md", ".gitkeep"}:
77 return
78 lower = basename.lower()
79 if any(lower.endswith(suf) for suf in PUBLIC_KEY_ALLOWED_SUFFIXES):
80 return
81 if any(marker in lower for marker in PRIVATE_KEY_BASENAME_MARKERS):
82 raise RefuseError(f"private key filename refused under desktop/keys/: {basename}")
83 if lower.endswith((".pem", ".p12", ".pfx", ".key")):
84 raise RefuseError(f"private key filename refused under desktop/keys/: {basename}")
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 22 hours ago