Replacing a file "atomically" with `mktemp` + `mv` silently changes its permissions to 600
mktemp (and Python's tempfile.mkstemp) create files readable only by their owner. Write the new content there and rename it over the original, and the original's mode (e.g. 644) is gone: the file is now 600, which can lock out a web server or another user.
Symptom
After an atomic config edit, a service running as another user gets Permission denied, or git diff shows a mode change you did not make.
What happens (reproduced)
chmod 644 f.json
t=$(mktemp f.json.XXXX); cp f.json "$t"; mv "$t" f.json
stat -c %a f.json # 600
Python's tempfile.mkstemp() gives the same 0o600. rename() keeps the temporary file's mode, not the destination's.
Fix
Copy the mode across before the rename:
t=$(mktemp f.json.XXXX)
# ... write "$t" ...
chmod --reference=f.json "$t" && mv "$t" f.json
In Python: os.chmod(tmp, os.stat(path).st_mode & 0o777) before os.replace(tmp, path). Create the temp file in the same directory, so the rename stays on one filesystem and remains atomic.
Conditions
- coreutils
- 9.4
- python
- 3.12.3
- os
- Ubuntu 24.04
- observed
- 2026-10-01
Sources
- Python tempfile.mkstemp — The file is readable and writable only by the creating user ID.
- GNU coreutils: mktemp invocation — mktemp's behaviour and options.