"""§4.13 containment regression tests. Every shell script that boots a target VM (build.sh, verify.sh, launch_target.sh) must hold the containment posture: * SLIRP `restrict=on` — no upstream egress, OR explicit operator -declared bridge with no internet route. (Both shellshock build and verify use restrict=on; build temporarily allows egress for package fetch but verify confirms-with-restrict.) * NO `-virtfs`, `-fsdev`, `-9pfs`, or any host-shared mount. * `snapshot=on` on the boot drive so verification doesn't mutate the artifact. * No `sudo` / setuid wrapper around `qemu-system-*` invocation. These are static checks against the shell scripts in the repo — they catch a regression at PR time, before any image gets built. A complementary runtime check would have to actually boot the VM, which is too heavy for CI; the spec.toml validator gives us the runtime declaration that the verifier must satisfy. """ from __future__ import annotations import re from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parent.parent TARGETS_DIR = REPO_ROOT / "vm" / "targets" def _strip_bash_comments(src: str) -> str: """Drop comment-only lines so the containment scan doesn't trip on inline documentation that mentions the forbidden flags.""" out = [] for line in src.splitlines(): stripped = line.lstrip() if stripped.startswith("#"): continue # Inline comments after code: trim everything after `#` UNLESS # the # is inside single or double quotes. Cheap heuristic: # only strip when there's a space before the #. idx = line.find(" #") if idx > 0: line = line[:idx] out.append(line) return "\n".join(out) def _every_target_with_verify(): if not TARGETS_DIR.exists(): return [] out = [] for child in TARGETS_DIR.iterdir(): if not child.is_dir(): continue verify = child / "verify.sh" if verify.exists(): out.append((child.name, verify)) return out def _every_target_with_build(): if not TARGETS_DIR.exists(): return [] out = [] for child in TARGETS_DIR.iterdir(): if not child.is_dir(): continue build = child / "build.sh" if build.exists(): out.append((child.name, build)) return out @pytest.mark.parametrize( "name,path", _every_target_with_verify() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_verify_uses_restrict_on(name, path: Path) -> None: """Every verify.sh MUST boot the produced image with no upstream egress. The verifier proves the image satisfies its spec under the SAME containment posture the orchestrator will use at episode time — a target that needs internet access during verification can't be trusted to behave under §4.13.""" src = path.read_text() qemu_inv = re.search(r"qemu-system-x86_64\b[^`]*?(?=\n[A-Z#]|\Z)", src, re.DOTALL) assert qemu_inv, f"{name}: no qemu-system-x86_64 invocation in verify.sh" qemu_text = qemu_inv.group(0) assert "restrict=on" in qemu_text, ( f"{name}: verify.sh qemu invocation must include " f"`restrict=on` on its netdev (§4.13). Got:\n{qemu_text}" ) @pytest.mark.parametrize( "name,path", _every_target_with_verify() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_verify_no_shared_filesystem(name, path: Path) -> None: """Targets MUST NOT have host-shared mounts during verification.""" src = _strip_bash_comments(path.read_text()) for forbidden in ("-virtfs", "-fsdev", "9pfs", "9p,trans"): assert forbidden not in src, ( f"{name}: verify.sh contains `{forbidden}` — host-shared " f"filesystem is a §4.13 containment regression." ) @pytest.mark.parametrize( "name,path", _every_target_with_verify() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_verify_uses_snapshot_on(name, path: Path) -> None: """`snapshot=on` so verification doesn't mutate the build artifact's bytes — the sha256 must be stable from build to publish to dataset-time use.""" src = path.read_text() # Search for the boot drive line; allow flexible spacing/quoting. drive_match = re.search(r'-drive[^\n]*file=[^\n]*\.qcow2[^\n]*', src) if drive_match: assert "snapshot=on" in drive_match.group(0), ( f"{name}: verify.sh boot drive must use snapshot=on (§4.13)" ) @pytest.mark.parametrize( "name,path", _every_target_with_verify() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_verify_no_sudo_qemu(name, path: Path) -> None: """QEMU MUST run as the unprivileged caller. No sudo / setuid wrappers (§4.13).""" src = path.read_text() assert "sudo qemu-system" not in src, ( f"{name}: verify.sh wraps qemu-system in sudo — privileged " f"QEMU is a §4.13 containment regression." ) @pytest.mark.parametrize( "name,path", _every_target_with_build() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_build_no_shared_filesystem(name, path: Path) -> None: """Even during build (when egress is permitted for package fetch), no host-shared filesystem mount.""" src = _strip_bash_comments(path.read_text()) for forbidden in ("-virtfs", "-fsdev", "9pfs", "9p,trans"): assert forbidden not in src, ( f"{name}: build.sh contains `{forbidden}` — host-shared " f"filesystem is a §4.13 containment regression." ) @pytest.mark.parametrize( "name,path", _every_target_with_build() or [pytest.param("none", None, marks=pytest.mark.skip(reason="no targets yet"))], ) def test_build_no_sudo_qemu(name, path: Path) -> None: src = path.read_text() assert "sudo qemu-system" not in src, ( f"{name}: build.sh wraps qemu-system in sudo (§4.13)" ) # --------------------------------------------------------------------- # Cross-cutting: every target with a build.sh must also have verify.sh # and vice versa. Half-built targets are a §1 violation. # --------------------------------------------------------------------- def test_every_target_has_complete_trio() -> None: """spec.toml + build.sh + verify.sh — all three or none. A target with a spec but no build script is a stub; a build with no verify bypasses §4.2 acceptance. PIPELINE.md §1 default-to-removal: if you can't ship the trio, ship nothing.""" if not TARGETS_DIR.exists(): return for child in TARGETS_DIR.iterdir(): if not child.is_dir(): continue files = {p.name for p in child.iterdir() if p.is_file()} # At least one of {spec.toml, build.sh, verify.sh} present means # all three required. relevant = {"spec.toml", "build.sh", "verify.sh"} & files if not relevant: continue missing = {"spec.toml", "build.sh", "verify.sh"} - files assert not missing, ( f"target {child.name}: incomplete trio, missing {missing}. " f"§1 default-to-removal: complete the trio or remove the dir." )