Skip to content

Create credential files with owner-only permissions - #945

Merged
0xallam merged 4 commits into
usestrix:mainfrom
lukiod:fix/secret-file-permissions
Aug 4, 2026
Merged

Create credential files with owner-only permissions#945
0xallam merged 4 commits into
usestrix:mainfrom
lukiod:fix/secret-file-permissions

Conversation

@lukiod

@lukiod lukiod commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Credential files were written with write_text and then chmod'd to 0600:

tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
with contextlib.suppress(OSError):
    tmp.chmod(0o600)

Two problems. The file exists with the default umask (usually 0644) between the write and the chmod, so the token is briefly readable by any local user. And the chmod is suppressed, so if it fails the file just stays 0644 with nothing reported.

Adds strix/utils/secret_files.py with write_secret_text, which opens with O_CREAT | O_EXCL and mode 0600 so the file never exists with wider permissions, and uses it for the viewer auth file and the env block.

Tests in tests/test_secret_files.py. The permission assertions are POSIX-only and skip on Windows, which is where I ran them — 2 passed, 4 skipped locally.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR centralizes sensitive-text persistence in a new owner-only atomic writer.

  • Replaces post-write chmod sequences for Codex credentials, CLI configuration, and viewer authentication.
  • Creates temporary files with mode 0600 and attempts to restrict their parent directory to 0700.
  • Adds content, overwrite, umask, directory-mode, and stale-temporary-file tests.

Confidence Score: 4/5

The concurrent temporary-file race should be fixed before merging because simultaneous credential writes can fail or publish malformed data.

The helper improves creation-time permissions, but all writes to a destination share an unprotected temporary pathname that concurrent viewer requests or CLI processes can delete or reuse.

Files Needing Attention: strix/utils/secret_files.py, strix/viewer/auth.py

Important Files Changed

Filename Overview
strix/utils/secret_files.py Adds restricted atomic writes, but the shared deterministic temporary pathname is unsafe under concurrent writes.
strix/config/codex.py Migrates subscription credential persistence to the helper and therefore inherits its concurrent-write failure.
strix/config/loader.py Migrates persisted environment configuration to owner-only creation.
strix/viewer/auth.py Migrates viewer credentials to the helper, exposing its temporary-file race through threaded request handling.
tests/test_secret_files.py Covers permissions and sequential stale temporary files but not concurrent writes to the same destination.
Prompt To Fix All With AI
### Issue 1
strix/utils/secret_files.py:24-30
**Shared temporary path races**

When two requests or processes write the same credential file concurrently, both use and unlink the same `.tmp` path, causing one write to raise `FileExistsError` or interfere with the other writer and publish malformed data. This is reachable through simultaneous OTP verification requests handled by the threaded viewer server.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix: create credential files with owner ..." | Re-trigger Greptile

Comment thread strix/utils/secret_files.py
lukiod added 2 commits August 1, 2026 09:54
Three call sites wrote secrets with Path.write_text and restricted them with
chmod afterwards. write_text creates using 0o666 & ~umask, so the contents were
readable by other local users until the chmod landed, and permanently if it
failed, since the failure was suppressed.

Affected: the Codex OAuth access and refresh tokens, the env block written by
the config loader, which carries API keys, and the viewer auth record.

write_secret_text passes the mode to os.open so it applies at creation. The
parent directory is created 0700, and a temporary left by an interrupted write
is replaced rather than truncated.

Measured on Linux under the default umask: 0644 holding the token before being
narrowed to 0600, 0666 under umask 0.
Import ordering in codex.py, Path moved into a type-checking block where it is
only used in annotations, and a noqa for the test fixture string ruff reads as a
password.
@lukiod
lukiod force-pushed the fix/secret-file-permissions branch from 47018c4 to d0223c0 Compare August 1, 2026 04:25
@0xallam
0xallam merged commit 23f1d76 into usestrix:main Aug 4, 2026
@lukiod
lukiod deleted the fix/secret-file-permissions branch August 6, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants