fix(gateway): hydrate cold profile secret sources 🩷 - #74549
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the verified cold-secondary hydration gap: current main constructs the multiplex profile scope before that home's external-source snapshot exists (gateway/run.py:1803-1804; agent/secret_scope.py:222-233).
Problems
hermes_cli/env_loader.py:117seeds the new local source environment from.envonly. The normal startup loader deliberately also reads<home>/.op.envafter.envto provideOP_SERVICE_ACCOUNT_TOKENfor 1Password resolution (hermes_cli/env_loader.py:324-336; regression:tests/test_env_loader_op_bootstrap.py:54-69). A cold secondary profile using that supported bootstrap file therefore still fails to hydrate its 1Password-backed key.
Suggested changes
- Mirror the
.op.envbootstrap behavior into the profile-local mapping while preserving profile-local isolation and.envprecedence. - Add a cold gateway-profile regression where the 1Password token exists only in that profile's
.op.env, and assert no token or resolved key reachesos.environ.
Automated hermes-sweeper review.
| for name, value in os.environ.items() | ||
| if _is_global_env(name) | ||
| } | ||
| local_env.update(load_env_file(home / ".env")) |
There was a problem hiding this comment.
This local mapping omits <profile>/.op.env. Normal startup loads that file after .env specifically to supply OP_SERVICE_ACCOUNT_TOKEN to the 1Password source (load_hermes_dotenv() at current main hermes_cli/env_loader.py:324-336). Please mirror that bootstrap behavior in the profile-local mapping and add a cold-profile regression; otherwise a supported 1Password setup still fails its first multiplex turn.
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.
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.
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.
|
Salvaged and merged in #76573 — your commit is on main as 6ab390a with your authorship, closing #74317. Your fix was the right layer exactly as the issue prescribed: hydrate the profile's OWN sources inside _profile_runtime_scope before scope build, never mutate os.environ, no root inheritance. We added one follow-up commit (52308ff) for the sweeper-flagged gap: seeding the /.op.env bootstrap (OP_SERVICE_ACCOUNT_TOKEN) into the profile-local env so cold profiles on the documented 1Password flow hydrate too, with .env winning on conflict. Preferred over the competing #75045/#75263 per the issue triage. Thanks @kingrubic! |
The salvaged hydrate_profile_secret_sources (NousResearch#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 NousResearch#74549. Seed .op.env via setdefault so .env values win; never touches os.environ. Two regression tests.
The salvaged hydrate_profile_secret_sources (NousResearch#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 NousResearch#74549. Seed .op.env via setdefault so .env values win; never touches os.environ. Two regression tests.
Summary
Cold multiplex profiles now hydrate their configured external secret sources before the profile runtime scope is built.
The resolver uses a profile-local environment and never writes profile credentials into process-global
os.environ. Bitwarden, 1Password, and command helpers receive that scoped environment while preserving the existing single-profile behavior.Fixes #74317.
Changes
.envprecedence, mapped/bulk ordering, and provenance snapshots intact.Testing
Focused/relevant gates:
85 passedacross multiplex credential isolation, secret-source, env-loader, command-source, and profile-scope suites.105tests before the final Bitwarden child-environment hardening; the focused suite was rerun after that change.All checks passed!git diff --check: passed.Broad-suite note: running the entire
tests/gatewaydirectory as one aggregate invocation produced widespread cross-module failures/errors unrelated to this change (2233 passed, 313 failed, 1908 errors). Representative reported failures passed when rerun individually. No changed-area focused test failed.Security impact
Positive: strengthens credential isolation between multiplex profiles. Scoped source resolution and child processes do not inherit unrelated process-global credentials.
Compatibility
No intentional breaking change. Existing single-profile source behavior is preserved.