Summary
prepareCodexSecurityCredentialHome creates …/codex-home as mode 0700 and checks only the leaf owner/mode. It does not walk ancestors for sticky / trusted ownership, and later credential use paths (acquireCodexSecurityCredentialHomeLock, codexSecurityHasStoredFileCredentials, login/logout fallbacks) do not re-verify privacy or ancestry.
On a group/world-writable non-sticky parent, another local UID can mv the private credential home aside and read auth.json (ChatGPT / API session material). This is the same cross-user rename class as scan-output issue #109 / PRs #110+#118, but currently unfixed for durable auth state.
Default ~/.codex/... is usually fine. Any CODEX_SECURITY_STATE_DIR / custom state under a shared volume is exposed.
Evidence (current main @ 9c7634b, @openai/codex-security@0.1.4)
sdk/typescript/src/runtime.ts — prepare only enforces leaf privacy:
prepareCodexSecurityCredentialHome → mkdir/chmod 0700 → requirePrivateCredentialHome(leaf)
- no sticky / trusted-ancestor walk
acquireCodexSecurityCredentialHomeLock and codexSecurityHasStoredFileCredentials do not re-check the home before use
auth.json existence checks require a regular non-symlink file but not private mode/owner
Open PR #118 hardens scan output ancestry only; it does not touch the credential-home path.
Steps to reproduce
mkdir -p /shared/cs && chmod 777 /shared/cs # no sticky bit
export CODEX_SECURITY_STATE_DIR=/shared/cs/state
# victim: login / scan with stored credentials → …/state/codex-home/auth.json (0700)
# other UID:
mv /shared/cs/state/codex-home /shared/cs/stolen
# read /shared/cs/stolen/auth.json
Sticky parents (e.g. /tmp with the sticky bit) are safe; non-sticky shared parents are not rejected today.
Why in scope (SECURITY.md)
Suggested fix
Reuse / mirror #118 helpers (requireSecureOutputAncestry / requireTrustedOutputAncestor):
- Reject non-sticky shared ancestors and untrusted parent owners for the credential home
- Re-check on prepare and every use path (lock, stored-auth probe, login/logout)
- Enforce private mode/owner on
auth.json before treating stored credentials as present
- Pin
(st_dev, st_ino) for the duration of a credential-home lock session (durable cross-process pin optional/deferred without a workbench row)
Affected version
@openai/codex-security@0.1.4
- Confirmed on
main at 9c7634b
Summary
prepareCodexSecurityCredentialHomecreates…/codex-homeas mode0700and checks only the leaf owner/mode. It does not walk ancestors for sticky / trusted ownership, and later credential use paths (acquireCodexSecurityCredentialHomeLock,codexSecurityHasStoredFileCredentials, login/logout fallbacks) do not re-verify privacy or ancestry.On a group/world-writable non-sticky parent, another local UID can
mvthe private credential home aside and readauth.json(ChatGPT / API session material). This is the same cross-user rename class as scan-output issue #109 / PRs #110+#118, but currently unfixed for durable auth state.Default
~/.codex/...is usually fine. AnyCODEX_SECURITY_STATE_DIR/ custom state under a shared volume is exposed.Evidence (current
main@9c7634b,@openai/codex-security@0.1.4)sdk/typescript/src/runtime.ts— prepare only enforces leaf privacy:prepareCodexSecurityCredentialHome→mkdir/chmod 0700→requirePrivateCredentialHome(leaf)acquireCodexSecurityCredentialHomeLockandcodexSecurityHasStoredFileCredentialsdo not re-check the home before useauth.jsonexistence checks require a regular non-symlink file but not private mode/ownerOpen PR #118 hardens scan output ancestry only; it does not touch the credential-home path.
Steps to reproduce
Sticky parents (e.g.
/tmpwith the sticky bit) are safe; non-sticky shared parents are not rejected today.Why in scope (SECURITY.md)
Suggested fix
Reuse / mirror #118 helpers (
requireSecureOutputAncestry/requireTrustedOutputAncestor):auth.jsonbefore treating stored credentials as present(st_dev, st_ino)for the duration of a credential-home lock session (durable cross-process pin optional/deferred without a workbench row)Affected version
@openai/codex-security@0.1.4mainat9c7634b