Skip to content

fix(buzz): the buzz CLI subprocess env leaks the launch profile's residue - #111985

Open
EloquentBrush0x wants to merge 1 commit into
NousResearch:mainfrom
EloquentBrush0x:fix/buzz-adapter-multiplex-env-leak
Open

EloquentBrush0x wants to merge 1 commit into
NousResearch:mainfrom
EloquentBrush0x:fix/buzz-adapter-multiplex-env-leak

Conversation

@EloquentBrush0x

Copy link
Copy Markdown
Contributor

Summary

_exec_buzz — the sole spawn point for the external buzz CLI binary — built its child env with a bare os.environ.copy(). Under gateway.multiplex_profiles, one process serves several profiles and raw os.environ holds the launch profile's YAML-to-env bridge output plus its .env residue and bridged TERMINAL_* settings. A served secondary profile's buzz invocation inherited all of it, including a HERMES_HOME that pointed at the wrong profile's home.

#99427 (salvaging #98748) already fixed the read side of this class for Buzz: BuzzAdapter's own config resolution (relay/channels/credentials/allowed users) now goes through _scoped_platform_setting() so a secondary profile's PlatformConfig wins over stale env. But that PR's diff never touched _exec_buzz's env construction (confirmed via gh pr diff — the env = os.environ.copy() line is an unchanged context line in that PR) — the BUZZ_RELAY_URL/BUZZ_PRIVATE_KEY/BUZZ_AUTH_TAG values it injects were already correctly scoped by the caller, so the leak was easy to miss: every other ambient variable the launch profile's environment carries (HERMES_HOME foremost) still rode along unscoped.

The same #111617 multiplex retrofit that fixed 9 other subprocess/background-thread spawn sites this window established served_profile_child_env() for exactly this shape (a2a's forward-to-profile subprocess, browser_tool's driver spawn, etc.) — this call site was missed.

Fix

Switches _exec_buzz to served_profile_child_env(inherit_credentials=False), mirroring tools/browser_tool.py's identical external-CLI-driver pattern: the target profile's home is pinned and the launch profile's residue is dropped, while the buzz CLI's own credentials (relay_url/private_key/auth_tag) keep arriving explicitly via the function's own parameters, unchanged.

Testing

  • Added TestExecBuzzChildEnv::test_served_profile_exec_drops_launch_profile_residue: sets up a launch home with a TERMINAL_ENV bridge var, arms multiplex + a served-profile home override, captures the env passed to a monkeypatched asyncio.create_subprocess_exec, and asserts HERMES_HOME is the served profile's home (not the launch profile's), TERMINAL_ENV residue is absent, and the caller-supplied BUZZ_RELAY_URL/BUZZ_PRIVATE_KEY still win.
  • Mutation-verified: reverted just the production line, confirmed the new test fails (HERMES_HOME leaked the launch profile's path), restored the fix, confirmed the full tests/gateway/test_buzz_adapter.py suite passes (187/187).
  • Also ran tests/agent/test_subprocess_env_guard.py (2/2) to confirm no regression in the sibling guard.

🤖 Generated with Claude Code

…idue

_exec_buzz (the sole spawn point for the external `buzz` CLI binary)
built its child env with a bare os.environ.copy(). Under
gateway.multiplex_profiles, one process serves several profiles and raw
os.environ holds the LAUNCH profile's YAML-to-env bridge output plus
its .env residue and bridged TERMINAL_* settings — a served secondary
profile's buzz invocation inherited all of it, including a HERMES_HOME
that pointed at the wrong profile's home.

NousResearch#99427 (salvaging NousResearch#98748) already fixed the read side of this class for
Buzz: BuzzAdapter's own config resolution (relay/channels/credentials/
allowed users) now goes through _scoped_platform_setting() so a
secondary profile's PlatformConfig wins over stale env. But that PR's
diff never touched _exec_buzz's env construction — the BUZZ_RELAY_URL/
BUZZ_PRIVATE_KEY/BUZZ_AUTH_TAG values it injects were already correctly
scoped by the caller, so the leak was easy to miss: every OTHER
ambient variable the launch profile's environment carries (HERMES_HOME
foremost) still rode along unscoped.

The same NousResearch#111617 multiplex retrofit that fixed 9 other subprocess/
background-thread spawn sites established served_profile_child_env()
for exactly this shape (a2a's forward-to-profile subprocess,
browser_tool's driver spawn, etc.) — this call site was missed.

Switches _exec_buzz to served_profile_child_env(inherit_credentials=False),
mirroring tools/browser_tool.py's identical external-CLI-driver pattern:
the target profile's home is pinned and the launch profile's residue is
dropped, while the buzz CLI's own credentials (relay_url/private_key/
auth_tag) keep arriving explicitly via the function's own parameters.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins area/profiles Multi-profile isolation, HERMES_HOME scoping labels Sep 15, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants