Files
Unraid-AppOps-Gateway-Public/backend/tests/test_mcp_hub_candidate_preparation.py
T

951 lines
37 KiB
Python

from __future__ import annotations
import hashlib
import importlib
import json
import os
import shutil
import subprocess
import sys
import types
from datetime import datetime, 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.flock = 2, 8, lambda *_args: None
monkeypatch.setitem(sys.modules, "fcntl", shim)
monkeypatch.syspath_prepend(str(BROKER_DIR))
sys.modules.pop("deploy", None)
sys.modules.pop("prepare_mcp_hub_candidate", None)
controller = importlib.import_module("deploy")
utility = importlib.import_module("prepare_mcp_hub_candidate")
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
yield controller, utility
sys.modules.pop("prepare_mcp_hub_candidate", None)
sys.modules.pop("deploy", None)
@pytest.fixture
def harness(modules, tmp_path, monkeypatch):
controller, utility = modules
revision = "a" * 40
epoch = "1790888863"
now = (
datetime.now(timezone.utc)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z")
)
metadata = {
"id": revision[:12],
"repository": controller.MCP_HUB_REPOSITORY,
"revision": revision,
"tree": "b" * 40,
"treeDigest": hashlib.sha256(("git-tree:" + "b" * 40).encode()).hexdigest(),
"builtAt": now,
}
repo = tmp_path / "hub.git"
project = {
"branch": "main",
"slug": controller.MCP_HUB_PROJECT,
"compose_project": controller.MCP_HUB_PROJECT,
"containers": list(controller.MCP_HUB_CONTAINERS),
"_mcp_hub_contract": controller.MCP_HUB_TOPOLOGY_CONTRACT,
"_repository_path": str(repo),
"repo_name": "itworx_mcp_hub",
}
state = types.SimpleNamespace(
source=tmp_path / "releases" / revision,
compose_marker="baseline",
clock=now,
ingestion_digest=None,
images={},
mutations=[],
inspect_calls=[],
)
state.source.mkdir(parents=True)
(state.source / "scripts").mkdir()
draft = state.source / "scripts" / "draft_cve_exceptions.py"
draft.write_text(
"import argparse, json\n"
"from datetime import date, timedelta\n"
"from pathlib import Path\n"
"p=argparse.ArgumentParser()\n"
"p.add_argument('--scan-summary', required=True)\n"
"p.add_argument('--image-digests', required=True)\n"
"p.add_argument('--evidence-reference', required=True)\n"
"p.add_argument('--output', required=True)\n"
"a=p.parse_args()\n"
"scan=json.loads(Path(a.scan_summary).read_text())\n"
"digest=json.loads(Path(a.image_digests).read_text())['images']\n"
"exceptions=[]\n"
"for row in scan['unresolvedCriticalOrHigh']:\n"
" refs=set(row['affectedImages'])\n"
" affected={s:v['imageId'] for s,v in digest.items() if v['tag'] in refs}\n"
" for cve in row['cves']:\n"
" exceptions.append({'id':row['package']+'-'+cve,'package':row['package'],'cve':cve,\n"
" 'installedVersion':row['installedVersion'],'severity':row['severity'],\n"
" 'approvalStatus':'pending','riskOwner':'','approver':'','affectedImageDigests':affected,\n"
" 'evidenceReference':a.evidence_reference})\n"
"Path(a.output).write_text(json.dumps({'schemaVersion':'1.0','exceptions':exceptions}))\n"
"",
encoding="utf-8",
)
raw_project = dict(project)
monkeypatch.setattr(controller, "STATE_ROOT", tmp_path)
monkeypatch.setattr(controller, "MCP_HUB_RELEASES_ROOT", tmp_path / "releases")
monkeypatch.setattr(controller, "_mcp_hub_require_trusted_state_root", lambda: None)
monkeypatch.setattr(controller, "_mcp_hub_require_no_posix_acl", lambda _path: None)
monkeypatch.setattr(controller, "_root_owned", lambda _path: True)
monkeypatch.setattr(
controller,
"load_projects",
lambda: {controller.MCP_HUB_REPOSITORY: raw_project},
)
monkeypatch.setattr(
controller, "effective_mcp_hub_project", lambda repository, value: dict(value)
)
monkeypatch.setattr(
controller, "repository_path", lambda _repository, _project: repo
)
monkeypatch.setattr(controller, "output", lambda _args: revision)
monkeypatch.setattr(
controller,
"validate_commit",
lambda _repo, branch, commit: commit if branch == "main" else "",
)
def deployment_metadata(_repo, _revision, _repository, *, built_at=None):
result = dict(metadata)
result["builtAt"] = built_at or state.clock
datetime.fromisoformat(result["builtAt"].replace("Z", "+00:00"))
return result
monkeypatch.setattr(controller, "deployment_metadata", deployment_metadata)
monkeypatch.setattr(controller, "mcp_hub_source_date_epoch", lambda *_args: epoch)
def stage(_project, _repo, commit):
assert commit == revision
return state.source
monkeypatch.setattr(controller, "mcp_hub_stage_release_source", stage)
monkeypatch.setattr(
controller, "exact_source_matches_revision", lambda *_args: True
)
monkeypatch.setattr(
controller, "_mcp_hub_verify_release_tree_metadata", lambda *_args: None
)
monkeypatch.setattr(controller, "mcp_hub_validate_secret_files", lambda _model: 21)
def rendered(project_arg, commit, repository):
assert commit == revision
assert repository == controller.MCP_HUB_REPOSITORY
services = {}
for index, service in enumerate(controller.MCP_HUB_SERVICES, 1):
if service in controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES:
local = controller.MCP_HUB_LOCAL_BUILD_IMAGE_REPOSITORIES.get(service)
if service == "connector-ingestion":
reference = (
f"{controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES[service]}@sha256:{state.ingestion_digest}"
if state.ingestion_digest
else ""
)
else:
reference = (
f"{local}:{revision[:12]}"
if local
else f"{controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES[service]}@sha256:{index:064x}"
)
elif service == "contextforge":
reference = f"itworx-mcp-hub/contextforge-schema:{revision}"
elif service == "ingestion-clamd":
reference = controller.MCP_HUB_INTAKE_CLAMAV_IMAGE
elif service == "postgres":
reference = controller.MCP_HUB_POSTGRES_IMAGE
elif service == "redis":
reference = controller.MCP_HUB_REDIS_IMAGE
else:
reference = f"fixture/{service}@sha256:{index:064x}"
services[service] = {"image": reference}
return {"services": services, "testMarker": state.compose_marker}
monkeypatch.setattr(controller, "mcp_hub_compose_model", rendered)
def inspect(reference):
state.inspect_calls.append(reference)
return state.images.get(reference)
monkeypatch.setattr(controller, "inspect_image", inspect)
monkeypatch.setattr(
controller, "run", lambda *_args, **_kwargs: state.mutations.append("run")
)
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
utility.freeze_candidate(controller.MCP_HUB_REPOSITORY, revision)
freeze_dir = tmp_path / "mcp-hub-source-freezes" / revision
freeze_doc = json.loads((freeze_dir / "freeze.json").read_text())
# Publication happens after freeze and advances the wall clock. The plan
# uses the immutable timestamp and the now-available registry digest.
state.clock = "2026-10-04T02:03:04.567Z"
state.ingestion_digest = "f" * 64
plan = utility.plan_candidate(controller.MCP_HUB_REPOSITORY, revision)
candidate_dir = tmp_path / "mcp-hub-release-candidates" / revision
plan_doc = json.loads((candidate_dir / "build.json").read_text())
model = rendered(project, revision, controller.MCP_HUB_REPOSITORY)
labels = {
"org.opencontainers.image.source": controller.MCP_HUB_OCI_IMAGE_SOURCE,
"org.opencontainers.image.revision": revision,
"org.opencontainers.image.source-tree": metadata["tree"],
"org.opencontainers.image.version": metadata["id"],
"org.opencontainers.image.created": metadata["builtAt"],
"org.opencontainers.image.source-date-epoch": epoch,
}
for index, service in enumerate(controller.MCP_HUB_SERVICES, 1):
reference = model["services"][service]["image"]
image_id = "sha256:" + f"{index:064x}"
config = {"User": "10001:10001"}
if (
service in controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES
or service == "contextforge"
):
config["Labels"] = dict(labels)
state.images[reference] = {"Id": image_id, "Config": config}
def write_input(name: str, value: object) -> Path:
path = candidate_dir / name
data = (
json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n"
).encode()
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(descriptor, "wb") as handle:
handle.write(data)
path.chmod(0o600)
return path
for index, service in enumerate(controller.MCP_HUB_SERVICES, 1):
image_id = "sha256:" + f"{index:064x}"
vuln = {
"SchemaVersion": 2,
"Trivy": {"Version": "0.73.0"},
"CreatedAt": now,
"Metadata": {
"ImageID": image_id,
"ImageConfig": {"architecture": "amd64", "os": "linux"},
},
"Results": [
{
"Class": "os-pkgs",
"Packages": [{"Name": "fixture", "Version": "1"}],
"Vulnerabilities": [],
}
],
}
secret = {
"ImageID": image_id,
"CreatedAt": now,
"TrivyVersion": "0.73.0",
"FindingCount": 0,
"SensitiveFindingDetailsPersisted": False,
"Results": [{"FindingCount": 0}],
}
write_input(f"vulnerability-{service}.json", vuln)
write_input(f"secret-{service}.json", secret)
write_input("trivy-db-metadata.json", {"UpdatedAt": now, "DownloadedAt": now})
# The reports must match the IDs in the final inspect inventory. Keep a
# convenient writer for intentional drift cases in each test.
monkeypatch.setattr(utility, "_test_write_input", write_input, raising=False)
yield types.SimpleNamespace(
controller=controller,
utility=utility,
revision=revision,
epoch=epoch,
metadata=metadata,
project=project,
state=state,
candidate_dir=candidate_dir,
model=model,
write_input=write_input,
now=now,
plan=plan,
plan_doc=plan_doc,
freeze_dir=freeze_dir,
freeze_doc=freeze_doc,
)
def test_plan_freezes_protected_metadata_without_candidate_or_approval(harness):
h = harness
document = json.loads((h.candidate_dir / "build.json").read_text())
assert document["schema"] == "itworx.mcp-hub-build-plan/v1"
assert document["repository"] == h.controller.MCP_HUB_REPOSITORY
assert document["revision"] == h.revision
assert document["build"] == h.metadata
assert document["build"]["builtAt"] == h.freeze_doc["build"]["builtAt"]
assert document["build"]["builtAt"] != h.state.clock
assert document["sourceDateEpoch"] == h.epoch
assert len(document["composeSha256"]) == 64
assert h.state.ingestion_digest in str(h.model)
assert not (h.candidate_dir / "candidate.json").exists()
assert not (h.candidate_dir / "approval.json").exists()
assert h.state.mutations == []
def _remove_candidate_and_freeze(h):
shutil.rmtree(h.candidate_dir, ignore_errors=True)
shutil.rmtree(h.freeze_dir.parent, ignore_errors=True)
def _prepare_freeze_dir(h):
h.freeze_dir.parent.mkdir(mode=0o700)
h.freeze_dir.mkdir(mode=0o700)
def test_plan_requires_existing_freeze_before_any_candidate_write(harness):
h = harness
_remove_candidate_and_freeze(h)
with pytest.raises(
h.controller.DeployError,
match="run freeze, publish the fixed ingestion image, then run plan",
):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_freeze_is_non_replayable(harness):
h = harness
original = (h.freeze_dir / "freeze.json").read_bytes()
with pytest.raises(h.controller.DeployError, match="never overwritten|already exists"):
h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert (h.freeze_dir / "freeze.json").read_bytes() == original
@pytest.mark.parametrize(
("mutation", "message"),
[
("revision", "freeze record.*source identity"),
("tree", "freeze record.*source identity"),
("calendar", "freeze record.*source identity"),
("epoch", "freeze record.*source epoch"),
("duplicate", "unavailable or invalid|duplicate"),
("partial", "unavailable or invalid|partial"),
],
)
def test_plan_rejects_invalid_freeze_records_without_candidate_writes(
harness, mutation, message
):
h = harness
_remove_candidate_and_freeze(h)
_prepare_freeze_dir(h)
record = dict(h.freeze_doc)
if mutation == "revision":
record["revision"] = "c" * 40
payload = json.dumps(record).encode()
elif mutation == "tree":
record["build"] = dict(record["build"])
record["build"]["tree"] = "d" * 40
payload = json.dumps(record).encode()
elif mutation == "calendar":
record["build"] = dict(record["build"])
record["build"]["builtAt"] = "2026-99-99T00:00:00.000Z"
payload = json.dumps(record).encode()
elif mutation == "epoch":
record["sourceDateEpoch"] = "1790888864"
payload = json.dumps(record).encode()
elif mutation == "duplicate":
payload = b'{"schema":"duplicate","schema":"duplicate"}'
else:
payload = b'{"schema":'
(h.freeze_dir / "freeze.json").write_bytes(payload)
(h.freeze_dir / "freeze.json").chmod(0o600)
with pytest.raises(h.controller.DeployError, match=message):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_plan_rejects_symlink_freeze_file_without_candidate_writes(harness, tmp_path):
h = harness
_remove_candidate_and_freeze(h)
_prepare_freeze_dir(h)
outside = tmp_path / "outside.json"
outside.write_text(json.dumps(h.freeze_doc))
try:
(h.freeze_dir / "freeze.json").symlink_to(outside)
except OSError:
pytest.skip("symlink creation is unavailable on this filesystem")
with pytest.raises(
h.controller.DeployError,
match="unavailable or invalid|unsafe|not a regular file",
):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_plan_rejects_fifo_freeze_record_without_blocking(harness):
if os.name == "nt" or not hasattr(os, "mkfifo"):
pytest.skip("FIFO files are a POSIX filesystem contract")
h = harness
_remove_candidate_and_freeze(h)
_prepare_freeze_dir(h)
os.mkfifo(h.freeze_dir / "freeze.json", 0o600)
with pytest.raises(h.controller.DeployError, match="not a regular file"):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_plan_rejects_unsafe_freeze_directory_mode_without_candidate_writes(harness):
h = harness
if os.name == "nt":
pytest.skip("POSIX private-mode enforcement is a Linux controller contract")
_remove_candidate_and_freeze(h)
_prepare_freeze_dir(h)
(h.freeze_dir / "freeze.json").write_text(json.dumps(h.freeze_doc))
(h.freeze_dir / "freeze.json").chmod(0o600)
h.freeze_dir.chmod(0o755)
with pytest.raises(h.controller.DeployError, match="private|root-owned|ACL"):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
@pytest.mark.parametrize(
"fault", ["file_mode", "owner", "dir_owner", "acl", "dir_acl"]
)
def test_plan_rejects_unsafe_freeze_file_metadata_without_candidate_writes(
harness, monkeypatch, fault
):
h = harness
if fault == "file_mode" and os.name == "nt":
pytest.skip("POSIX private-mode enforcement is a Linux controller contract")
_remove_candidate_and_freeze(h)
_prepare_freeze_dir(h)
freeze_file = h.freeze_dir / "freeze.json"
freeze_file.write_text(json.dumps(h.freeze_doc))
freeze_file.chmod(0o600)
if fault == "file_mode":
freeze_file.chmod(0o644)
match = "unsafe"
elif fault in {"owner", "dir_owner"}:
original = h.controller._root_owned
denied_path = h.freeze_dir if fault == "dir_owner" else freeze_file
monkeypatch.setattr(
h.controller,
"_root_owned",
lambda path: False if Path(path) == denied_path else original(path),
)
match = "root-owned|unsafe|owner"
else:
original = h.controller._mcp_hub_require_no_posix_acl
def reject_freeze_acl(path):
if Path(path) == (h.freeze_dir if fault == "dir_acl" else freeze_file):
raise h.controller.DeployError("freeze ACL denied")
original(path)
monkeypatch.setattr(h.controller, "_mcp_hub_require_no_posix_acl", reject_freeze_acl)
match = "ACL"
with pytest.raises(h.controller.DeployError, match=match):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_freeze_rejects_nonexact_local_main_head_without_writes(harness, monkeypatch):
h = harness
_remove_candidate_and_freeze(h)
monkeypatch.setattr(h.controller, "output", lambda _args: "c" * 40)
with pytest.raises(h.controller.DeployError, match="exact protected main head"):
h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.freeze_dir.parent.exists()
def test_freeze_rejects_staged_source_mismatch_without_freeze_record(harness, monkeypatch):
h = harness
_remove_candidate_and_freeze(h)
monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: False)
with pytest.raises(h.controller.DeployError, match="Staged MCP Hub source differs"):
h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.freeze_dir.parent.exists()
def test_freeze_rejects_invalid_epoch_before_staging_or_freeze_record(harness, monkeypatch):
h = harness
_remove_candidate_and_freeze(h)
monkeypatch.setattr(h.controller, "mcp_hub_source_date_epoch", lambda *_args: "invalid")
monkeypatch.setattr(
h.controller,
"mcp_hub_stage_release_source",
lambda *_args: pytest.fail("invalid epoch must fail before source staging"),
)
with pytest.raises(h.controller.DeployError, match="source epoch"):
h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.freeze_dir.parent.exists()
def test_plan_rejects_staged_source_drift_before_candidate_writes(harness, monkeypatch):
h = harness
shutil.rmtree(h.candidate_dir)
monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: False)
with pytest.raises(h.controller.DeployError, match="Staged MCP Hub source differs"):
h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not h.candidate_dir.exists()
def test_freeze_wrong_repository_and_nonroot_fail_before_writes(modules, monkeypatch, tmp_path):
controller, utility = modules
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
monkeypatch.setattr(controller, "STATE_ROOT", tmp_path)
with pytest.raises(controller.DeployError, match="fixed MCP Hub repository"):
utility.freeze_candidate("someone/else", "a" * 40)
assert list(tmp_path.iterdir()) == []
monkeypatch.setattr(utility, "_is_host_root", lambda: False)
with pytest.raises(controller.DeployError, match="host root"):
utility.freeze_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40)
assert list(tmp_path.iterdir()) == []
def test_plan_candidate_freezes_the_selected_workflow_creation_timestamp(
harness, monkeypatch
):
h = harness
revision = h.revision
built_at = "2026-10-03T12:34:56.789Z"
observed = []
metadata = {
"id": revision[:12],
"repository": h.controller.MCP_HUB_REPOSITORY,
"revision": revision,
"tree": "d" * 40,
"treeDigest": hashlib.sha256(("git-tree:" + "d" * 40).encode()).hexdigest(),
"builtAt": built_at,
}
monkeypatch.setattr(h.controller, "output", lambda _args: revision)
monkeypatch.setattr(h.controller, "validate_commit", lambda _repo, _branch, commit: commit)
def deployment_metadata(_repo, supplied_revision, repository, *, built_at=None):
observed.append(built_at)
assert supplied_revision == revision
assert repository == h.controller.MCP_HUB_REPOSITORY
return dict(metadata)
monkeypatch.setattr(h.controller, "deployment_metadata", deployment_metadata)
monkeypatch.setattr(h.controller, "mcp_hub_stage_release_source", lambda *_args: h.state.source)
monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: True)
compose_calls = []
def fixed_compose_modeler(project, revision, repository):
compose_calls.append((project, revision, repository))
return {
"services": {
service: {"image": f"fixture/{service}:candidate"}
for service in h.controller.MCP_HUB_SERVICES
}
}
monkeypatch.setattr(
h.controller,
"mcp_hub_compose_model",
lambda *_args: pytest.fail("plan used the ambient Compose renderer"),
)
staging = (
h.controller.STATE_ROOT
/ "mcp-hub-release-imports"
/ revision
/ ("1" * 32)
/ revision
)
h.utility.plan_candidate(
h.controller.MCP_HUB_REPOSITORY,
revision,
built_at=built_at,
staging_directory=staging,
compose_modeler=fixed_compose_modeler,
)
plan = json.loads(
(staging / "build.json").read_text()
)
assert observed == [built_at, built_at]
assert plan["build"]["builtAt"] == built_at
assert len(compose_calls) == 1
def test_candidate_preparation_rejects_staging_path_outside_private_import_anchor(
harness,
):
h = harness
revision = h.revision
staging = h.controller.STATE_ROOT / "outside-imports" / revision
with pytest.raises(h.controller.DeployError, match="private anchor|staging path"):
h.utility.plan_candidate(
h.controller.MCP_HUB_REPOSITORY,
revision,
built_at="2026-10-03T12:34:56.789Z",
staging_directory=staging,
)
assert not staging.exists()
def test_candidate_preparation_rejects_symlinked_staging_destination(harness):
h = harness
revision = h.revision
anchor = h.controller.STATE_ROOT / "mcp-hub-release-imports" / revision
token = anchor / ("2" * 32)
token.mkdir(parents=True, mode=0o700)
target = h.controller.STATE_ROOT / "untrusted-target"
target.mkdir(mode=0o700)
staging = token / revision
try:
staging.symlink_to(target, target_is_directory=True)
except OSError:
pytest.skip("symlink creation is unavailable on this filesystem")
with pytest.raises(h.controller.DeployError):
h.utility.plan_candidate(
h.controller.MCP_HUB_REPOSITORY,
revision,
built_at="2026-10-03T12:34:56.789Z",
staging_directory=staging,
)
def test_candidate_preparation_rejects_nonprivate_staging_parent(harness):
if os.name == "nt":
pytest.skip("POSIX private-mode enforcement is a Linux controller contract")
h = harness
revision = h.revision
anchor = h.controller.STATE_ROOT / "mcp-hub-release-imports" / revision
token = anchor / ("3" * 32)
token.mkdir(parents=True, mode=0o700)
token.chmod(0o755)
staging = token / revision
with pytest.raises(h.controller.DeployError, match="private|safe|canonical"):
h.utility.plan_candidate(
h.controller.MCP_HUB_REPOSITORY,
revision,
built_at="2026-10-03T12:34:56.789Z",
staging_directory=staging,
)
def test_wrong_repository_and_nonroot_fail_before_controller_access(
modules, monkeypatch
):
controller, utility = modules
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
monkeypatch.setattr(
controller, "load_projects", lambda: pytest.fail("must reject repo first")
)
with pytest.raises(controller.DeployError, match="fixed MCP Hub repository"):
utility.plan_candidate("someone/else", "a" * 40)
monkeypatch.setattr(utility, "_is_host_root", lambda: False)
with pytest.raises(controller.DeployError, match="root"):
utility.plan_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40)
def test_non_exact_main_head_is_denied_before_staging(modules, monkeypatch, tmp_path):
controller, utility = modules
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
project = {
"branch": "main",
"_mcp_hub_contract": controller.MCP_HUB_TOPOLOGY_CONTRACT,
}
monkeypatch.setattr(
controller, "load_projects", lambda: {controller.MCP_HUB_REPOSITORY: project}
)
monkeypatch.setattr(
controller, "effective_mcp_hub_project", lambda _repo, value: value
)
monkeypatch.setattr(
controller, "repository_path", lambda *_args: tmp_path / "hub.git"
)
monkeypatch.setattr(controller, "output", lambda _args: "c" * 40)
monkeypatch.setattr(
controller,
"mcp_hub_stage_release_source",
lambda *_args: pytest.fail("must not stage"),
)
with pytest.raises(controller.DeployError, match="exact protected main head"):
utility.plan_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40)
def test_capture_emits_candidate_and_pending_only_envelope_without_apply_calls(
harness, monkeypatch
):
h = harness
calls = []
original_run = h.utility.subprocess.run
def observe_draft(command, **kwargs):
calls.append((command, kwargs))
return original_run(command, **kwargs)
monkeypatch.setattr(h.utility.subprocess, "run", observe_draft)
result = h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
candidate = json.loads((h.candidate_dir / "candidate.json").read_text())
exceptions = json.loads((h.candidate_dir / "exceptions.json").read_text())
digests = json.loads((h.candidate_dir / "image-digests.json").read_text())
assert result["status"] == "pending_review"
assert set(candidate["images"]) == set(h.controller.MCP_HUB_SERVICES)
assert candidate["images"]["connector-ingestion"]["imageRef"].endswith(
"@sha256:" + h.state.ingestion_digest
)
assert candidate["build"]["builtAt"] == h.freeze_doc["build"]["builtAt"]
assert digests["images"]["web"]["tag"] == candidate["images"]["web"]["imageRef"]
assert digests["images"]["web"]["imageId"] == candidate["images"]["web"]["imageId"]
candidate_bytes = (h.candidate_dir / "candidate.json").read_bytes()
exceptions_bytes = (h.candidate_dir / "exceptions.json").read_bytes()
assert result["candidateSha256"] == hashlib.sha256(candidate_bytes).hexdigest()
assert result["exceptionsSha256"] == hashlib.sha256(exceptions_bytes).hexdigest()
assert not (h.candidate_dir / "approval.json").exists()
assert all(item["approvalStatus"] == "pending" for item in exceptions["exceptions"])
assert h.state.mutations == []
assert len(h.state.inspect_calls) == 17
assert len(calls) == 1
assert (
Path(calls[0][0][1]) == h.state.source / "scripts" / "draft_cve_exceptions.py"
)
assert calls[0][1]["stdout"] is subprocess.DEVNULL
assert calls[0][1]["stderr"] is subprocess.DEVNULL
assert set(calls[0][1]["env"]) == {
"PATH",
"PYTHONDONTWRITEBYTECODE",
"PYTHONIOENCODING",
}
def test_capture_can_use_the_importers_fixed_image_inspector(harness, monkeypatch):
h = harness
inspected = []
def fixed_inspector(reference):
inspected.append(reference)
return h.state.images.get(reference)
monkeypatch.setattr(
h.controller,
"inspect_image",
lambda _reference: pytest.fail("capture used the ambient Docker context"),
)
monkeypatch.setattr(
h.controller,
"mcp_hub_compose_model",
lambda *_args: pytest.fail("capture used the ambient Compose renderer"),
)
result = h.utility.capture_candidate(
h.controller.MCP_HUB_REPOSITORY,
h.revision,
image_inspector=fixed_inspector,
compose_modeler=lambda *_args: h.model,
)
assert result["status"] == "pending_review"
assert len(inspected) == 17
assert set(inspected) == {
row["image"] for row in h.model["services"].values()
}
@pytest.mark.parametrize("mutation", ["label", "root"])
def test_capture_rejects_contextforge_provenance_drift_without_candidate(
harness, mutation
):
h = harness
reference = h.model["services"]["contextforge"]["image"]
config = h.state.images[reference]["Config"]
if mutation == "label":
config["Labels"]["org.opencontainers.image.source-tree"] = "wrong"
else:
config["User"] = "0:10001"
with pytest.raises(h.controller.DeployError, match="provenance|non-root runtime"):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
assert not (h.candidate_dir / "approval.json").exists()
assert h.state.mutations == []
def test_pending_high_findings_are_drafted_from_the_exact_staged_source(harness):
h = harness
report_path = h.candidate_dir / "vulnerability-web.json"
report = json.loads(report_path.read_text())
report["Results"][0]["Vulnerabilities"] = [
{
"PkgName": "libfixture",
"VulnerabilityID": "CVE-2026-12345",
"InstalledVersion": "1.2.3",
"Severity": "HIGH",
}
]
report_path.unlink()
h.write_input(report_path.name, report)
result = h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
candidate = json.loads((h.candidate_dir / "candidate.json").read_text())
summary = json.loads((h.candidate_dir / "scan-summary.json").read_text())
exceptions = json.loads((h.candidate_dir / "exceptions.json").read_text())[
"exceptions"
]
row = summary["unresolvedCriticalOrHigh"][0]
assert row["affectedImages"] == [candidate["images"]["web"]["imageRef"]]
assert exceptions[0]["approvalStatus"] == "pending"
assert exceptions[0]["package"] == "libfixture"
assert exceptions[0]["cve"] == "CVE-2026-12345"
assert exceptions[0]["affectedImageDigests"] == {
"web": candidate["images"]["web"]["imageId"]
}
assert result["status"] == "pending_review"
assert not h.state.mutations
def test_findings_for_same_cve_at_distinct_versions_remain_distinct(harness):
h = harness
for service, version in (("web", "1.2.3"), ("worker", "4.5.6")):
path = h.candidate_dir / f"vulnerability-{service}.json"
report = json.loads(path.read_text())
report["Results"][0]["Vulnerabilities"] = [
{
"PkgName": "libfixture",
"VulnerabilityID": "CVE-2026-12345",
"InstalledVersion": version,
"Severity": "HIGH",
}
]
path.unlink()
h.write_input(path.name, report)
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
summary = json.loads((h.candidate_dir / "scan-summary.json").read_text())
assert {row["installedVersion"] for row in summary["unresolvedCriticalOrHigh"]} == {
"1.2.3",
"4.5.6",
}
def test_same_package_cve_version_with_conflicting_severity_fails_closed(harness):
h = harness
for service, severity in (("web", "HIGH"), ("worker", "CRITICAL")):
path = h.candidate_dir / f"vulnerability-{service}.json"
report = json.loads(path.read_text())
report["Results"][0]["Vulnerabilities"] = [
{
"PkgName": "libfixture",
"VulnerabilityID": "CVE-2026-12345",
"InstalledVersion": "1.2.3",
"Severity": severity,
}
]
path.unlink()
h.write_input(path.name, report)
with pytest.raises(h.controller.DeployError, match="conflicting scan severities"):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
def test_draft_for_all_images_is_rejected_when_finding_scope_is_narrower(harness):
h = harness
path = h.candidate_dir / "vulnerability-web.json"
report = json.loads(path.read_text())
report["Results"][0]["Vulnerabilities"] = [
{
"PkgName": "libfixture",
"VulnerabilityID": "CVE-2026-12345",
"InstalledVersion": "1.2.3",
"Severity": "HIGH",
}
]
path.unlink()
h.write_input(path.name, report)
draft_script = h.state.source / "scripts" / "draft_cve_exceptions.py"
draft_script.write_text(
draft_script.read_text().replace("if v['tag'] in refs", "if True"),
encoding="utf-8",
)
with pytest.raises(h.controller.DeployError, match="pending-only document"):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
def test_missing_scan_report_fails_without_publishing_candidate(harness):
h = harness
(h.candidate_dir / "secret-web.json").unlink()
with pytest.raises(h.controller.DeployError):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
assert not (h.candidate_dir / "approval.json").exists()
assert h.state.mutations == []
def test_frozen_compose_drift_and_image_identity_drift_fail_closed(harness):
h = harness
h.state.compose_marker = "changed"
with pytest.raises(h.controller.DeployError, match="Compose"):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
h.state.compose_marker = "baseline"
web_ref = h.model["services"]["web"]["image"]
h.state.images[web_ref]["Id"] = "sha256:" + "f" * 64
with pytest.raises(h.controller.DeployError):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
def test_fixable_critical_or_high_finding_is_not_captured(harness):
h = harness
path = h.candidate_dir / "vulnerability-web.json"
report = json.loads(path.read_text())
report["Results"][0]["Vulnerabilities"] = [
{
"PkgName": "libfixture",
"VulnerabilityID": "CVE-2026-12345",
"InstalledVersion": "1.2.3",
"FixedVersion": "1.2.4",
"Severity": "CRITICAL",
}
]
path.unlink()
h.write_input(path.name, report)
with pytest.raises(h.controller.DeployError, match="fixable"):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert not (h.candidate_dir / "candidate.json").exists()
def test_capture_never_overwrites_existing_records(harness):
h = harness
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
original = (h.candidate_dir / "candidate.json").read_bytes()
with pytest.raises(h.controller.DeployError):
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
assert (h.candidate_dir / "candidate.json").read_bytes() == original
assert h.state.mutations == []
def test_capture_preserves_full_supported_trivy_database_metadata(harness):
h = harness
path = h.candidate_dir / "trivy-db-metadata.json"
path.unlink()
metadata = {
"UpdatedAt": h.now,
"DownloadedAt": h.now,
"Version": 2,
"NextUpdate": h.now,
}
h.write_input(path.name, metadata)
h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision)
candidate = json.loads((h.candidate_dir / "candidate.json").read_text())
assert (
candidate["databaseMetadataSha256"]
== hashlib.sha256(path.read_bytes()).hexdigest()
)
assert json.loads(path.read_text()) == metadata
def test_cli_requires_explicit_fixed_target_and_subcommand(modules, monkeypatch):
_controller, utility = modules
monkeypatch.setattr(utility, "_is_host_root", lambda: False)
with pytest.raises(SystemExit):
utility.main([])
monkeypatch.setattr(utility, "_is_host_root", lambda: True)
assert (
utility.main(["plan", "--repository", "other/repo", "--revision", "a" * 40])
== 2
)