"""Read-only verification at the Windows Desktop handoff receipt boundary.""" import json from pathlib import Path, PurePosixPath import re import struct from hermes_cli.main_desktop import ( _HTML_TAG_WITH_URL, _MODULE_TAG, _desktop_build_needed, _desktop_exe_integrity_error, _desktop_packaged_executable, ) def _verify_packaged_entry(resources: Path) -> None: """Read ASAR's Pickle header and the entry declared by packaged package.json. dist/** is unpacked by electron-builder. No Node install or application launch is needed at this boundary; this is not a full dependency audit. """ archive = resources / "app.asar" try: with archive.open("rb") as stream: size, header_size, payload_size, json_size = struct.unpack("<4I", stream.read(16)) if (size != 4 or header_size != payload_size + 4 or payload_size != 4 + ((json_size + 3) // 4) * 4 or not 0 < json_size <= 64 * 1024 * 1024 or 8 + header_size > archive.stat().st_size): raise ValueError("invalid ASAR header") header = json.loads(stream.read(json_size)) def read_member(name: str) -> bytes: path = PurePosixPath(name) if not name or path.is_absolute() or ".." in path.parts or "\\" in name or ":" in name: raise ValueError("invalid ASAR entry path") node = header for part in path.parts: node = node["files"][part] length = node["size"] if not isinstance(length, int) or length <= 0: raise ValueError(f"empty ASAR entry: {name}") if node.get("unpacked"): data = (resources / "app.asar.unpacked" / path).read_bytes() else: offset = int(node["offset"]) if offset < 0 or 8 + header_size + offset + length > archive.stat().st_size: raise ValueError(f"truncated ASAR entry: {name}") stream.seek(8 + header_size + offset) data = stream.read(length) if len(data) != length or not data.strip(): raise ValueError(f"incomplete ASAR entry: {name}") return data package = json.loads(read_member("package.json")) read_member(package["main"]).decode("utf-8") except (OSError, ValueError, KeyError, TypeError, struct.error) as exc: raise RuntimeError(f"The updated Desktop archive or main entry is invalid: {exc}") from exc index = resources / "app.asar.unpacked" / "dist" / "index.html" try: html = index.read_text(encoding="utf-8-sig") if not any(_MODULE_TAG.search(match.group(0)) and match.group(0).lower().startswith(" Path: """The checkout this module was imported from: the only root the receipt may describe.""" return Path(__file__).resolve().parent.parent def _root_to_verify(project_root: Path | None) -> Path: """The root the receipt may describe: a caller's root is a hint, not a contract. The hand-off script is read before the pull, so an update that ships a fix to the caller side cannot apply it to its own run: a pre-fix ``windows.ps1`` still passes ``Path.cwd()``, which is HERMES_HOME and never holds a packaged Desktop app. Honour a caller-supplied root only while it actually carries one, and fall back to the checkout this module was imported from -- the tree the update just wrote. A supplied root that does carry a packaged app is still verified as given, so real damage there stays fail-closed. """ if project_root is None: return checkout_root() if _desktop_packaged_executable(project_root / "apps" / "desktop") is not None: return project_root return checkout_root() def verify_windows_desktop_update(project_root: Path | None = None) -> None: """Raise when a zero-exit updater left an incomplete or stale packaged app. The root defaults to the imported checkout, never the caller's cwd: the hand-off is spawned from HERMES_HOME by the pre-update Desktop, and a cwd-derived root reported a healthy install as "Desktop executable is missing" (Sep 2026). A caller-supplied root with no packaged app falls back to that same checkout, so a stale in-flight hand-off cannot fail a healthy install either. """ project_root = _root_to_verify(project_root) desktop = project_root / "apps" / "desktop" executable = _desktop_packaged_executable(desktop) if executable is None: raise RuntimeError("The updated Desktop executable is missing") error = _desktop_exe_integrity_error(executable) if error: raise RuntimeError(f"The updated Desktop executable is invalid: {error}") _verify_packaged_entry(executable.parent / "resources") if _desktop_build_needed(desktop, project_root, source_mode=False): raise RuntimeError("The updated Desktop build is stale, unstamped, or incomplete") if __name__ == "__main__": verify_windows_desktop_update()