Skip to content

fix(gateway): multiplex credential isolation — authz, Slack, WeChat, secondary adapters (4-PR cluster salvage) - #65629

Merged
teknium1 merged 7 commits into
mainfrom
salvage/multiplex-credential-cluster
Jul 16, 2026
Merged

fix(gateway): multiplex credential isolation — authz, Slack, WeChat, secondary adapters (4-PR cluster salvage)#65629
teknium1 merged 7 commits into
mainfrom
salvage/multiplex-credential-cluster

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Consolidated salvage of the open multiplex credential-isolation cluster: under gateway.multiplex_profiles, authorization allowlists, the Slack Socket Mode app token, WeChat credentials, and secondary-profile adapter creation now all resolve through the routed profile's secret scope instead of leaking the default profile's process env. Closes #59739, closes #59662.

Cherry-picks four contributor commits (authorship preserved, rebase merge) plus hardening follow-ups.

Salvaged work

Follow-ups on top

Not salvaged (redundant on current main)

Validation

Check Result
slack/HA/mattermost/signal/multiplex-registry suites 510 passed
tests/gateway/ -k "multiplex or authz or webhook or weixin or slack or signal or mattermost or homeassistant" 1,408 passed, 0 failed
E2E (temp HERMES_HOME) scoped authz reads win over env; unscoped multiplex falls back without raising; weixin bare-getenv reads gone; HA/signal validate_config accepts scoped-config creds, rejects none
ruff clean

