Repository navigation
fix(mcp): pair never replaces existing credentials; start unconfigured on keychain files - #730
Merged
Merged
Conversation
…d on keychain files totalreclaw_pair wrote ~/.totalreclaw/credentials.json without checking. The file is shared with Hermes, whose desktop keychain marker reads as "not configured" here, so a generate-mode pairing silently moved every client on the machine to a new account. The tool now refuses with already_configured when the server is configured or the file exists (both modes, no session opened), and creates the file with an exclusive open so a file that appears mid-pairing is not overwritten (relay gets nack already_configured). No replace path; that is #716. A keychain-wrapped file (v2 or the v1 marker) or an unknown version made the server exit at startup, so the host showed no tools. Those now raise UnsupportedCredentialsError; the server starts unconfigured and every tool call returns not_configured with a reason. A v2 bundle that fails validation stays fatal. No change to key derivation, pairing crypto or bundle parsing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bd3G1BkVMFSXkcdAQ7nFax
3 of 4 tasks
…egrade # Conflicts: # mcp/CHANGELOG.md
p-diogo
marked this pull request as ready for review
November 3, 2026 21:41
Collaborator
Author
|
Approved by Pedro 2026-11-03 (L3 sign-off). Merged main (CHANGELOG conflict with #729 only); MCP suite 48/48 suites, 832 passed locally. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Risk tier L3 (credentials rail). Needs Pedro's sign-off before merge. The MCP-Hermes parity plan (internal PR 669, items 0.2/0.3) rates pairing and credential handling L3. This PR only adds refusals and a softer startup failure. Key derivation, pairing crypto and bundle parsing are untouched.
What
1.
totalreclaw_pairnever replaces an existingcredentials.jsonwritePairedCredentialsoverwrote~/.totalreclaw/credentials.jsonwithout checking. Every TotalReclaw client on the machine shares that file. A Hermes desktop install keeps the phrase in the OS keychain and writes only a marker ("mnemonic": "__keychain__:v1:0x…",keychain_wrapped: true). The MCP server can't read that marker, so it reports "not configured". A generate-mode pairing then silently moved Hermes, and everything else on the machine, to a new account.already_configuredwith an explanation and opens no relay session. This applies to bothgenerateandimport. A generate pairing always makes a different account, and an import over a file this server can't read can't be checked to be the same account. If the server is configured because of the file, the server is already working, so a same-account import has nothing to do.wx, 0600). If credentials appear while the user is on the pairing page, nothing is overwritten: the relay getsnack already_configured, and the page shows its existing "Gateway could not finish pairing (already_configured)" error.replaceargument is honoured. The agent is told not to move, rename or delete the file.2. A file the server can't read no longer stops it from starting
A
version: 2file withkeychain_wrapped: true(Hermes' bundle keychain form) or an unknownversionmademain()throw. The host then showed no TotalReclaw tools, and the reason sat in a log file the user never sees.resolveManagedCredentialnow throwsUnsupportedCredentialsError(reason: keychain_wrapped | unknown_version) for those cases. It also recognises the v1 keychain marker, which used to read as "nothing configured" with no explanation.index.tscatches only that class. The server starts unconfigured, and every tool call returnsnot_configuredwithreasonand a plain message telling the agent not to calltotalreclaw_pair. Nothing in the file is used, so this is not a downgrade.version: 2bundle that failsparseBundleV1(§4.7 validation). That points to a corrupt or tampered file, and the parity plan says it stays fatal. The existingcredentials.test.tsthrow paths are unchanged.totalreclaw_pair.Relation to #716
#716 (draft, L3) ports the full Hermes design:
replace=trueopens a relay #72 replace-mode session, the user confirms on the page, and the old file is kept. This PR is a strict subset of #716's guard. It reuses #716's names (CredentialsExistError,credentialsPath/isConfigured/openSessionoptions, thealready_configuredpayload, exclusive create) so that #716 can rebase on top and add replace mode. That rebase means taking #716'swriteCredentialsFile(…, replace)and adding thereplacebranch to the guard.The startup degrade (part 2) is not in #716. The pair-wait/copy fix is separate in #729 (L2), so the rail doesn't hold it up.
Tests
mcp/tests/pair-credentials-guard.test.ts(8 tests):replace/replace_confirmedargs are sentnack already_configuredwxflag fails 5 of the 8.mcp/tests/credentials-unsupported.test.ts(10 tests): v2 keychain, v1 marker with and without the flag, unknown versions, no file content echoed, an invalid v2 bundle stays a plain fatal Error, the dispatch envelope with and without a reason.cd mcp && npm install --legacy-peer-deps && npm run build && npm test(as CI does; mcp has no lockfile): 47 suites, 821 passed, 5 skipped.node scripts/check-url-binding.cjs: OK.scripts/check-phrase-safety.shandscripts/check-phrase-safety-dist.sh: clean.Not verified: the co-located Hermes scenario on a real host (Hermes keychain install, then MCP in Claude Desktop). That is the QA step the parity plan lists for 0.2.
Not changed here, worth a follow-up
PAIR_TOOL_DESCRIPTION's "WHEN NOT TO USE: user already has TOTALRECLAW_RECOVERY_PHRASE in env → tool will return URL" is now inaccurate, because the tool refuses instead. It was left alone to avoid a conflict with fix(mcp): pair waits for the whole relay session, honest instructions #729's edit on the adjacent line. fix(mcp): pair refuses to overwrite existing credentials #716 rewrites it anyway.not_configuredmessage and the server instructions (prompts.ts) still tell users to paste the phrase into the host config. docs(mcp): Claude Desktop guide first, set up via totalreclaw_pair, phrase never in the host config #725 (docs) and feat(mcp): fit server instructions in 512 / 2,000 characters #717 (instructions) own that wording.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bd3G1BkVMFSXkcdAQ7nFax