fix(matrix): honor profile secret scope for recovery key under multiplex - #69110
Conversation
The Matrix adapter read MATRIX_RECOVERY_KEY via os.getenv, so under gateway.multiplex_profiles every profile resolved the default profile's key. That produced "recovery key verification failed: Key MAC does not match" and broke E2EE for secondary profiles (NousResearch#69090). Route the read through agent.secret_scope.get_secret, which honors the active profile's scope, with an os.getenv fallback for an unscoped read under multiplex (default-profile startup loop) — mirroring the Slack app-token pattern (NousResearch#59739). Applied to both the startup verification site and the status diagnostic. Fixes NousResearch#69090
|
Thanks for the focused profile-isolation fix. The premise remains valid on current main: Matrix still reads The proposed helper matches the current secret-scope contract in No correctness or completeness defect was identified in the PR diff. Its two replacements cover every current runtime Automated hermes-sweeper review. |
|
Salvaged and merged in #76573 — your commit is on main as 153442d with your authorship, closing #69090. Clean cherry-pick, zero changes needed: get_secret with no cross-profile borrow on scoped miss, and the Slack-pattern UnscopedSecretError fallback for the default profile's startup path was exactly right (we reused it as the reference pattern for the WhatsApp salvage in the same PR). All 6 of your tests run on main. Thanks @sergioperezcheco! |
Fixes #69090.
Under gateway.multiplex_profiles, the Matrix adapter read MATRIX_RECOVERY_KEY with os.getenv, so every profile resolved the default profile's key. That caused "recovery key verification failed: Key MAC does not match" and broke E2EE for secondary profiles — direct sends and hermes -p worked fine because only the multiplexed gateway path hit this os.getenv call.
The fix routes the recovery-key read through agent.secret_scope.get_secret, which is scope-aware: a secondary profile under multiplex resolves its own key from the installed secret scope instead of falling through to os.environ. An unscoped read under multiplex (the default-profile startup loop / background reconnect) raises UnscopedSecretError, and in that context os.environ is that profile's own value, so we fall back to it — the same pattern the Slack adapter already uses for SLACK_APP_TOKEN (#59739).
This is a credential-read bug, so the secret scope (not os.environ) must be authoritative: a secondary profile that hasn't set a recovery key returns empty rather than silently borrowing the default profile's key (which is what produced the confusing MAC mismatch).
Applied to both the startup verification site and the status diagnostic so they stay consistent. Added a regression test covering all four scopes: inactive/env, active+scoped (uses scope not env), active+unscoped (falls back to env), and active+scoped-without-key (returns empty, no cross-profile leak). The remaining os.getenv reads in the adapter are non-credential config knobs (E2EE mode, allowed rooms, batch delays) so they're intentionally untouched.
Verified the three resolution paths behave correctly and that get_secret's scope-isolation semantics hold. The 6 new tests pass; existing secret_scope tests (13) still pass.