Known residual gaps (follow-up candidates, flagged in review): _get_unauthorized_dm_behavior allowlist probes still on os.getenv; _make_adapter_auth_check not profile-stamped (open #61985 covers this); webhook first-match ambiguity when two profiles run the same platform.

Infographic

multiplex-credential-isolation

aguung and others added 6 commits July 16, 2026 05:14
Secondary profiles under gateway multiplex keep tokens/allowlists in
profile secret_scope, not process os.environ. Auth and Slack were still
reading os.getenv, so Slack on a secondary profile failed allowlist and
socket mode. Webhook deliver also only looked at default adapters.

- Prefer get_secret for allowlists / allow-all flags (authz_mixin)
- Slack app token + allowlist via secret_scope with getenv fallback
- Wrap secondary profile message handlers in _profile_runtime_scope
  before auth runs
- Resolve home-channel env from secret_scope / PlatformConfig
- Webhook deliver falls back to _profile_adapters for target platform
- Template key event_type for webhook prompts
…for consistent profile-scoped resolution

Per egilewski's security review, WEIXIN_BASE_URL and WEIXIN_CDN_BASE_URL
were still resolved from process-global environment variables, leaving
mixed-scope bypasses in multiplex mode.

Changed files:
- gateway/platforms/weixin.py: Added get_secret import, replaced os.getenv()
  with get_secret() for WEIXIN_ACCOUNT_ID, WEIXIN_TOKEN, WEIXIN_BASE_URL,
  WEIXIN_CDN_BASE_URL in WeixinAdapter.__init__() and send_weixin_direct()
- tools/send_message_tool.py: Added get_secret import, replaced os.getenv()
  with get_secret() for all WEIXIN_* fallbacks in _handle_send()

All runtime Weixin send paths now resolve both credentials and endpoint
configuration from the same profile-scoped source.
Partial cherry-pick of a7ffbbf from PR #63256: secondary-profile
adapter creation errors no longer abort the whole secondary startup
(try/except around _create_adapter + loud warning on None return), and
Home Assistant's check_ha_requirements() becomes dep-only with the
credential moved to a new validate_ha_config() so secondary profiles
whose HASS_TOKEN lives in the profile secret scope are not silently
dropped by the registry gate.

Telegram diagnostic hunks and profile-label stamping dropped: the
regression they targeted does not exist on current main and they
conflict with the connect() teardown fence.
Follow-ups on top of the cherry-picked cluster commits:

- slack: scope-authoritative app-token read — get_secret() with a
  narrow UnscopedSecretError fallback to os.getenv. Keeps @kohoj's
  correct semantics (scoped profile can never silently inherit the
  default profile's Socket Mode app) while fixing the regression where
  the default-profile startup loop and background reconnect rebuild,
  which call connect() unscoped under multiplex, would raise and
  fail-loop. Supersedes the 'or os.getenv' variant from #64461 which
  reintroduced the cross-profile fallback leak.
- test: unscoped-multiplex fallback regression test for connect().
- run.py: convert the last legacy self.adapters.get(source.platform)
  site (_rename_discord_auto_thread) to _adapter_for_source(source)
  so profile-routed Discord sources rename threads on the right
  adapter (from #57417's sweep).
- AUTHOR_MAP entry for @aguung.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets platform/signal Signal CLI adapter platform/slack Slack app adapter platform/webhook Webhook / API server platform/wecom WeCom / WeChat Work adapter area/auth Authentication, OAuth, credential pools 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 labels Jul 16, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Comment

Scope

  • 14 files, +396/-50 lines. Multiplex credential isolation for authz, Slack, WeChat, and secondary adapters.

Quality

  • Significant security-relevant change (credential isolation).
  • Part of a 4-PR cluster salvage.

Notes

  • Large surface area for a single PR (14 files).
  • Credential multiplexing and authz changes require careful security review.

Looks Good

  • Well-scoped fix for a complex multi-adapter scenario.

Reviewed by Hermes Agent

teknium1 pushed a commit that referenced this pull request Jul 16, 2026
Subset of PR #61985: _make_adapter_auth_check gains a profile_name
parameter and secondary-profile adapters (started in
_start_one_profile_adapters) bind it, so the auth callback's
SessionSource resolves the routed profile's adapter and pairing store
instead of silently falling back to the default profile. This is the
gap left open by the #65629 merge — adapter-internal auth checks (e.g.
Slack thread-context fetch) fire outside the wrapped message handler.

The PR's authz_mixin.py hunks are dropped: main's _auth_env (merged via
PR #65629) already covers the scoped allowlist reads they targeted.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
Subset of PR NousResearch#61985: _make_adapter_auth_check gains a profile_name
parameter and secondary-profile adapters (started in
_start_one_profile_adapters) bind it, so the auth callback's
SessionSource resolves the routed profile's adapter and pairing store
instead of silently falling back to the default profile. This is the
gap left open by the NousResearch#65629 merge — adapter-internal auth checks (e.g.
Slack thread-context fetch) fire outside the wrapped message handler.

The PR's authz_mixin.py hunks are dropped: main's _auth_env (merged via
PR NousResearch#65629) already covers the scoped allowlist reads they targeted.
yournetworkplug-ctrl added a commit to yournetworkplug-ctrl/hermes-agent that referenced this pull request Jul 29, 2026
… multiplex_profiles

Replace raw os.getenv("TELEGRAM_ALLOWED_USERS") reads in the Telegram
adapter's pre-filter with gateway.authz_mixin._auth_env so the served
profile's .env is consulted when its secret scope is installed. This
mirrors the gateway-layer fix in PRs NousResearch#61985/NousResearch#65629/NousResearch#65700 down to the
adapter pre-filter, fixing the Telegram mirror of issue NousResearch#72348.

See README.md for details, scope notes, and end-to-end test evidence.

Fixes NousResearch#72348 (Telegram mirror)
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Subset of PR NousResearch#61985: _make_adapter_auth_check gains a profile_name
parameter and secondary-profile adapters (started in
_start_one_profile_adapters) bind it, so the auth callback's
SessionSource resolves the routed profile's adapter and pairing store
instead of silently falling back to the default profile. This is the
gap left open by the NousResearch#65629 merge — adapter-internal auth checks (e.g.
Slack thread-context fetch) fire outside the wrapped message handler.

The PR's authz_mixin.py hunks are dropped: main's _auth_env (merged via
PR NousResearch#65629) already covers the scoped allowlist reads they targeted.
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 comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists platform/signal Signal CLI adapter platform/slack Slack app adapter platform/webhook Webhook / API server platform/wecom WeCom / WeChat Work adapter 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/security Security vulnerability or hardening

Projects

None yet

7 participants