From 96e88a355528fa05f0de1cd2f4719b5c4d634382 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:00:59 -0700 Subject: [PATCH] fix: force 0600 on fallback state rewrites, not just creation --- app/state_store.py | 8 ++++++++ app/tests/test_state_store.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/app/state_store.py b/app/state_store.py index c2a69f7..1958286 100644 --- a/app/state_store.py +++ b/app/state_store.py @@ -153,6 +153,14 @@ class AtomicJsonStore: # per-download option overrides that must not leak on shared mounts. fd = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: + # The 0o600 mode above only applies when the file is created; force + # it on rewrites too so an existing, broadly-permissioned state file + # is tightened to match the atomic path. Best-effort because some + # network filesystems reject chmod, and that must not re-crash save. + try: + os.fchmod(f.fileno(), 0o600) + except OSError: + pass self._write_payload(payload, f) f.flush() self._best_effort_fsync(f.fileno()) diff --git a/app/tests/test_state_store.py b/app/tests/test_state_store.py index 9309f4f..82e3e30 100644 --- a/app/tests/test_state_store.py +++ b/app/tests/test_state_store.py @@ -41,6 +41,23 @@ class StateStoreTests(unittest.TestCase): payload = store.load() self.assertEqual(payload["items"], [{"key": "a"}]) + def test_fallback_tightens_permissions_on_existing_file(self): + with tempfile.TemporaryDirectory() as tmp: + path = os.path.join(tmp, "queue.json") + with open(path, "w", encoding="utf-8") as f: + f.write("{}") + os.chmod(path, 0o644) + + store = AtomicJsonStore(path, kind="persistent_queue:queue") + with patch( + "state_store.tempfile.mkstemp", + side_effect=PermissionError(1, "Operation not permitted"), + ): + store.save({"items": [{"key": "a"}]}) + + self.assertEqual(os.stat(path).st_mode & 0o777, 0o600) + self.assertEqual(store.load()["items"], [{"key": "a"}]) + def test_save_falls_back_to_direct_write_when_replace_fails(self): with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "queue.json")