openclaw - 💡(How to fix) Fix Matrix: cross-signing bootstrap fails on MAS-fronted Synapse (MSC3967) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#74504Fetched 2026-04-30 06:23:27
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
2
Author
Timeline (top)
referenced ×3commented ×1cross-referenced ×1

OpenClaw 2026.4.26's Matrix channel cannot complete cross-signing key publication on Synapse homeservers fronted by Matrix Authentication Service (MSC3861 / MAS, used by ESS) when the bot logs in with a Personal Access Token. The bootstrap loop emits a misleading error (Matrix cross-signing key upload requires UIA; provide matrix.password for m.login.password fallback) and terminates with cross-signing unpublished, which in turn breaks E2EE message decryption and self-verification flows.

The MAS auth provider exposes a single UIA stage on /keys/device_signing/uploadorg.matrix.cross_signing_reset — that is intentionally unsatisfiable by non-interactive clients (the satisfaction mechanism is a browser redirect to MAS, see Synapse PR #17509). OpenClaw's UIA callback only knows three response shapes (null, m.login.dummy, m.login.password), so it cannot route around the MAS stage and cannot surface the recovery URL that MAS emits in the challenge.

OpenClaw also doesn't take advantage of MSC3967 (stable in Synapse 1.79+, including ESS 1.x), which lets the very first /keys/device_signing/upload for a user bypass UIA entirely when no master key exists yet on the server.

Error Message

[MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401] [MatrixClientLite] Initial cross-signing bootstrap failed, trying reset: Error: Error while importing m.cross_signing.master: The public key of the imported private key doesn't match the public key that was uploaded to the server. [MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401] [MatrixClientLite] Failed to bootstrap cross-signing: Error: Matrix cross-signing key upload requires UIA; provide matrix.password for m.login.password fallback.

Root Cause

This is the load-bearing failure for any bot deployment on an ESS / MAS-fronted homeserver. matrix-commander, Element X, and other bot projects route around it (or have human operators in the loop). OpenClaw's bot-only design hits this wall on first deploy and has no escape hatch — the operator gets a "configure matrix.password" suggestion that isn't actionable.

Fix Action

Fix / Workaround

Workarounds (not great)

Code Example

[MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401]
   [MatrixClientLite] Initial cross-signing bootstrap failed, trying reset:
     Error: Error while importing m.cross_signing.master:
       The public key of the imported private key doesn't match the public key
       that was uploaded to the server.
   [MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401]
   [MatrixClientLite] Failed to bootstrap cross-signing:
     Error: Matrix cross-signing key upload requires UIA;
       provide matrix.password for m.login.password fallback.

---

{
     "flows": [{ "stages": ["org.matrix.cross_signing_reset"] }],
     "session": "<MAS session token>",
     "params": {
       "org.matrix.cross_signing_reset": {
         "url": "https://mas.<homeserver>/account/cross_signing_reset?token=…"
       }
     }
   }
RAW_BUFFERClick to expand / collapse

Bug: cross-signing bootstrap fails on MAS-fronted Synapse with PAT-authenticated bot accounts

Summary

OpenClaw 2026.4.26's Matrix channel cannot complete cross-signing key publication on Synapse homeservers fronted by Matrix Authentication Service (MSC3861 / MAS, used by ESS) when the bot logs in with a Personal Access Token. The bootstrap loop emits a misleading error (Matrix cross-signing key upload requires UIA; provide matrix.password for m.login.password fallback) and terminates with cross-signing unpublished, which in turn breaks E2EE message decryption and self-verification flows.

The MAS auth provider exposes a single UIA stage on /keys/device_signing/uploadorg.matrix.cross_signing_reset — that is intentionally unsatisfiable by non-interactive clients (the satisfaction mechanism is a browser redirect to MAS, see Synapse PR #17509). OpenClaw's UIA callback only knows three response shapes (null, m.login.dummy, m.login.password), so it cannot route around the MAS stage and cannot surface the recovery URL that MAS emits in the challenge.

OpenClaw also doesn't take advantage of MSC3967 (stable in Synapse 1.79+, including ESS 1.x), which lets the very first /keys/device_signing/upload for a user bypass UIA entirely when no master key exists yet on the server.

Environment

OpenClaw version2026.4.26
@openclaw/matrix2026.4.25
matrix-js-sdk41.4.0-rc.0
matrix-sdk-crypto-nodejs0.5.1
matrix-sdk-crypto-wasm18.2.0
Node22.x
HomeserverSynapse 1.145.0+ess.1 (ESS, MAS-fronted)
AuthPersonal Access Token issued by MAS
Deploy targetk3s on Linux (image ghcr.io/openclaw/openclaw:2026.4.26)

Reproduction

  1. Deploy OpenClaw with channels.matrix.<account>.userId and MATRIX_ACCESS_TOKEN set to a MAS PAT for a bot account that has some prior cross-signing state on the homeserver but whose local secret-storage private keys do not match the server's master public key (any partial earlier bootstrap attempt produces this state). The same trigger occurs cleanly on a brand-new bot if any other cross-signing setup ever happened for that user.
  2. Run openclaw matrix verify bootstrap.
  3. Observe in the gateway/bot log:
    [MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401]
    [MatrixClientLite] Initial cross-signing bootstrap failed, trying reset:
      Error: Error while importing m.cross_signing.master:
        The public key of the imported private key doesn't match the public key
        that was uploaded to the server.
    [MatrixClient] FetchHttpApi: <-- POST .../keys/device_signing/upload [... 401]
    [MatrixClientLite] Failed to bootstrap cross-signing:
      Error: Matrix cross-signing key upload requires UIA;
        provide matrix.password for m.login.password fallback.
    The 401 response body is shaped:
    {
      "flows": [{ "stages": ["org.matrix.cross_signing_reset"] }],
      "session": "<MAS session token>",
      "params": {
        "org.matrix.cross_signing_reset": {
          "url": "https://mas.<homeserver>/account/cross_signing_reset?token=…"
        }
      }
    }
  4. openclaw matrix verify status reports Cross-signing published: no, Backup: missing on server.
  5. Subsequent encrypted-message receives produce Failed to decrypt a room event (Megolm) because the bot has no published cross-signed device.

Expected behaviour

  1. First-publish (MSC3967) path on MAS: when the user has no server-side master key, the bot's first /keys/device_signing/upload should be sent with no auth and Synapse will accept it. OpenClaw's bootstrap should detect this case and not pre-emptively force a setupNewCrossSigning: true upload that confuses MSC3967 detection.
  2. MAS-required reset: when the server already has a master key and OpenClaw's local secret storage doesn't match it, OpenClaw should not blindly attempt a reset upload (which will 401 on MAS). It should surface a structured error containing the MAS reset URL so the operator can complete the OAuth re-auth in a browser, then re-run the bootstrap inside the MAS approval window.
  3. Misleading password message: when the homeserver advertises org.matrix.cross_signing_reset in the UIA flows, asking the operator to "provide matrix.password for m.login.password fallback" is wrong — m.login.password is not advertised, the user does not have a password (PAT-only on MAS), and even setting one would not satisfy the MAS stage.

Actual behaviour

The current createSigningKeysUiAuthCallback() in extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts:99-124 only attempts:

  1. makeRequest(null) → caught
  2. makeRequest({ type: "m.login.dummy" }) → caught
  3. makeRequest({ type: "m.login.password", … }) if password is set → otherwise throws the misleading error

The callback does not inspect the 401 response body at all, so it cannot:

  • detect that the homeserver advertises org.matrix.cross_signing_reset;
  • propagate the MAS reset URL to the operator;
  • pass the UIA session token through to subsequent attempts (matrix-js-sdk's UIA contract requires this for the password and dummy stages too — current code omits it);
  • distinguish a 401-with-UIA from any other failure (it catches and re-tries blindly).

The bootstrapCrossSigning() flow at extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts:126-320 then escalates a key-mismatch failure to a setupNewCrossSigning: true retry, which on MAS deterministically 401s on the same stage, producing two confusing UIA failures back-to-back in the log.

Why this matters

This is the load-bearing failure for any bot deployment on an ESS / MAS-fronted homeserver. matrix-commander, Element X, and other bot projects route around it (or have human operators in the loop). OpenClaw's bot-only design hits this wall on first deploy and has no escape hatch — the operator gets a "configure matrix.password" suggestion that isn't actionable.

Suggested fix

Three coordinated changes in extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts, plus type extensions and a homeserver-capability probe in sdk.ts. PR with implementation and regression tests is filed separately.

  1. Make the UIA callback inspect the 401 response body, route by advertised stage (m.login.dummy → no-arg, m.login.password → identifier+password, org.matrix.cross_signing_reset → throw structured error), and pass the session token through.
  2. Probe /_matrix/client/v1/auth_metadata once to detect MAS (the endpoint returns 200 with an OAuth issuer when MSC3861 is enabled, 404 otherwise; the older unstable_features["org.matrix.msc3861"] flag is no longer surfaced by Synapse 1.145+/ESS), cache the result and the MSC2965 account_management_uri for URL construction.
  3. Always attempt the /keys/device_signing/upload rather than short-circuiting on MAS. The 401 response is the in-band signal — the UIA callback turns it into MatrixCrossSigningResetRequiredError with the MAS reset URL. When the operator has just approved the cross-signing reset action in MAS via a browser, the same upload returns 200 within the approval window and lands cross-signing. A short-circuit would block this recovery flow; treating the upload attempt as the "do it / tell me what to do" probe handles both cases with one code path.

Workarounds (not great)

  • Manually PUT /_matrix/client/v3/user/<user>/account_data/m.cross_signing.{master,self_signing,user_signing} to {} — does not clear server-side cross-signing public keys (those live in the keys table queried via /keys/query), so this alone doesn't unblock the bot.
  • Use Synapse's admin API (POST /_synapse/admin/v1/users/<user>/_reset_cross_signing_keys) to actually clear server-side cross-signing keys, then have the bot do a clean MSC3967 first-publish. Requires homeserver admin.
  • Visit the MAS reset URL in a browser and approve, then re-run openclaw matrix verify bootstrap within the MAS approval window. Currently the bot does not surface this URL anywhere — operator has to capture it from a manual curl of /keys/device_signing/upload.

References

extent analysis

TL;DR

The most likely fix involves modifying the UIA callback in crypto-bootstrap.ts to inspect the 401 response body, route by advertised stage, and pass the session token through, allowing the operator to complete the OAuth re-auth in a browser.

Guidance

  1. Inspect 401 response body: Modify the UIA callback to check the response body for advertised stages, such as org.matrix.cross_signing_reset, and handle them accordingly.
  2. Route by advertised stage: Update the UIA callback to route requests based on the advertised stage, passing the session token through to subsequent attempts.
  3. Surface MAS reset URL: Modify the UIA callback to surface the MAS reset URL when the org.matrix.cross_signing_reset stage is encountered, allowing the operator to complete the OAuth re-auth.
  4. Probe homeserver capabilities: Probe the homeserver's capabilities, such as MSC3861, to determine the correct course of action for cross-signing key publication.

Example

// Example of modified UIA callback
const createSigningKeysUiAuthCallback = async (request: any) => {
  // ...
  if (response.statusCode === 401) {
    const responseBody = await response.json();
    if (responseBody.flows && responseBody.flows[0].stages.includes('org.matrix.cross_signing_reset')) {
      // Surface MAS reset URL
      throw new MatrixCrossSigningResetRequiredError(responseBody.params['org.matrix.cross_signing_reset'].url);
    }
  }
  // ...
};

Notes

The provided solution assumes that the MatrixCrossSigningResetRequiredError class is defined and handled properly in the codebase. Additionally, the example code snippet is a simplified representation of the necessary changes and may require further modifications to fit the specific use case.

Recommendation

Apply the suggested fix, which involves modifying the UIA callback to inspect the 401 response body

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Matrix: cross-signing bootstrap fails on MAS-fronted Synapse (MSC3967) [1 comments, 2 participants]