Skip to content

fix(gateway): hydrate cold profile secret sources 🩷 - #74549

Closed
kingrubic wants to merge 1 commit into
NousResearch:mainfrom
kingrubic:fix/74317-cold-profile-secret-source
Closed

fix(gateway): hydrate cold profile secret sources 🩷#74549
kingrubic wants to merge 1 commit into
NousResearch:mainfrom
kingrubic:fix/74317-cold-profile-secret-source

Conversation

@kingrubic

Copy link
Copy Markdown
Contributor

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

  • Add once-per-home cold-profile secret-source hydration with fail-open behavior.
  • Propagate profile-local source environments through timeout worker threads.
  • Keep .env precedence, mapped/bulk ordering, and provenance snapshots intact.
  • Prevent command, Bitwarden, and 1Password subprocesses from inheriting sibling-profile credentials.
  • Add regressions for cold bootstrap, empty scoped mappings, command-helper isolation, source adapters, and concurrent first access.

Testing

Focused/relevant gates:

  • 85 passed across multiplex credential isolation, secret-source, env-loader, command-source, and profile-scope suites.
  • An earlier broader relevant run passed 105 tests before the final Bitwarden child-environment hardening; the focused suite was rerun after that change.
  • Ruff: All checks passed!
  • git diff --check: passed.
  • Independent audit: approved after bounded context, concurrency, fail-open, and subprocess-isolation probes.

Broad-suite note: running the entire tests/gateway directory 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.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 30, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:117 seeds the new local source environment from .env only. The normal startup loader deliberately also reads <home>/.op.env after .env to provide OP_SERVICE_ACCOUNT_TOKEN for 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.env bootstrap behavior into the profile-local mapping while preserving profile-local isolation and .env precedence.
  • 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 reaches os.environ.

Automated hermes-sweeper review.

Comment thread hermes_cli/env_loader.py
for name, value in os.environ.items()
if _is_global_env(name)
}
local_env.update(load_env_file(home / ".env"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 30, 2026
teknium1 added a commit that referenced this pull request Aug 2, 2026
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.
teknium1 added a commit that referenced this pull request Aug 2, 2026
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.
teknium1 added a commit that referenced this pull request Aug 2, 2026
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.
@teknium1

teknium1 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

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!

@teknium1 teknium1 closed this Aug 2, 2026
webtecnica pushed a commit to webtecnica/hermes-agent that referenced this pull request Aug 4, 2026
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.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(secrets): cold multiplex profiles do not resolve configured external sources into their secret scope

3 participants