Keep credentials out of the MCP connection pool's cache key - #1573
Open
GeiserX wants to merge 1 commit into
Open
Keep credentials out of the MCP connection pool's cache key#1573GeiserX wants to merge 1 commit into
GeiserX wants to merge 1 commit into
Conversation
The pool key describing a remote MCP session's identity embedded the connection's resolved credential values, and the headers and query params those same secrets had already been rendered into. That key is retained as a Map key for the pool's lifetime, so the secret stayed readable in process memory long after the call that needed it, with nothing left to read it. Hash the whole serialised identity instead. Equal identities still produce equal keys, so reuse is unchanged, and a rotated token, a different rendered auth header or a query-param credential each still dial a fresh session. Hashing everything rather than the fields known to be sensitive means a field added later is covered without anyone having to remember it carries a secret. SHA-256 rather than a cheap hash on purpose: a collision would mean reusing a connection authenticated as somebody else.
This was referenced Aug 13, 2026
Author
|
Context for this one: #1585 explains why this PR and twelve others exist — they came out of a single pass over credential handling, asking for each credential where it ends up, how long it stays, and who can read it once it's there. This PR stands alone and doesn't depend on any of the others. |
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.
TL;DR
The MCP connection pool's cache key contains the connection's credentials, and that key outlives the call by a long way. The key is a
Mapkey held for the pool's lifetime, so a bearer token sits readable in process memory long after the call that needed it finished — with nothing left to read it.This hashes the key. Equal identities still produce equal keys, so pooled reuse is unchanged; a rotated token or a different auth header still dials a fresh session, so separation is unchanged. Nothing reads the key back, so nothing observable changes.
Not a leak — the key never reaches a log, span or error message. It is unnecessary retention, which is a smaller problem, but a free one to remove.
What is wrong
connectionPoolKeybuilds the identity a pooled remote MCP session is looked up by:The secret arrives by two routes, not one.
valuesis the connection's resolved credential inputs — for an OAuth connection that is the access token itself. AndbuildConnectorInputhas already rendered those same secrets ontoheaders/queryParams(Authorization: Bearer …, or?token=…for servers that authenticate that way), so they are in the key a second time.That string is then stored as a key in the pool's
Mapand kept until the pool drops the entry. The call that needed the credential is long finished; the credential is still there.What changes
The key becomes the SHA-256 digest of the same identity. One function, one call site.
The whole serialised identity is hashed rather than only the fields known to hold secrets. That is deliberate: the two credential-bearing fields today are
valuesandheaders/queryParams, but a field added later would have to be remembered to be sensitive. Hashing everything covers it by construction. Nothing is lost by making the key opaque, because nothing ever reads it back — the pool only compares it.SHA-256 rather than a cheap non-cryptographic hash is also deliberate. A collision here means reusing a connection authenticated as somebody else, so it has to be one that a user who controls their own credential values cannot aim at another key.
Why reuse and separation both still hold
These pull in opposite directions, and a change that satisfied only one of them would be worse than doing nothing — dropping the credential from the key entirely would look like a privacy improvement and be a session-hijack bug.
Tests
packages/plugins/mcp/src/sdk/connection-pool-key.test.ts— 7 tests pinning both halves: the key is a bare 64-char hex digest carrying no plaintext, the same identity is stable, and a rotated value / a different auth header / a query-param credential / a different endpoint / a different template each separate. Insertion order still does not split one identity in two.Every test was mutation-checked, with each mutation verified to have landed in the file and an unmutated control run before and after:
valuesfrom the keyheadersfrom the keyqueryParamsfrom the keyendpointfrom the keytemplatefrom the keyFull package: 133 passed, 29 skipped.
tsgo --noEmit,oxlint --deny-warningsandoxfmt --checkall clean on the changed files.