feat: select retained Custodian snapshot through pinned descriptor

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
This commit is contained in:
tegwick 2026-10-19 15:22:22 +02:00
parent fed119a17c
commit 3b25aea7b0
6 changed files with 150 additions and 17 deletions

View file

@ -1,6 +1,7 @@
"""Fail-closed admission for trusted-owner fleet snapshots and freshness receipts."""
import hashlib
import json
import re
import stat
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
@ -79,3 +80,21 @@ def admit(fleet, policy_path, receipts_path, repos, now=None):
if actual_files != expected_files or total > policy["max_snapshot_bytes"]:
raise ValueError("snapshot content outside manifest or size policy")
return hashlib.sha256(raw).hexdigest()
def select_snapshot(snapshot_root, state_root, descriptor_path):
"""Read one protected atomic descriptor; never follow a mutable symlink pointer."""
root, state, path = Path(snapshot_root), Path(state_root), Path(descriptor_path)
if not path.is_absolute() or ".." in path.parts or state not in path.parents:
raise ValueError("snapshot descriptor outside private state")
descriptor = json.loads(_read(path))
snapshot_id = descriptor.get("snapshot_id", "")
if descriptor.get("schema") != "activity-core.custodian-snapshot-selection.v1" or not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}", snapshot_id):
raise ValueError("invalid snapshot selection")
selected = root / snapshot_id
manifest_hash = hashlib.sha256(_read(selected / "manifest.json")).hexdigest()
receipts = state / "inputs" / (snapshot_id + ".receipts.json")
receipts_hash = hashlib.sha256(_read(receipts)).hexdigest()
if manifest_hash != descriptor.get("manifest_sha256") or receipts_hash != descriptor.get("receipts_sha256"):
raise ValueError("snapshot selection digest mismatch")
return selected, receipts

View file

@ -84,6 +84,11 @@ def build_command(operation: str, params: dict) -> tuple[list[str], Path, Path,
argv = [sys.executable, str(script)]
request = None
if operation == "custodian_security_flow":
from activity_core.custodian_inputs import select_snapshot
descriptor = os.environ.get("ACTIVITY_CORE_CUSTODIAN_SNAPSHOT_DESCRIPTOR")
selected_receipts = None
if descriptor:
fleet, selected_receipts = select_snapshot(fleet, state, descriptor)
repos = os.environ.get("ACTIVITY_CORE_CUSTODIAN_REPOS", "").split(",")
if not repos or any(re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", repo) is None for repo in repos):
raise ValueError("explicit canonical security-flow repository list required")
@ -91,7 +96,7 @@ def build_command(operation: str, params: dict) -> tuple[list[str], Path, Path,
_path(str(fleet / repo / "workplans"), directory=True)
from activity_core.custodian_inputs import admit
policy_path = _path(os.environ.get("ACTIVITY_CORE_CUSTODIAN_INPUT_POLICY"))
receipts_path = _path(os.environ.get("ACTIVITY_CORE_CUSTODIAN_FRESHNESS_RECEIPTS"))
receipts_path = selected_receipts or _path(os.environ.get("ACTIVITY_CORE_CUSTODIAN_FRESHNESS_RECEIPTS"))
admit(fleet, policy_path, receipts_path, repos)
hub = os.environ.get("ACTIVITY_CORE_CUSTODIAN_HUB", "")
parsed = urlsplit(hub)