fix(auth): only a Codex token refresh writes through to root

Following the grant's source on every save made a fresh device-code
login (or `hermes auth import`) under a profile that had been borrowing
root's Codex grant overwrite root's account instead of creating the
profile's own. Redirecting a save into another file is the exception, so
it is opt-in: the refresh path passes write_through=True; login, import
and recovery keep saving locally. The two save branches collapse into one
(store, path, set_active) triple.

Test: root discovery on Windows comes from LOCALAPPDATA — set it so the
fixture's root is the resolved root on every host.
This commit is contained in:
kshitijk4poor
2026-09-13 20:54:14 +05:30
committed by kshitij
parent 0ff20dc98a
commit 66ddd5f83c
2 changed files with 25 additions and 16 deletions
+14 -14
View File
@@ -142,7 +142,9 @@ def _sync_codex_pool_entries(
_clear_pool_entry_status(entry)
def _save_codex_tokens(tokens: Dict[str, str], last_refresh: str = None, label: str = None) -> None:
def _save_codex_tokens(
tokens: Dict[str, str], last_refresh: str = None, label: str = None, *, write_through: bool = False,
) -> None:
"""Save Codex OAuth tokens to the auth store the grant was resolved FROM.
Codex refresh tokens are single-use with rotation-family reuse detection. A profile without its
@@ -151,10 +153,13 @@ def _save_codex_tokens(tokens: Dict[str, str], last_refresh: str = None, label:
consumed refresh token, the next process replays it and OpenAI revokes the whole family
(#87503). Root-only write-back: a profile copy would shadow root and disable the write-through
on the next refresh (#74339). Mirrors the xAI source-aware save.
Only a token REFRESH passes ``write_through=True``: a fresh login or import under a profile
is the profile's own grant and must not overwrite the root account it was borrowing.
"""
from hermes_cli.auth import (
_auth_file_path, _load_auth_store, _provider_state_transaction, _same_path,
_save_auth_store, _save_provider_state, _store_provider_state, _utc_now_z)
_save_auth_store, _store_provider_state, _utc_now_z)
if last_refresh is None:
last_refresh = _utc_now_z()
with _provider_state_transaction("openai-codex") as (auth_store, state, source_path):
@@ -166,20 +171,15 @@ def _save_codex_tokens(tokens: Dict[str, str], last_refresh: str = None, label:
state.update(tokens=tokens, last_refresh=last_refresh, auth_mode="chatgpt")
if label and str(label).strip():
state["label"] = str(label).strip()
if source_path is not None and not _same_path(source_path, _auth_file_path()):
target_store, target_path, set_active = auth_store, None, True
if write_through and source_path is not None and not _same_path(source_path, _auth_file_path()):
# Root-borrowed grant: the transaction already holds root's lock, so write the rotated
# chain into ROOT's store (never set_active — a refresh is not a provider choice).
root_store = _load_auth_store(source_path)
_store_provider_state(root_store, "openai-codex", state, set_active=False)
_sync_codex_pool_entries(
root_store, tokens, last_refresh,
previous_singleton_tokens=previous_singleton_tokens)
_save_auth_store(root_store, target_path=source_path)
return
_save_provider_state(auth_store, "openai-codex", state)
target_store, target_path, set_active = _load_auth_store(source_path), source_path, False
_store_provider_state(target_store, "openai-codex", state, set_active=set_active)
_sync_codex_pool_entries(
auth_store, tokens, last_refresh, previous_singleton_tokens=previous_singleton_tokens)
_save_auth_store(auth_store)
target_store, tokens, last_refresh, previous_singleton_tokens=previous_singleton_tokens)
_save_auth_store(target_store, target_path=target_path)
def _recover_codex_tokens_from_cli(reason: str) -> Optional[Dict[str, str]]:
@@ -396,7 +396,7 @@ def _refresh_codex_auth_tokens(tokens: Dict[str, str], timeout_seconds: float) -
updated_tokens = {
**tokens, "access_token": refreshed["access_token"],
"refresh_token": refreshed["refresh_token"]}
_save_codex_tokens(updated_tokens)
_save_codex_tokens(updated_tokens, write_through=True)
return updated_tokens
@@ -31,6 +31,7 @@ def _read(path: Path) -> dict:
def profile_env(tmp_path, monkeypatch):
"""Global root at tmp/.hermes, active profile at tmp/.hermes/profiles/work (real on-disk layout)."""
monkeypatch.setattr(Path, "home", lambda: tmp_path)
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path)) # Windows resolves the native root from here
root = tmp_path / ".hermes"
profile = root / "profiles" / "work"
profile.mkdir(parents=True)
@@ -49,7 +50,7 @@ def test_profile_refresh_of_root_grant_writes_through_to_root(profile_env):
_write(profile_path, {"version": 1, "providers": {}})
rotated = _pair("new")
auth._save_codex_tokens(rotated, last_refresh="2026-08-16T00:00:00Z")
auth._save_codex_tokens(rotated, last_refresh="2026-08-16T00:00:00Z", write_through=True)
root = _read(root_path)
assert root["providers"]["openai-codex"]["tokens"] == rotated
@@ -68,7 +69,15 @@ def test_profile_owned_grant_stays_local(profile_env):
_write(root_path, {"version": 1, "providers": {}})
rotated = _pair("next")
auth._save_codex_tokens(rotated, last_refresh="2026-08-16T00:00:00Z")
auth._save_codex_tokens(rotated, last_refresh="2026-08-16T00:00:00Z", write_through=True)
assert _read(profile_path)["providers"]["openai-codex"]["tokens"] == rotated
assert "openai-codex" not in _read(root_path).get("providers", {})
# A fresh login under a profile that was borrowing root's grant is the profile's own account,
# never a rewrite of root's.
_write(profile_path, {"version": 1, "providers": {}})
_write(root_path, {"version": 1, "providers": {"openai-codex": {"auth_mode": "chatgpt", "tokens": _pair("root")}}})
auth._save_codex_tokens(_pair("login"))
assert _read(profile_path)["providers"]["openai-codex"]["tokens"] == _pair("login")
assert _read(root_path)["providers"]["openai-codex"]["tokens"] == _pair("root")