fix(env): seed .op.env bootstrap into cold-profile hydration
The salvaged hydrate_profile_secret_sources (#74549) seeded its profile-local env from <home>/.env only, but the documented 1Password bootstrap flow puts OP_SERVICE_ACCOUNT_TOKEN in the gitignored <home>/.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.
This commit is contained in:
parent
5438e9c629
commit
52308ff455
|
|
@ -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 <home>/.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
|
||||
|
|
|
|||
|
|
@ -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 <home>/.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."""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue