from __future__ import annotations import hashlib import importlib import io import json import contextlib import stat import struct import sys import tarfile import types import zipfile from datetime import datetime, timedelta, timezone from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] BROKER_DIR = ROOT / "ops" / "gitea-deploy-broker" @pytest.fixture def modules(monkeypatch): if "fcntl" not in sys.modules: shim = types.ModuleType("fcntl") shim.LOCK_EX, shim.LOCK_UN, shim.LOCK_NB = 2, 8, 4 shim.flock = lambda *_args: None monkeypatch.setitem(sys.modules, "fcntl", shim) monkeypatch.syspath_prepend(str(BROKER_DIR)) for name in ("deploy", "prepare_mcp_hub_candidate", "hub_candidate_import"): sys.modules.pop(name, None) controller = importlib.import_module("deploy") prepare = importlib.import_module("prepare_mcp_hub_candidate") importer = importlib.import_module("hub_candidate_import") monkeypatch.setattr(prepare, "_is_host_root", lambda: True) yield types.SimpleNamespace( controller=controller, prepare=prepare, importer=importer ) for name in ("hub_candidate_import", "prepare_mcp_hub_candidate", "deploy"): sys.modules.pop(name, None) def _zip_stream_member(name: str, payload: bytes, offset: int) -> tuple[bytes, bytes]: encoded_name = name.encode("ascii") crc = zipfile.crc32(payload) & 0xFFFFFFFF local = struct.pack( " bytearray: names = sorted(importer._expected_zip_members()) if extra_member: names.append("unexpected.txt") members = [(name, b"{}" if name != "images.tar" else b"tar-data") for name in names] output = bytearray() centrals: list[bytes] = [] for name, payload in members: local, central = _zip_stream_member(name, payload, len(output)) output.extend(local) centrals.append(central) central_offset = len(output) for central in centrals: output.extend(central) central_size = len(output) - central_offset output.extend( struct.pack( " None: path.write_bytes(data) path.chmod(0o600) def _synthetic_scan_reports( services, images, *, created_at: str, finding_service: str | None = None ) -> dict[str, bytes]: report_data: dict[str, bytes] = {} for service in services: image_id = images[service]["imageId"] vulnerabilities = ( [ { "Severity": "HIGH", "PkgName": "package", "VulnerabilityID": "CVE-2099-0001", "InstalledVersion": "1.0", } ] if service == finding_service else [] ) vulnerability = { "SchemaVersion": 2, "Metadata": { "ImageID": image_id, "ImageConfig": {"os": "linux", "architecture": "amd64"}, }, "CreatedAt": created_at, "Trivy": {"Version": "0.65.0"}, "Results": [ { "Packages": [{"Name": "package", "Version": "1.0"}], "Vulnerabilities": vulnerabilities, } ], } secret = { "ImageID": image_id, "CreatedAt": created_at, "TrivyVersion": "0.65.0", "FindingCount": 0, "SensitiveFindingDetailsPersisted": False, "Results": [{"FindingCount": 0}], } report_data[f"vulnerability-{service}.json"] = json.dumps( vulnerability ).encode() report_data[f"secret-{service}.json"] = json.dumps(secret).encode() report_data["trivy-db-metadata.json"] = json.dumps( {"UpdatedAt": created_at, "DownloadedAt": created_at} ).encode() return report_data def test_run_identity_requires_exact_protected_push_and_no_pull_request(modules): importer = modules.importer revision = "a" * 40 run = { "path": importer.RUN_PATH, "event": "push", "head_branch": "main", "head_sha": revision, "repository": {"full_name": "Jens/ITWorx_MCP_Hub"}, "head_repository": None, "pull_requests": [], } assert importer._run_matches(run, revision) for change in ( {"event": "workflow_dispatch"}, {"path": "other.yml@refs/heads/main"}, {"head_repository": {"full_name": "attacker/fork"}}, {"pull_requests": [{"number": 12}]}, {"head_sha": "b" * 40}, {"repository": {"full_name": "attacker/repository"}}, ): assert not importer._run_matches({**run, **change}, revision) def test_selected_run_and_jobs_accept_the_live_gitea_job_shape(modules): importer = modules.importer revision = "a" * 40 now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace( "+00:00", "Z" ) run = { "id": 21531, "path": importer.RUN_PATH, "event": "push", "head_branch": "main", "head_sha": revision, "repository": {"full_name": "Jens/ITWorx_MCP_Hub"}, "head_repository": None, "pull_requests": [], "created_at": now, "run_attempt": 1, "status": "completed", "conclusion": "success", } job_rows = [ { "id": 37145 + index, "name": name, "run_id": 21531, "status": "completed", "conclusion": "success", "runner_id": 22, "started_at": now, "completed_at": now, } for index, name in enumerate(("full", "release-images")) ] calls = [] def api(path, *, query): calls.append((path, query)) if path.endswith("/actions/runs"): return {"total_count": 1, "workflow_runs": [run]} if path.endswith("/actions/runs/21531/jobs"): return {"total_count": 2, "jobs": job_rows} raise AssertionError(f"unexpected fixed API path: {path}") selected, built_at = importer._selected_run(api, revision) assert selected == run assert built_at.endswith("Z") assert importer._jobs_ready(api, revision, selected) assert calls[0][1] == { "event": "push", "branch": "main", "head_sha": revision, "page": "1", "limit": "100", } assert calls[1][1] == {"page": "1", "limit": "100"} def test_required_candidate_jobs_must_both_succeed_and_bind_run(modules): importer = modules.importer run = { "id": 5, "status": "completed", "conclusion": "success", "run_attempt": 1, } rows = [ {"name": name, "run_id": 5, "status": "completed", "conclusion": "success"} for name in ("full", "release-images") ] def api(_path, *, query): assert query == {"page": "1", "limit": "100"} return {"total_count": len(rows), "jobs": rows} assert importer._jobs_ready(api, "a" * 40, run) rows[1] = {**rows[1], "conclusion": "failure"} with pytest.raises(importer.CandidateRejected): importer._jobs_ready(api, "a" * 40, run) def test_recheck_rejects_a_new_attempt_for_the_same_workflow_run(modules, monkeypatch): importer = modules.importer revision = "a" * 40 original = {"id": 21531, "run_attempt": 1} restarted = {"id": 21531, "run_attempt": 2} artifact = {"id": 730} monkeypatch.setattr(importer, "_remote_main_tree", lambda *_args: "b" * 40) monkeypatch.setattr(importer, "_fixed_workflow", lambda *_args: None) monkeypatch.setattr( importer, "_selected_run", lambda *_args: (restarted, "2026-10-03T10:00:00.000Z"), ) monkeypatch.setattr(importer, "_jobs_ready", lambda *_args: True) with pytest.raises(importer.CandidateRejected, match="run changed"): importer._recheck_protected_artifact( lambda *_args, **_kwargs: None, revision=revision, tree="b" * 40, initial_run=original, initial_built_at="2026-10-03T10:00:00.000Z", initial_artifact=artifact, ) def test_unfixable_vulnerability_finding_is_bound_to_fixed_image_reference(modules): importer = modules.importer controller = modules.controller created_at = datetime.now(timezone.utc).isoformat(timespec="seconds").replace( "+00:00", "Z" ) images = { service: { "imageId": f"sha256:{index:064x}", "imageRef": f"registry.example/{service}:fixed", } for index, service in enumerate(controller.MCP_HUB_SERVICES, start=1) } report_data = _synthetic_scan_reports( controller.MCP_HUB_SERVICES, images, created_at=created_at, finding_service=controller.MCP_HUB_SERVICES[0], ) _reports, findings = importer._validate_reports(images, report_data) assert len(findings) == 1 assert findings[0]["imageRef"] == images[controller.MCP_HUB_SERVICES[0]]["imageRef"] summary = importer._candidate_findings_summary(findings, images) assert summary["unresolvedCriticalOrHigh"][0]["affectedImages"] == [ findings[0]["imageRef"] ] @pytest.mark.parametrize("finding_present", [False, True]) @pytest.mark.parametrize("rerun_during_load", [False, True]) def test_fake_local_import_publishes_candidate_and_requires_review_for_findings( modules, monkeypatch, tmp_path, finding_present, rerun_during_load ): importer = modules.importer prepare = modules.prepare controller = modules.controller revision, tree, epoch = "a" * 40, "b" * 40, "1790985600" built_at = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace( "+00:00", "Z" ) build = { "id": revision[:12], "repository": importer.REPOSITORY, "revision": revision, "tree": tree, "treeDigest": hashlib.sha256(f"git-tree:{tree}".encode()).hexdigest(), "builtAt": built_at, } compose_sha = "c" * 64 model_services = {} images = {} for index, service in enumerate(controller.MCP_HUB_SERVICES, start=1): external = importer.EXTERNAL_IMAGES.get(service) image_ref = ( external[0] if external is not None else f"registry.example/itworx/{service}:{revision[:12]}" ) archive_ref = external[1] if external is not None else image_ref model_services[service] = {"image": image_ref} images[service] = { "imageRef": image_ref, "archiveRef": archive_ref, "imageId": f"sha256:{index:064x}", } model = {"services": model_services} created_at = built_at finding_service = controller.MCP_HUB_SERVICES[0] if finding_present else None report_data = _synthetic_scan_reports( controller.MCP_HUB_SERVICES, images, created_at=created_at, finding_service=finding_service, ) _reports, findings = importer._validate_reports(images, report_data) summary = importer._candidate_findings_summary(findings, images) digests = { "images": { service: { "imageId": images[service]["imageId"], "tag": images[service]["imageRef"], } for service in controller.MCP_HUB_SERVICES } } exception_rows = [ { "package": finding["package"], "cve": finding["cve"], "installedVersion": finding["installedVersion"], "severity": finding["severity"], "affectedImageDigests": { finding["imageRef"]: images_by_ref["imageId"] for images_by_ref in [ next( row for row in images.values() if row["imageRef"] == finding["imageRef"] ) ] }, } for finding in findings ] draft = {"schemaVersion": "1.0", "exceptions": exception_rows} state_root = tmp_path / "state" state_root.mkdir(mode=0o700) state_root.chmod(0o700) releases_root = tmp_path / "releases" source = releases_root / revision source.mkdir(parents=True) monkeypatch.setattr(controller, "STATE_ROOT", state_root) monkeypatch.setattr(controller, "MCP_HUB_RELEASES_ROOT", releases_root) monkeypatch.setattr(controller, "GLOBAL_DEPLOY_LOCK", state_root / "deploy.lock") monkeypatch.setattr(controller, "_mcp_hub_require_trusted_state_root", lambda: None) monkeypatch.setattr(controller, "_mcp_hub_require_no_posix_acl", lambda *_args: None) monkeypatch.setattr(controller, "_root_owned", lambda *_args: True) def create_private_directory(path, *, create=False, root=None): del root if create: path.mkdir(mode=0o700, parents=True, exist_ok=True) path.chmod(0o700) if path.is_symlink() or not path.is_dir(): raise controller.DeployError("test private directory missing") def candidate_directory(candidate_revision): directory = state_root / "mcp-hub-release-candidates" / candidate_revision for path in (directory.parent, directory): if path.is_symlink() or not path.is_dir(): raise controller.DeployError("test candidate directory missing") return directory monkeypatch.setattr(controller, "_mcp_hub_private_directory", create_private_directory) monkeypatch.setattr(controller, "_mcp_hub_candidate_directory", candidate_directory) project, repo = {"branch": "main"}, tmp_path / "repo" monkeypatch.setattr(prepare, "_fixed_target", lambda *_args: (project, repo)) monkeypatch.setattr(controller, "deployment_metadata", lambda *_args, **_kwargs: build) monkeypatch.setattr(controller, "mcp_hub_source_date_epoch", lambda *_args: epoch) monkeypatch.setattr(controller, "mcp_hub_stage_release_source", lambda *_args: source) monkeypatch.setattr(controller, "exact_source_matches_revision", lambda *_args: True) monkeypatch.setattr(controller, "_mcp_hub_verify_release_tree_metadata", lambda *_args: None) monkeypatch.setattr( prepare, "_render", lambda *_args, **_kwargs: (model, compose_sha), ) monkeypatch.setattr(importer, "_remote_main_tree", lambda *_args: tree) monkeypatch.setattr(importer, "_fixed_workflow", lambda *_args: None) run = {"id": 21531, "run_attempt": 1} run_state = {"attempt": 1} monkeypatch.setattr( importer, "_selected_run", lambda *_args: ({**run, "run_attempt": run_state["attempt"]}, built_at), ) monkeypatch.setattr(importer, "_jobs_ready", lambda *_args: True) artifact_bytes = b"bounded fake Gitea artifact" artifact = { "id": 730, "size_in_bytes": len(artifact_bytes), "name": importer.ARTIFACT_PREFIX + revision, "workflow_run": {"id": run["id"]}, } monkeypatch.setattr(importer, "_candidate_artifact", lambda *_args: artifact) monkeypatch.setattr(importer, "_docker_root", lambda: tmp_path) monkeypatch.setattr(importer, "_check_disk_headroom", lambda **_kwargs: None) monkeypatch.setattr(importer, "_parse_artifact_zip", lambda *_args, **_kwargs: ( {}, report_data, hashlib.sha256(artifact_bytes).hexdigest(), "d" * 64, "e" * 64, 123, )) monkeypatch.setattr( importer, "_validate_manifest", lambda *_args, **_kwargs: (images, {}), ) monkeypatch.setattr(importer, "_validate_docker_save", lambda *_args, **_kwargs: None) monkeypatch.setattr(importer, "_bounded_global_deploy_lock", contextlib.nullcontext) monkeypatch.setattr(importer, "_image_preflight", lambda *_args: None) def fake_fixed_docker(*args, **_kwargs): if rerun_during_load and args and args[0] == "load": run_state["attempt"] = 2 return object() monkeypatch.setattr(importer, "_fixed_docker", fake_fixed_docker) monkeypatch.setattr(importer, "_verify_loaded_images", lambda *_args, **_kwargs: {}) monkeypatch.setattr( controller, "_mcp_hub_check_candidate_exceptions", lambda *_args: None, ) monkeypatch.setattr( prepare, "_draft_pending_exceptions", lambda *_args: draft, ) def fake_plan(_repository, _revision, *, built_at, staging_directory, **_kwargs): Path(staging_directory).mkdir(mode=0o700) frozen = { "schema": prepare.BUILD_SCHEMA, "repository": importer.REPOSITORY, "revision": revision, "build": build, "sourceDateEpoch": epoch, "composeSha256": compose_sha, } controller._mcp_hub_write_private_new( Path(staging_directory) / "build.json", prepare._json_bytes(frozen) ) return {"status": "planned", "revision": revision, "composeSha256": compose_sha} monkeypatch.setattr(prepare, "plan_candidate", fake_plan) def fake_capture(_repository, _revision, *, staging_directory, **_kwargs): image_rows = { service: { "imageRef": images[service]["imageRef"], "imageId": images[service]["imageId"], "vulnerabilitySha256": hashlib.sha256( report_data[f"vulnerability-{service}.json"] ).hexdigest(), "secretSha256": hashlib.sha256( report_data[f"secret-{service}.json"] ).hexdigest(), } for service in controller.MCP_HUB_SERVICES } candidate = { "schema": prepare.CANDIDATE_SCHEMA, "repository": importer.REPOSITORY, "revision": revision, "build": build, "sourceDateEpoch": epoch, "composeSha256": compose_sha, "images": image_rows, "databaseMetadataSha256": hashlib.sha256( report_data["trivy-db-metadata.json"] ).hexdigest(), "summarySha256": hashlib.sha256(prepare._json_bytes(summary)).hexdigest(), "digestsSha256": hashlib.sha256(prepare._json_bytes(digests)).hexdigest(), } outputs = { "candidate.json": candidate, "scan-summary.json": summary, "image-digests.json": digests, "exceptions.json": draft, } for filename, value in outputs.items(): controller._mcp_hub_write_private_new( Path(staging_directory) / filename, prepare._json_bytes(value) ) candidate_sha = hashlib.sha256(prepare._json_bytes(candidate)).hexdigest() exceptions_sha = hashlib.sha256(prepare._json_bytes(draft)).hexdigest() return { "candidateSha256": candidate_sha, "exceptionsSha256": exceptions_sha, } monkeypatch.setattr(prepare, "capture_candidate", fake_capture) def download_artifact(_artifact_id, destination, _maximum): _private_file(destination, artifact_bytes) return None candidate_directory = state_root / "mcp-hub-release-candidates" / revision if rerun_during_load: with pytest.raises(importer.CandidateRejected, match="run changed"): importer.prepare_candidate( importer.REPOSITORY, revision, api_json=lambda *_args, **_kwargs: None, download_artifact=download_artifact, ) assert not candidate_directory.exists() return first = importer.prepare_candidate( importer.REPOSITORY, revision, api_json=lambda *_args, **_kwargs: None, download_artifact=download_artifact, ) assert candidate_directory.is_dir() assert stat.S_IMODE(candidate_directory.stat().st_mode) == 0o700 candidate_before_review = (candidate_directory / "candidate.json").read_bytes() receipt_before_review = (candidate_directory / "import-receipt.json").read_bytes() collision_stage = tmp_path / "second-candidate" collision_stage.mkdir(mode=0o700) with pytest.raises(importer.CandidateRejected, match="never overwrites"): importer._publish_candidate(collision_stage, candidate_directory) assert (candidate_directory / "candidate.json").read_bytes() == candidate_before_review if finding_present: assert first["status"] == "pending_review" reviewed = { **draft, "reviewer": "release-operator", "exceptions": [ {**row, "justification": "Reviewed unfixable finding"} for row in draft["exceptions"] ], } exceptions_path = candidate_directory / "exceptions.json" exceptions_path.unlink() controller._mcp_hub_write_private_new( exceptions_path, prepare._json_bytes(reviewed) ) approval = { "schema": importer.APPROVAL_SCHEMA, "candidateSha256": first["candidateSha256"], "exceptionsSha256": hashlib.sha256( prepare._json_bytes(reviewed) ).hexdigest(), } controller._mcp_hub_write_private_new( candidate_directory / "approval.json", prepare._json_bytes(approval) ) final = importer.prepare_candidate( importer.REPOSITORY, revision, api_json=lambda *_args, **_kwargs: None, download_artifact=download_artifact, ) assert final["status"] == "ready" else: assert first["status"] == "ready" final = first assert final["candidateSha256"] == hashlib.sha256(candidate_before_review).hexdigest() assert (candidate_directory / "candidate.json").read_bytes() == candidate_before_review assert (candidate_directory / "import-receipt.json").read_bytes() == receipt_before_review def test_candidate_artifact_is_exact_bounded_and_unexpired(modules): importer = modules.importer revision = "c" * 40 run_id = 11 artifact = { "id": 22, "name": importer.ARTIFACT_PREFIX + revision, "size_in_bytes": 4096, "expired": False, "expires_at": (datetime.now(timezone.utc) + timedelta(hours=1)) .isoformat() .replace("+00:00", "Z"), "workflow_run": {"id": run_id}, } result = importer._candidate_artifact( lambda _path, *, query: {"artifacts": [artifact]}, run_id, revision ) assert result == artifact for changed in ( {"expired": True}, {"name": "hub-release-candidate-" + "d" * 40}, {"size_in_bytes": importer.MAX_ARTIFACT_ZIP_BYTES + 1}, {"workflow_run": {"id": run_id + 1}}, ): with pytest.raises(importer.CandidateRejected): importer._candidate_artifact( lambda _path, *, query: {"artifacts": [{**artifact, **changed}]}, run_id, revision, ) def test_streamed_uploader_zip_descriptors_are_verified(modules, tmp_path): importer = modules.importer payload = _streamed_zip_bytes(importer) zip_path = tmp_path / "candidate.zip" tar_path = tmp_path / "images.tar" _private_file(zip_path, bytes(payload)) ( manifest, reports, zip_sha, manifest_sha, tar_sha, tar_bytes, ) = importer._parse_artifact_zip(zip_path, tar_path, expected_size=len(payload)) assert manifest == {} assert len(reports) == 35 assert zip_sha == hashlib.sha256(payload).hexdigest() assert manifest_sha == hashlib.sha256(b"{}").hexdigest() assert tar_sha == hashlib.sha256(b"tar-data").hexdigest() assert tar_bytes == len(b"tar-data") assert tar_path.read_bytes() == b"tar-data" tampered = bytearray(payload) name_size = len("candidate-manifest.json") descriptor_offset = 30 + name_size + 2 tampered[descriptor_offset + 4] ^= 0x01 _private_file(zip_path, bytes(tampered)) with pytest.raises(importer.CandidateRejected, match="descriptor"): importer._parse_artifact_zip(zip_path, tar_path, expected_size=len(tampered)) def test_zip_central_directory_is_bounded_before_zipfile_parse(modules, tmp_path, monkeypatch): importer = modules.importer payload = _streamed_zip_bytes(importer, extra_member=True) path = tmp_path / "too-many.zip" _private_file(path, bytes(payload)) monkeypatch.setattr( importer.zipfile, "ZipFile", lambda *_args, **_kwargs: pytest.fail("ZipFile parsed an oversized member inventory"), ) with pytest.raises(importer.CandidateRejected, match="central directory"): importer._zip_central_directory_preflight(path, len(payload)) def test_zip_oversized_central_directory_is_rejected_without_allocating_it( modules, tmp_path, monkeypatch ): importer = modules.importer payload = struct.pack( "