Skip to content

fix(matrix): read MATRIX_RECOVERY_KEY from the active profile secret scope (#69090) - #69133

Closed
PRATHAMESH75 wants to merge 1 commit into
NousResearch:mainfrom
PRATHAMESH75:fix/matrix-recovery-key-profile-scope
Closed

fix(matrix): read MATRIX_RECOVERY_KEY from the active profile secret scope (#69090)#69133
PRATHAMESH75 wants to merge 1 commit into
NousResearch:mainfrom
PRATHAMESH75:fix/matrix-recovery-key-profile-scope

Conversation

@PRATHAMESH75

Copy link
Copy Markdown
Contributor

Problem

In a multiplexed gateway (gateway.multiplex_profiles = true), sending an encrypted Matrix message through a secondary profile fails E2EE cross-signing with:

Matrix: recovery key verification failed: Key MAC does not match

Direct API sends and hermes -p <profile> send work; only the multiplexed gateway path is affected. Fixes #69090.

Root cause

Each profile's Matrix adapter is connected inside _profile_runtime_scope (gateway/run.py:9532), which installs that profile's .env as the active secret scope so get_secret(...) resolves the routed profile's credentials. The adapter already threads the per-profile access token and homeserver through its config object, but the E2EE cross-signing bootstrap read the recovery key straight from the process environment:

recovery_key = os.getenv("MATRIX_RECOVERY_KEY", "").strip()

os.getenv ignores the profile scope, so every secondary profile verified cross-signing with the default profile's recovery key — hence the MAC mismatch.

Fix

Resolve the recovery key via get_secret, the same profile-scope-aware primitive the Slack adapter already uses:

recovery_key = (get_secret("MATRIX_RECOVERY_KEY", "") or "").strip()

get_secret reads the active profile's secret scope when one is installed (the multiplexed connect path) and falls back to os.environ when none is — so single-profile gateways are behavior-identical to the old os.getenv. The read sits inside connect()'s existing try/except, so an unscoped multiplex read (fail-closed UnscopedSecretError) degrades to "E2EE bootstrap skipped" rather than crashing.

Scope note

I intentionally left the recovery_key_configured read in get_diagnostics() (adapter.py:1740) on os.getenv. That is a non-authoritative diagnostic boolean, and get_diagnostics has no gateway caller that guarantees it runs inside a profile scope — switching it to get_secret would risk a fail-closed raise on an unscoped status read for no correctness benefit. The reported failure is entirely the connect-time verification path fixed above.

Test

tests/gateway/test_matrix.py::TestMatrixAccessTokenAuth::test_connect_reads_recovery_key_from_active_profile_scope drives the real connect() E2EE path with a secret scope holding the profile's key while os.environ holds a different (default-profile) key, and asserts OlmMachine.verify_with_recovery_key is awaited with the scoped key. It fails against the old os.getenv line and passes with the fix.

252 passed  (tests/gateway/test_matrix.py)
13 passed   (tests/agent/test_secret_scope.py)

The arm64 fork-Docker job is expected to fail on fork PRs and is unrelated to this change.

…scope (NousResearch#69090)

In a multiplexed gateway (gateway.multiplex_profiles) every profile's
Matrix adapter connects inside _profile_runtime_scope, which installs the
routed profile's .env as the active secret scope. The E2EE cross-signing
bootstrap read MATRIX_RECOVERY_KEY via os.getenv, so every secondary
profile verified with the DEFAULT profile's recovery key and failed with
'recovery key verification failed: Key MAC does not match'.

Resolve the key via get_secret(), which honors the active profile scope
and falls back to os.environ when no scope is installed (single-profile
gateways — behavior unchanged).

Fixes NousResearch#69090
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data duplicate This issue or pull request already exists labels Jul 22, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.
Duplicate of #69110: both route MATRIX_RECOVERY_KEY through profile-aware secret resolution for multiplexed Matrix E2EE. #69110 is the focused implementation; this head additionally includes an accidental adapter.py.bak copy.

@PRATHAMESH75

Copy link
Copy Markdown
Contributor Author

Closing in favor of #69110 by @sergioperezcheco, which was opened first and is the more complete fix for the same bug (#69090):

No reason to keep a narrower, partly-overlapping duplicate open. Full credit to @sergioperezcheco for #69110.

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 duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have platform/matrix Matrix adapter (E2EE) 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]: Matrix adapter uses only the default recovery key in multi-profile mode

2 participants