Harden release source execution

This commit is contained in:
2026-07-22 21:37:14 +02:00
parent 349a099e5a
commit 7ecf1f17b0
11 changed files with 3331 additions and 58 deletions

View File

@@ -33,10 +33,11 @@ from .catalog_entry_synthesis import (
validate_initial_entry_closure,
)
from .contracts import collect_contracts
from .git_state import collect_repository_snapshot
from .git_state import collect_repository_snapshot, git_text
from .model import CatalogEntryChange, SelectiveCatalogCandidate
from .module_directory import write_module_directory
from .source_provenance import (
SourceTagProvenanceIssue,
catalog_source_selection,
selected_source_provenance,
source_tag_provenance_issues,
@@ -45,7 +46,12 @@ from .version_alignment import (
selected_release_webui_bundle_issues,
selected_repository_version_issues,
)
from .workspace import load_repository_specs, resolve_workspace_root, website_root
from .workspace import (
load_repository_specs,
resolve_repo_path,
resolve_workspace_root,
website_root,
)
GITEA_BASE = "git+ssh://git@git.add-ideas.de/add-ideas"
MAX_BASE_JSON_BYTES = 16 * 1024 * 1024
@@ -77,6 +83,7 @@ def build_selective_catalog_candidate(
check_public: bool = True,
write_summary: bool = True,
python_artifacts: dict[str, Path | str] | None = None,
frozen_source_provenance: dict[str, dict[str, str]] | None = None,
) -> SelectiveCatalogCandidate:
channel = validate_release_channel(channel)
if not repo_versions:
@@ -88,15 +95,23 @@ def build_selective_catalog_candidate(
workspace = resolve_workspace_root(workspace_root)
enforce_selected_version_alignment(repo_versions=repo_versions, workspace=workspace)
enforce_selected_source_provenance(
repo_versions=repo_versions,
workspace=workspace,
remote=source_remote,
)
selected_provenance = selected_source_provenance(
repo_versions=repo_versions,
workspace=workspace,
)
if frozen_source_provenance is None:
enforce_selected_source_provenance(
repo_versions=repo_versions,
workspace=workspace,
remote=source_remote,
)
selected_provenance = selected_source_provenance(
repo_versions=repo_versions,
workspace=workspace,
)
else:
selected_provenance = enforce_frozen_selected_source_provenance(
repo_versions=repo_versions,
expected_provenance=frozen_source_provenance,
workspace=workspace,
remote=source_remote,
)
web_root = website_root(workspace)
authenticated_base = load_authenticated_catalog_base(
base_catalog=base_catalog,
@@ -132,7 +147,10 @@ def build_selective_catalog_candidate(
]
candidate["release"] = release
repo_contracts = contracts_by_repo(workspace)
repo_contracts = contracts_by_repo(
workspace,
selected_repositories=set(repo_versions),
)
changes = apply_repo_updates(
candidate,
repo_versions=repo_versions,
@@ -151,6 +169,7 @@ def build_selective_catalog_candidate(
payload=candidate,
workspace=workspace,
remote=source_remote,
require_selected_heads=frozen_source_provenance is None,
)
output_root = resolve_output_dir(output_dir=output_dir, channel=channel, generated_at=generated_at)
@@ -283,11 +302,107 @@ def enforce_selected_source_provenance(
)
def enforce_frozen_selected_source_provenance(
*,
repo_versions: dict[str, str],
expected_provenance: dict[str, dict[str, str]],
workspace: Path,
remote: str = "origin",
) -> dict[str, dict[str, str]]:
"""Verify detached, clean source checkouts against durable object receipts.
Normal interactive candidate generation still requires a named, current
source branch. The durable executor instead operates on isolated clones of
exact annotated origin tags, so it supplies the commit and tag object that
must be present at each detached HEAD.
"""
if set(expected_provenance) != set(repo_versions):
raise ValueError(
"Frozen source provenance must cover exactly the selected repositories."
)
expected_commits: dict[str, str] = {}
expected_tag_objects: dict[str, str] = {}
for repo in sorted(repo_versions):
identity = expected_provenance.get(repo)
commit = identity.get("commit_sha") if isinstance(identity, dict) else None
tag_object = (
identity.get("tag_object_sha") if isinstance(identity, dict) else None
)
if (
not isinstance(identity, dict)
or set(identity) != {"commit_sha", "tag_object_sha"}
or not isinstance(commit, str)
or re.fullmatch(r"[0-9a-f]{40}|[0-9a-f]{64}", commit) is None
or not isinstance(tag_object, str)
or re.fullmatch(r"[0-9a-f]{40}|[0-9a-f]{64}", tag_object) is None
):
raise ValueError(
f"Frozen source provenance is malformed for {repo}."
)
expected_commits[repo] = commit
expected_tag_objects[repo] = tag_object
failures = list(
source_tag_provenance_issues(
repo_versions=repo_versions,
workspace=workspace,
remote=remote,
expected_commits=expected_commits,
expected_tag_objects=expected_tag_objects,
)
)
specs = {spec.name: spec for spec in load_repository_specs(include_website=False)}
for repo, version in sorted(repo_versions.items()):
spec = specs.get(repo)
if spec is None:
continue
path = resolve_repo_path(spec, workspace)
snapshot = collect_repository_snapshot(
spec,
workspace_root=workspace,
target_tag=f"v{version.removeprefix('v')}",
online=False,
)
if snapshot.dirty_entries:
failures.append(
SourceTagProvenanceIssue(
repo,
f"v{version.removeprefix('v')}",
"frozen source worktree is not clean",
)
)
head = git_text(path, "rev-parse", "--verify", "HEAD")
if head != expected_commits[repo]:
failures.append(
SourceTagProvenanceIssue(
repo,
f"v{version.removeprefix('v')}",
"frozen source HEAD does not match its durable commit receipt",
)
)
if failures:
raise ValueError(
"Frozen source provenance gate failed: "
+ "; ".join(issue.describe() for issue in failures)
)
observed = selected_source_provenance(
repo_versions=repo_versions,
workspace=workspace,
)
if observed != expected_provenance:
raise ValueError(
"Frozen source object identities changed during candidate synthesis."
)
return observed
def enforce_complete_catalog_source_provenance(
*,
payload: object,
workspace: Path,
remote: str = "origin",
require_selected_heads: bool = True,
) -> None:
selection = catalog_source_selection(payload)
failures = [*selection.issues]
@@ -296,7 +411,9 @@ def enforce_complete_catalog_source_provenance(
repo_versions=selection.all_versions,
workspace=workspace,
remote=remote,
require_head_repos=selection.selected_versions,
require_head_repos=(
selection.selected_versions if require_selected_heads else ()
),
expected_commits=selection.selected_commits,
expected_tag_objects=selection.selected_tag_objects,
)
@@ -408,7 +525,9 @@ def read_bounded_json_source(source: Path | str, *, label: str) -> object:
raise ValueError(f"{label.capitalize()} is not valid UTF-8 JSON.") from exc
def _read_regular_file_without_symlinks(path: Path, *, label: str) -> bytes:
def _read_regular_file_without_symlinks(
path: Path, *, label: str, require_private_owner: bool = False
) -> bytes:
absolute = path.absolute()
parts = absolute.parts[1:]
if not parts:
@@ -439,6 +558,13 @@ def _read_regular_file_without_symlinks(path: Path, *, label: str) -> bytes:
raise ValueError(
f"{label.capitalize()} must be a bounded regular file."
)
if require_private_owner and (
initial.st_uid != os.geteuid() or stat.S_IMODE(initial.st_mode) & 0o077
):
raise ValueError(
f"{label.capitalize()} must be owned by the current operator "
"and inaccessible to other users."
)
chunks: list[bytes] = []
remaining = initial.st_size
while remaining:
@@ -474,8 +600,14 @@ def next_sequence(payload: dict[str, Any], *, generated_at: datetime) -> int:
return max(current + 1, timestamp_sequence)
def contracts_by_repo(workspace: Path) -> dict[str, Any]:
specs = load_repository_specs(include_website=False)
def contracts_by_repo(
workspace: Path, *, selected_repositories: set[str] | None = None
) -> dict[str, Any]:
specs = tuple(
spec
for spec in load_repository_specs(include_website=False)
if selected_repositories is None or spec.name in selected_repositories
)
snapshots = tuple(
collect_repository_snapshot(spec, workspace_root=workspace, target_tag=None, online=False)
for spec in specs
@@ -719,7 +851,11 @@ def parse_signing_key(value: str) -> tuple[str, Ed25519PrivateKey]:
raise ValueError("Catalog signing key ID is not a bounded opaque ID.")
path = Path(path_text).expanduser()
private_key = serialization.load_pem_private_key(
_read_regular_file_without_symlinks(path, label="catalog signing key"),
_read_regular_file_without_symlinks(
path,
label="catalog signing key",
require_private_owner=True,
),
password=None,
)
if not isinstance(private_key, Ed25519PrivateKey):