refactor(secrets): every private-credential file is written by utils.atomic_json_write(mode=0o600)
Ten hand-rolled "write a token file safely" routines each carried a
different subset of {0600-on-create, fsync, atomic_replace, parent-0700
guard, BaseException cleanup}. Two of them (iron_proxy state files,
the exchanged-JWT store) still opened the temp file at process umask
and chmod'ed afterwards - the exact TOCTOU window the others document
as fixed. None of the bare-os.replace copies got atomic_replace's
Windows-contention retry or EXDEV fallback.
utils gains fsync_dir= (absorbs auth.py's dir fsync), atomic_write_bytes
(vault blob) and mode= on atomic_write_text; the ten sites become 1-3
line callers. mkstemp creates the temp file O_EXCL at 0600 regardless of
umask, so the payload is never umask-readable.
Behavior change: iron_proxy proxy.yaml/mappings.json and the exchanged-JWT
store are now 0600 from creation and fsync'd; every credential write goes
through atomic_replace (symlink-preserving, Windows retry, EXDEV copy).
auth_nous shared store now uses atomic_replace too (it forced os.replace
with no recorded reason). secret_sources cache parent-0700 goes through
the guarded secure_parent_dir instead of an unguarded chmod.
This commit is contained in:
@@ -187,14 +187,22 @@ def reseed_if_terminal(auth_path: str, seed_raw: str) -> str:
|
||||
# Surgical replacement: swap ONLY providers.nous, preserve everything else.
|
||||
providers["nous"] = seed_nous
|
||||
|
||||
tmp_path = f"{auth_path}.rebootstrap.tmp"
|
||||
with open(tmp_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(store, fh)
|
||||
os.replace(tmp_path, auth_path)
|
||||
# 0600 from creation: the seed holds a refresh token and must never sit at umask, even briefly.
|
||||
# (stdlib only by design — see module docstring — so this mirrors utils.atomic_json_write by hand.)
|
||||
tmp_path = f"{auth_path}.rebootstrap.{os.getpid()}.tmp"
|
||||
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
try:
|
||||
os.chmod(auth_path, 0o600)
|
||||
except OSError:
|
||||
pass
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
json.dump(store, fh)
|
||||
fh.flush()
|
||||
os.fsync(fh.fileno())
|
||||
os.replace(tmp_path, auth_path)
|
||||
except BaseException:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
return "reseeded" if terminal else "reseeded_newer"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user