From 52308ff455c50ef2c75f4097af14378362d2d5fc Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:14:07 -0700 Subject: [PATCH] fix(env): seed .op.env bootstrap into cold-profile hydration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The salvaged hydrate_profile_secret_sources (#74549) seeded its profile-local env from /.env only, but the documented 1Password bootstrap flow puts OP_SERVICE_ACCOUNT_TOKEN in the gitignored /.op.env (mirrored from load_hermes_dotenv). A cold profile using that flow still failed 1Password hydration — the one unaddressed item from the sweeper review on #74549. Seed .op.env via setdefault so .env values win; never touches os.environ. Two regression tests. --- hermes_cli/env_loader.py | 9 ++++ tests/test_env_loader_secret_sources.py | 72 +++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/hermes_cli/env_loader.py b/hermes_cli/env_loader.py index 1fd0ea99a00e1..abeea95dfc00c 100644 --- a/hermes_cli/env_loader.py +++ b/hermes_cli/env_loader.py @@ -208,6 +208,15 @@ def _hydrate_profile_secret_sources(home: Path) -> dict[str, str]: if _is_global_env(name) } local_env.update(load_env_file(home / ".env")) + # Mirror load_hermes_dotenv()'s .op.env bootstrap: the 1Password + # service-account token lives in /.op.env (gitignored), not + # .env. Without seeding it here a cold profile configured for the + # supported .op.env flow fails 1Password hydration (sweeper review + # on #74549). .env values win — never override an existing key. + op_env = home / ".op.env" + if op_env.exists(): + for _name, _value in load_env_file(op_env).items(): + local_env.setdefault(_name, _value) local_env["HERMES_HOME"] = str(home) report = apply_all(cfg, home, environ=local_env) except Exception: # noqa: BLE001 — preserve fail-open startup behavior diff --git a/tests/test_env_loader_secret_sources.py b/tests/test_env_loader_secret_sources.py index c0918f2b78a5b..303ed92268c52 100644 --- a/tests/test_env_loader_secret_sources.py +++ b/tests/test_env_loader_secret_sources.py @@ -174,6 +174,78 @@ def test_cold_profile_bitwarden_uses_profile_bootstrap_without_global_env( assert os.environ.get("ANTHROPIC_API_KEY") is None +def test_cold_profile_hydration_seeds_op_env_bootstrap(tmp_path, monkeypatch): + """The .op.env bootstrap file must feed cold-profile hydration. + + load_hermes_dotenv() reads /.op.env for OP_SERVICE_ACCOUNT_TOKEN + (the documented gitignored 1Password bootstrap); hydration must mirror + that or a cold profile using the supported .op.env flow fails 1Password + resolution (sweeper review on #74549). .env wins on conflict. + """ + monkeypatch.delenv("OP_SERVICE_ACCOUNT_TOKEN", raising=False) + (tmp_path / ".env").write_text("UNRELATED=x\n", encoding="utf-8") + (tmp_path / ".op.env").write_text( + "OP_SERVICE_ACCOUNT_TOKEN=ops_from-op-env\n", encoding="utf-8" + ) + (tmp_path / "config.yaml").write_text( + "secrets:\n" + " onepassword:\n" + " enabled: true\n", + encoding="utf-8", + ) + + from agent.secret_sources import registry as reg_module + + seen_env = {} + + def _capture_apply_all(_cfg, home_path, environ=None): + from agent.secret_sources.registry import ApplyReport + seen_env.update(environ or {}) + return ApplyReport(sources=[], provenance={}) + + monkeypatch.setattr(reg_module, "apply_all", _capture_apply_all) + reg_module._reset_registry_for_tests() + + env_loader.hydrate_profile_secret_sources(tmp_path) + + assert seen_env.get("OP_SERVICE_ACCOUNT_TOKEN") == "ops_from-op-env" + # Never leaked into the process env. + assert os.environ.get("OP_SERVICE_ACCOUNT_TOKEN") is None + + +def test_cold_profile_hydration_dotenv_wins_over_op_env(tmp_path, monkeypatch): + """.env takes precedence over .op.env for the same key (setdefault).""" + monkeypatch.delenv("OP_SERVICE_ACCOUNT_TOKEN", raising=False) + (tmp_path / ".env").write_text( + "OP_SERVICE_ACCOUNT_TOKEN=ops_from-dotenv\n", encoding="utf-8" + ) + (tmp_path / ".op.env").write_text( + "OP_SERVICE_ACCOUNT_TOKEN=ops_from-op-env\n", encoding="utf-8" + ) + (tmp_path / "config.yaml").write_text( + "secrets:\n" + " onepassword:\n" + " enabled: true\n", + encoding="utf-8", + ) + + from agent.secret_sources import registry as reg_module + + seen_env = {} + + def _capture_apply_all(_cfg, home_path, environ=None): + from agent.secret_sources.registry import ApplyReport + seen_env.update(environ or {}) + return ApplyReport(sources=[], provenance={}) + + monkeypatch.setattr(reg_module, "apply_all", _capture_apply_all) + reg_module._reset_registry_for_tests() + + env_loader.hydrate_profile_secret_sources(tmp_path) + + assert seen_env.get("OP_SERVICE_ACCOUNT_TOKEN") == "ops_from-dotenv" + + def test_apply_external_secret_sources_noop_when_disabled(tmp_path, monkeypatch): """Disabled Bitwarden config must not touch the source map."""