Seal catalog publication Git metadata
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 15s
Security Audit / security-audit (push) Failing after 13s

This commit is contained in:
2026-07-22 22:31:58 +02:00
parent 76e4baa76e
commit e005e54333
2 changed files with 93 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import hashlib
import json import json
import os import os
from pathlib import Path from pathlib import Path
@@ -26,12 +27,65 @@ from govoplan_release.publisher import ( # noqa: E402
verify_committed_publication, verify_committed_publication,
verify_remote_branch_head, verify_remote_branch_head,
verify_remote_publication, verify_remote_publication,
_git_bytes,
_sanitized_git_environment, _sanitized_git_environment,
_seal_git_metadata_file,
_trusted_npm_command, _trusted_npm_command,
) )
class ReleaseCatalogPublicationTests(unittest.TestCase): class ReleaseCatalogPublicationTests(unittest.TestCase):
def test_publication_git_writes_ignore_permissive_operator_umask(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
repository = Path(temp_dir) / "website"
repository.mkdir()
self._git(repository, "init", "--quiet")
payload_number = 0
while True:
payload = f"private publication object {payload_number}\n".encode()
header = f"blob {len(payload)}\0".encode()
expected_object = hashlib.sha1(
header + payload,
usedforsecurity=False,
).hexdigest()
object_parent = repository / ".git" / "objects" / expected_object[:2]
if not object_parent.exists():
break
payload_number += 1
previous_umask = os.umask(0o002)
try:
object_id = (
_git_bytes(
repository,
"hash-object",
"-w",
"--stdin",
input_bytes=payload,
)
.decode("ascii")
.strip()
)
finally:
os.umask(previous_umask)
object_path = object_parent / expected_object[2:]
self.assertEqual(expected_object, object_id)
self.assertEqual(os.geteuid(), object_parent.stat().st_uid)
self.assertEqual(0o700, object_parent.stat().st_mode & 0o777)
self.assertEqual(os.geteuid(), object_path.stat().st_uid)
self.assertEqual(0o400, object_path.stat().st_mode & 0o777)
def test_publication_git_metadata_is_sealed_after_git_writes(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
metadata = Path(temp_dir) / "index"
metadata.write_bytes(b"index")
metadata.chmod(0o664)
_seal_git_metadata_file(metadata, label="test index")
self.assertEqual(0o600, metadata.stat().st_mode & 0o777)
def test_publication_git_identity_is_fixed_and_non_personal(self) -> None: def test_publication_git_identity_is_fixed_and_non_personal(self) -> None:
with patch.dict( with patch.dict(
os.environ, os.environ,

View File

@@ -614,6 +614,14 @@ def publish_catalog_candidate(
effective_tag_name, effective_tag_name,
publication_commit, publication_commit,
) )
_seal_git_metadata_file(
resolved_web_root
/ ".git"
/ "refs"
/ "tags"
/ effective_tag_name,
label="website publication tag reference",
)
publication_tag_object = git_text( publication_tag_object = git_text(
resolved_web_root, resolved_web_root,
"rev-parse", "rev-parse",
@@ -1445,9 +1453,38 @@ def commit_publication_tree(
frozen_head, frozen_head,
) )
_git_checked(web_root, "read-tree", "--reset", commit_sha) _git_checked(web_root, "read-tree", "--reset", commit_sha)
_seal_git_metadata_file(
web_root / ".git" / "refs" / "heads" / branch,
label="website branch reference",
)
_seal_git_metadata_file(
web_root / ".git" / "index",
label="website Git index",
)
return commit_sha return commit_sha
def _seal_git_metadata_file(path: Path, *, label: str) -> None:
try:
metadata = path.lstat()
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISREG(metadata.st_mode)
or metadata.st_uid != os.geteuid()
):
raise RuntimeError(f"{label} is not an operator-owned regular file")
os.chmod(path, 0o600, follow_symlinks=False)
sealed = path.lstat()
except OSError as exc:
raise RuntimeError(f"{label} cannot be sealed") from exc
if (
not stat.S_ISREG(sealed.st_mode)
or sealed.st_uid != os.geteuid()
or stat.S_IMODE(sealed.st_mode) != 0o600
):
raise RuntimeError(f"{label} was not sealed to operator-only access")
def verify_committed_publication( def verify_committed_publication(
*, *,
web_root: Path, web_root: Path,
@@ -1948,6 +1985,8 @@ def _git_bytes(
"-C", "-C",
str(path), str(path),
"-c", "-c",
"core.sharedRepository=0600",
"-c",
"core.fsmonitor=false", "core.fsmonitor=false",
"-c", "-c",
"core.hooksPath=/dev/null", "core.hooksPath=/dev/null",