This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new 6cd3688  Disallow writing to release files after staging
6cd3688 is described below

commit 6cd36889ef90e616092d9a4b8c9ca2c1009d33c6
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jan 20 17:04:38 2026 +0000

    Disallow writing to release files after staging
---
 atr/server.py                   | 21 +++++++++++++++++++++
 atr/storage/writers/revision.py |  7 +++++++
 2 files changed, 28 insertions(+)

diff --git a/atr/server.py b/atr/server.py
index b52bcfe..cd76bf9 100644
--- a/atr/server.py
+++ b/atr/server.py
@@ -238,6 +238,8 @@ def _app_setup_lifecycle(app: base.QuartApp) -> None:
         if listener := app.extensions.get("logging_listener"):
             listener.start()
 
+        await asyncio.to_thread(_set_file_permissions_to_read_only)
+
         worker_manager = manager.get_worker_manager()
         await worker_manager.start()
 
@@ -706,6 +708,25 @@ def _register_routes(app: base.QuartApp) -> None:
         return await template.render("notfound.html", error="404 Not Found", 
traceback="", status_code=404), 404
 
 
+def _set_file_permissions_to_read_only() -> None:
+    """Set permissions of all files in the unfinished and finished directories 
to read only."""
+    # TODO: After a migration period, incorrect permissions should be an error
+    directories = [util.get_unfinished_dir(), util.get_finished_dir()]
+    fixed_count = 0
+    for directory in directories:
+        if not directory.exists():
+            continue
+        for file_path in directory.rglob("*"):
+            if not file_path.is_file():
+                continue
+            mode = stat.S_IMODE(file_path.stat().st_mode)
+            if mode != 0o444:
+                os.chmod(file_path, 0o444)
+                fixed_count += 1
+    if fixed_count > 0:
+        log.info(f"Set permissions of {fixed_count} files to read only 
(0o444)")
+
+
 def _validate_config(app_config: type[config.AppConfig], hot_reload: bool) -> 
None:
     # Custom configuration for the database path is no longer supported
     configured_path = app_config.SQLITE_DB_PATH
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index ddc89e5..f7037c6 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -157,6 +157,13 @@ class CommitteeParticipant(FoundationCommitter):
             await aioshutil.rmtree(temp_dir)
             raise
 
+        # Make files read only to prevent them from being modified through 
hard links
+        try:
+            await asyncio.to_thread(util.chmod_files, temp_dir_path, 0o444)
+        except Exception:
+            await aioshutil.rmtree(temp_dir)
+            raise
+
         async with SafeSession(temp_dir) as data:
             try:
                 # This is the only place where models.Revision is constructed


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to