#!/usr/bin/env python3 """What every shipped desktop bundle must contain, in one place. Three packages now ship the same payload — the macOS ``.app``/``.dmg``, the Linux ``.tar.gz``, and the Windows ``.zip`` that carries the Linux bundle as its WSL2 payload — and each one has a verifier that must refuse the same failures: a science stack that silently did not install, a source tree missing the Web UI or the R worker, bytecode the app will rewrite in place, a developer's ``.env`` swept into the image. Written once here rather than copied into each verifier, for the same reason the two sandbox smokes share one implementation (``harness/smoke/sandbox_boundary.py``): two copies drift until one platform quietly stops checking what the other still does, and the platform that stopped is the one that ships the broken image. Stdlib only, and importable from a plain ``sys.path`` insert of ``scripts/`` — these run against an unpacked release artifact, where the project is not installed. """ from __future__ import annotations import ast import hashlib import json import re import unicodedata from html.parser import HTMLParser from pathlib import Path, PurePosixPath from urllib.parse import unquote, urlsplit _HERE = Path(__file__).resolve().parent #: The pip/import manifest both builders install from and every verifier checks. MANIFEST = _HERE / "bundled_packages.txt" #: Resources whose absence only shows up at runtime — a missing app.js is a #: blank browser tab, a missing r_worker.R is a dead R channel — long after #: release. REQUIRED_SOURCES = ( "openai4s/__init__.py", "openai4s/cli/main.py", "openai4s/kernel/worker.py", "openai4s/kernel/r_worker.R", "openai4s/compute/templates/run.sh.tmpl", "openai4s/compute/templates/wrapper.sh.tmpl", "openai4s/server/webui/index.html", "openai4s/server/webui/dist/index.html", "openai4s/server/webui/theme-bootstrap.js", "openai4s/server/webui/app.js", "openai4s/server/webui/style.css", "openai4s/server/webui/vendor/3Dmol-min.js", "openai4s_compute_provider/__init__.py", "openai4s_worker_runtime/__init__.py", "envs/python.yml", "envs/r.yml", "skills/bioskills/COLLECTION.json", "skills/bioskills/LICENSE", "skills/bioskills/MANIFEST.json", "skills/bioskills/README.md", "skills/bioskills/README_zh.md", ) #: Only ever checked against the top of our own source tree: `tests/` and #: `.git/` are perfectly legitimate *inside* third-party site-packages. FORBIDDEN_SOURCES = (".git", ".venv", ".build", "tests", ".claude") ENV_TEMPLATES = frozenset({".env.example", ".env.sample", ".env.template"}) MIN_SKILLS = 20 #: Floor for the required pinned bioSkills collection. MIN_COLLECTION_SKILLS = 561 BIOSKILLS_COMMIT = "d91ed3d563019e649dc854c56ccd62551359488a" BIOSKILLS_MANIFEST_SHA256 = ( "e1747551da95e9320368d4d4f7002d3b9708a808d0b9b0f117e36ed66968530b" ) _COLLECTION_BOUNDARY_FILES = frozenset( {"COLLECTION.json", "MANIFEST.json", "README.md", "README_zh.md"} ) _RUNTIME_SENTINELS = { unicodedata.normalize("NFKC", name).casefold(): name for name in ("SKILL.md", "kernel.py", "COLLECTION.json", "MANIFEST.json") } #: Below this, the bundle was not precompiled — see :func:`check_bytecode`. MIN_PYC = 500 #: A scan that silently enumerated nothing reports the same "clean" as one that #: actually looked, so the sample size is part of the assertion. MIN_SCANNED_FILES = 200 _CREDENTIAL_ASSIGNMENT = re.compile(r"(?i)(api[_-]?key|secret|token)\s*=\s*[\"']?\S") class BundleCheckError(RuntimeError): """A shipped bundle does not satisfy the contract.""" def check_web_assets(src: Path) -> None: """Check the default page's local resources, including hashed Vite assets.""" web = src / "openai4s/server/webui" class Resources(HTMLParser): def handle_starttag(self, tag, attrs): field = "src" if tag == "script" else "href" if tag == "link" else None url = dict(attrs).get(field, "") if not url or not url.startswith("/static/"): return path = web / unquote(urlsplit(url).path).removeprefix("/static/") if not path.resolve().is_relative_to(web.resolve()) or not path.is_file(): raise BundleCheckError(f"default Web UI resource is missing: {url}") Resources().feed((web / "dist/index.html").read_text("utf-8")) def check_python_scripts(runtime: Path) -> None: """Reject Python console commands pointing at the build machine.""" from relocate_bundle_scripts import HEADER, python_body for path in (runtime / "bin").iterdir(): if path.is_symlink() or not path.is_file(): continue with path.open("rb") as source: if source.read(2) != b"#!": continue source.seek(0) content = source.read() if python_body(content) is not None and not content.startswith(HEADER): raise BundleCheckError(f"Python command is not relocatable: {path.name}") # -------------------------------------------------------------------------- # the pre-baked science stack # -------------------------------------------------------------------------- def manifest_packages(arch: str | None = None) -> list[tuple[str, str]]: """``(pip-name, import-name)`` for every package the builders pre-bake. Read from the same manifest the build scripts install from, so the package set a verifier enforces is exactly the one that was bundled. When ``arch`` is given, lines annotated ``skip_arch=`` are excluded — the builders drop them for targets that have no wheel, so the verifier must not demand them there. """ if not MANIFEST.is_file(): raise BundleCheckError(f"missing package manifest {MANIFEST}") packages: list[tuple[str, str]] = [] for raw in MANIFEST.read_text("utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue parts = line.split() if len(parts) < 2: raise BundleCheckError(f"manifest line missing import name: {raw!r}") if arch is not None and f"skip_arch={arch}" in parts[2:]: continue packages.append((parts[0], parts[1])) if not packages: raise BundleCheckError("package manifest lists no packages") return packages def bundled_imports(arch: str | None = None) -> list[str]: return [import_name for _, import_name in manifest_packages(arch)] # -------------------------------------------------------------------------- # checks shared by every platform's verifier # -------------------------------------------------------------------------- def declared_version(src: Path) -> str: """``openai4s.__version__`` as the *bundled* source tree declares it.""" tree = ast.parse((src / "openai4s" / "__init__.py").read_text("utf-8")) for node in tree.body: if isinstance(node, ast.Assign) and len(node.targets) == 1: target = node.targets[0] if isinstance(target, ast.Name) and target.id == "__version__": if isinstance(node.value, ast.Constant) and isinstance( node.value.value, str ): return node.value.value raise BundleCheckError("bundled openai4s.__version__ is not a literal string") def _check_bioskills_payload(root: Path) -> None: """Bind every desktop bundle byte to the same reviewed collection pin.""" if root.is_symlink(): raise BundleCheckError("bioSkills collection root must not be a symlink") for path in root.rglob("*"): if path.is_symlink(): raise BundleCheckError( "bioSkills payload must not contain symlinks: " f"{path.relative_to(root).as_posix()}" ) manifest_path = root / "MANIFEST.json" payload = manifest_path.read_bytes() if hashlib.sha256(payload).hexdigest() != BIOSKILLS_MANIFEST_SHA256: raise BundleCheckError( "bioSkills MANIFEST.json digest does not match the reviewed pin" ) try: manifest = json.loads(payload) except ValueError as error: raise BundleCheckError(f"invalid bioSkills manifest: {error}") from error if manifest.get("skill_count") != MIN_COLLECTION_SKILLS: raise BundleCheckError( f"bioSkills manifest must list {MIN_COLLECTION_SKILLS} recipes" ) upstream = manifest.get("upstream") or {} if upstream.get("commit") != BIOSKILLS_COMMIT: raise BundleCheckError( f"bioSkills manifest must pin upstream commit {BIOSKILLS_COMMIT}" ) rows = manifest.get("files") if not isinstance(rows, list): raise BundleCheckError("bioSkills manifest has no files list") recorded: dict[str, dict] = {} for row in rows: if not isinstance(row, dict): raise BundleCheckError("bioSkills manifest file row is not an object") relative = row.get("path") if not isinstance(relative, str) or not relative: raise BundleCheckError("bioSkills manifest file row has no path") path = PurePosixPath(relative) if ( path.is_absolute() or "\\" in relative or ".." in path.parts or path.as_posix() != relative ): raise BundleCheckError(f"unsafe bioSkills manifest path: {relative!r}") if relative in recorded: raise BundleCheckError(f"duplicate bioSkills manifest path: {relative}") recorded[relative] = row shipped = { path.relative_to(root).as_posix() for path in root.rglob("*") if path.is_file() and "__pycache__" not in path.parts } expected = set(recorded) | _COLLECTION_BOUNDARY_FILES missing = sorted(expected - shipped) extra = sorted(shipped - expected) if missing or extra: detail = [] if missing: detail.append(f"missing {len(missing)} file(s), e.g. {missing[0]}") if extra: detail.append(f"unmanifested {len(extra)} file(s), e.g. {extra[0]}") raise BundleCheckError("bioSkills inventory mismatch: " + "; ".join(detail)) for relative, row in recorded.items(): target = root.joinpath(*PurePosixPath(relative).parts) data = target.read_bytes() if len(data) != row.get("size"): raise BundleCheckError(f"bioSkills payload size mismatch: {relative}") if hashlib.sha256(data).hexdigest() != row.get("sha256"): raise BundleCheckError(f"bioSkills payload hash mismatch: {relative}") def check_sources(src: Path) -> int: """Every runtime-only resource is present, and the Skill catalog is real.""" src = Path(src) if src.is_symlink(): raise BundleCheckError("source tree root must not be a symlink") # ``Path.is_file`` and ``rglob`` follow symlinked ancestors. A copied app # whose ``src/skills`` points back to the build machine could therefore # satisfy every inventory/hash check while shipping no self-contained # catalog at all. Reject every component on each required path before any # read follows it. for relative in REQUIRED_SOURCES: current = src for part in PurePosixPath(relative).parts: current /= part if current.is_symlink(): shown = current.relative_to(src).as_posix() raise BundleCheckError( f"source tree must not contain symlinks: {shown}" ) skills_root = src / "skills" if skills_root.exists(): for path in skills_root.rglob("*"): if path.is_symlink(): raise BundleCheckError( "source tree must not contain symlinks: " f"{path.relative_to(src).as_posix()}" ) expected = _RUNTIME_SENTINELS.get( unicodedata.normalize("NFKC", path.name).casefold() ) if expected is not None and path.name != expected: raise BundleCheckError( "source tree contains mis-cased runtime sentinel " f"{path.relative_to(src).as_posix()!r}; expected {expected!r}" ) missing = [name for name in REQUIRED_SOURCES if not (src / name).is_file()] if missing: raise BundleCheckError("source tree is missing: " + ", ".join(missing)) check_web_assets(src) # Two floors, not one total. Folding the 561-recipe collection into the # same count made the curated floor unfalsifiable: a bundle that shipped # bioskills and dropped every curated Skill still cleared 20. curated = sorted(src.glob("skills/*/SKILL.md")) collection = sorted(src.glob("skills/bioskills/*/SKILL.md")) skills = curated + collection if len(curated) < MIN_SKILLS: raise BundleCheckError( f"bundle ships only {len(curated)} curated Skills; " f"expected at least {MIN_SKILLS}" ) if len(collection) < MIN_COLLECTION_SKILLS: raise BundleCheckError( f"bundle ships only {len(collection)} bioSkills recipes; " f"expected at least {MIN_COLLECTION_SKILLS}" ) _check_bioskills_payload(src / "skills" / "bioskills") if len(skills) < MIN_SKILLS: raise BundleCheckError( f"bundle ships only {len(skills)} Skills; expected at least {MIN_SKILLS}" ) return len(skills) def check_bytecode(roots: list[Path]) -> int: """Every .py in the image must ship never-revalidated hash-based bytecode. If it does not, the app compiles on first import and writes ``__pycache__`` into its own bundle — which on macOS invalidates the code signature the moment anyone uses the app, and on every platform silently recompiles the entire stdlib and science stack on *each* launch wherever the bundle is read-only (straight from the DMG, or a system-wide install directory a non-admin user cannot write to). Timestamp bytecode is no better: unpacking or copying the bundle rewrites the .py mtimes, so all of it reads stale. """ compiled: list[Path] = [] for root in roots: if root.exists(): compiled.extend(root.rglob("__pycache__/*.pyc")) if len(compiled) < MIN_PYC: raise BundleCheckError( f"bundle ships only {len(compiled)} .pyc files — it was not precompiled, " "so it will write bytecode into its own bundle on first run" ) for path in compiled[:40]: flags = int.from_bytes(path.read_bytes()[4:8], "little") # bit0 = hash-based, bit1 = check_source. We require hash-based with # revalidation OFF, i.e. exactly 0b01. if flags & 0b11 != 0b01: kind = "timestamp" if not flags & 0b01 else "checked-hash" raise BundleCheckError( f"{path.name} carries {kind} bytecode; the build must use " "--invalidation-mode unchecked-hash or the app will rewrite it in place" ) return len(compiled) def check_no_secrets(root: Path, src: Path, launchers: list[Path]) -> None: """No credential-shaped material anywhere in the shipped tree. ``root`` is the whole bundle (the dotenv walk covers third-party packages too), ``src`` is our own source tree, and ``launchers`` are the shell / batch entry points, which are the one file a build script writes by hand and could therefore bake a value into. """ # Imported lazily: `scripts/` must be on sys.path, which is the caller's # job, and a module-level import would make this file unimportable on its # own. from source_secret_scan import candidate_files as secret_scan_candidates from source_secret_scan import scan as secret_scan shipped = [name for name in FORBIDDEN_SOURCES if (src / name).exists()] if shipped: raise BundleCheckError("source tree ships: " + ", ".join(shipped)) # A dotenv anywhere in the image is the one way a maintainer's provider key # can actually reach a user, so this walk covers the whole bundle. dotenvs = [ path.relative_to(root).as_posix() for path in root.rglob(".env*") if path.is_file() and path.name.casefold() not in ENV_TEMPLATES ] if dotenvs: raise BundleCheckError("bundle ships dotenv files: " + ", ".join(dotenvs[:5])) scanned = len(secret_scan_candidates(src)) if scanned < MIN_SCANNED_FILES: raise BundleCheckError( f"the credential scan only enumerated {scanned} files in the source tree — " "it is not actually inspecting the bundle" ) findings = secret_scan(src) if findings: located = ", ".join( f"{finding.path}:{finding.line}:{finding.detector}" for finding in findings[:5] ) raise BundleCheckError( f"credential-shaped material inside the bundle ({len(findings)} finding(s)): {located}" ) for launcher in launchers: if not launcher.is_file(): continue text = launcher.read_text("utf-8", errors="replace") if _CREDENTIAL_ASSIGNMENT.search(text): raise BundleCheckError(f"{launcher.name} assigns a credential-shaped value") __all__ = [ "BundleCheckError", "ENV_TEMPLATES", "FORBIDDEN_SOURCES", "MANIFEST", "MIN_PYC", "MIN_SCANNED_FILES", "MIN_COLLECTION_SKILLS", "MIN_SKILLS", "REQUIRED_SOURCES", "bundled_imports", "check_bytecode", "check_no_secrets", "check_sources", "declared_version", "manifest_packages", ]