This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch sbp in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit 1bac8a96d2261902e52d22f16c4ca48ba93d02a9 Author: Sean B. Palmer <[email protected]> AuthorDate: Mon Apr 6 14:50:39 2026 +0100 Ensure that attestation files are read only after writing --- atr/attestable.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/atr/attestable.py b/atr/attestable.py index 999e5c6b..e510e5fe 100644 --- a/atr/attestable.py +++ b/atr/attestable.py @@ -17,8 +17,13 @@ from __future__ import annotations +import asyncio import json -from typing import TYPE_CHECKING, Any +import os +from typing import TYPE_CHECKING, Any, Final + +if TYPE_CHECKING: + from collections.abc import Callable import aiofiles import aiofiles.os @@ -37,6 +42,8 @@ import atr.util as util if TYPE_CHECKING: import pathlib +_READONLY_PERMISSIONS: Final[int] = 0o444 + def attestable_checks_path( project_key: safe.ProjectKey, version_key: safe.VersionKey, revision_number: safe.RevisionNumber @@ -169,7 +176,7 @@ async def github_tp_payload_write( # Dump the workflow payload, excluding exp and nbf - which shouldn't have made it this far. If they do, # it's safe to remove them as they've been validated by the model already, and we should never store # stale dates - await util.atomic_write_file( + await _atomic_write_readonly( payload_path.path, json.dumps(github_payload.model_dump(exclude={"exp", "nbf"}), indent=2) ) @@ -267,7 +274,7 @@ async def write_checks_data( result = models.AttestableChecksV2(checks=current) return result.model_dump_json(indent=2) - await util.atomic_modify_file(attestable_checks_path(project_key, version_key, revision_number).path, modify) + await _atomic_modify_readonly(attestable_checks_path(project_key, version_key, revision_number).path, modify) async def write_files_data( @@ -293,11 +300,20 @@ async def write_files_data( classifications=classifications, ) file_path = attestable_path(project_key, version_key, revision_number) - await util.atomic_write_file(file_path.path, result.model_dump_json(indent=2)) + await _atomic_write_readonly(file_path.path, result.model_dump_json(indent=2)) checks_file_path = attestable_checks_path(project_key, version_key, revision_number) if not checks_file_path.path.exists(): - async with aiofiles.open(checks_file_path, "w", encoding="utf-8") as f: - await f.write(models.AttestableChecksV2().model_dump_json(indent=2)) + await _atomic_write_readonly(checks_file_path.path, models.AttestableChecksV2().model_dump_json(indent=2)) + + +async def _atomic_modify_readonly(file_path: pathlib.Path, modify: Callable[[str], str]) -> None: + await util.atomic_modify_file(file_path, modify) + await asyncio.to_thread(os.chmod, file_path, _READONLY_PERMISSIONS) + + +async def _atomic_write_readonly(file_path: pathlib.Path, content: str) -> None: + await util.atomic_write_file(file_path, content) + await asyncio.to_thread(os.chmod, file_path, _READONLY_PERMISSIONS) def _compute_hashes_with_attribution( # noqa: C901 --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
