Blog • Agent Security

Nachdem ein MCP-Client OAuth-Zugangsdaten an den falschen Autorisierungsserver gesendet hat: bei welchem JSON-Protokolleintrag soll der Tool-Aufruf stoppen?

Am 30. September 2026 veröffentlichte das MCP-TypeScript-SDK GHSA-6qxp-vccf-f47h, Schwere High, CVSS 7.5. In betroffenen Versionen band der OAuth-Client gespeicherte Zugangsdaten nicht an den Autorisierungsserver, zu dem sie gehören. Ein MCP-Server in der Verbindung konnte einen anderen Autorisierungsserver nennen, und der Client sandte die Zugangsdaten ohne Nutzeraktion. Was nach der Meldung bleibt, ist die JSON-Zeile, die den Tool-Aufruf stoppt, keine wiederholbare Anfrage.

Wer MCP-Rechte trägt, steht in dem Artikel zur MCP-Sicherheit. Der Rechteentscheid nach bestandener Schema-Prüfung steht in dem vorherigen Artikel. Dieser Artikel beginnt beim Protokoll nach der Meldung: besteht credential_check nicht, darf der spätere Tool-Aufruf nicht dastehen.

Die Entscheidung: unterscheiden sich die Issuer, stoppen Sie bei credential_check. decision ist deny, credentialsSent ist false, und kein Tool-Aufruf folgt.

Was steht in der ersten Protokollzeile nach der Meldung?

Betroffen sind Apps, die das SDK als OAuth-Client über HTTP nutzen, sich mit einem MCP-Server verbinden können, dem sie nicht voll vertrauen, und Zugangsdaten für einen legitimen Autorisierungsserver halten. Einstiege sind ein authProvider am Transport, die withOAuth-Middleware und direkte Aufrufe von auth oder fetchToken. Mit diesem SDK gebaute MCP-Server und stdio-Clients liegen außerhalb. Bei 1.x sind 1.12.0 bis 1.30.1 in 1.31.0 behoben. Bei 2.x ist Client 2.0.0 bis 2.1.0 nur betroffen für gebündelte Provider ohne expectedIssuer, gespeicherte Zugangsdaten ohne issuer, direkte fetchToken-Aufrufe oder Provider, die Speicher über OAuthTokensSchema oder OAuthClientInformationSchema zurücklesen. Behoben in 2.2.0. Importieren Sie core direkt, heben Sie es ebenfalls auf 2.2.0. Die Meldung ist GHSA-6qxp-vccf-f47h.

Der Stopp, wenn die Issuer sich unterscheiden. Beide Adressen sind Platzhalter, keine Server zum Verbinden.
{
  "step": "credential_check",
  "savedIssuer": "https://auth.example.com",
  "namedAuthorizationServer": "https://other.example.net",
  "issuerMatch": false,
  "credentialsSent": false,
  "decision": "deny",
  "reason": "issuer_mismatch"
}

Diese Zeile hält nur den Stopp fest. savedIssuer ist der Autorisierungsserver, zu dem die Zugangsdaten gehören. namedAuthorizationServer ist der, den dieser MCP-Server nennt. Unterscheiden sich die beiden Zeichenketten, ist issuerMatch false. decision ist deny, credentialsSent ist false. Legen Sie kein Token und kein Client-Secret in die Felder. Verwundbarkeitslisten führen dasselbe Problem als CVE-2026-104850. Dieser Artikel folgt den Versionen und der Korrektur in der GitHub-Meldung.

Welches Schema legt die Form dieser JSON-Zeile fest?

Nutzen Sie JSON Schema, um Enums und Pflichtfelder festzulegen. Es beweist, dass dies ein credential_check ist. decision darf nur allow, deny oder ask sein. reason darf nur issuer_mismatch, issuer_match oder awaiting_user sein. Es beweist nicht, dass dieser Schritt stoppen soll.

Die Form des Stopps. issuerMatch false und decision allow bestehen diese Enums trotzdem.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["step", "savedIssuer", "namedAuthorizationServer", "issuerMatch", "credentialsSent", "decision", "reason"],
  "properties": {
    "step": { "enum": ["credential_check"] },
    "savedIssuer": { "type": "string" },
    "namedAuthorizationServer": { "type": "string" },
    "issuerMatch": { "type": "boolean" },
    "credentialsSent": { "type": "boolean" },
    "decision": { "enum": ["allow", "deny", "ask"] },
    "reason": { "enum": ["issuer_mismatch", "issuer_match", "awaiting_user"] }
  }
}

Schema-Bestehen heißt nur, dass die Felder in den Enums liegen. issuerMatch false, credentialsSent true und decision allow ist eine gültige Form. Was das Senden stoppt, ist die App-Regel im nächsten Abschnitt, nicht diese Formprüfung.

Welche drei Regeln stoppen den Schritt trotzdem, wenn die Form stimmt?

Die Korrektur in der Meldung: der Client speichert den Autorisierungsserver als issuer an den Zugangsdaten und sendet sie nicht an einen anderen. Der Nutzer meldet sich neu an, oder der Aufruf schlägt fehl. Der Vergleich liegt außerhalb des Modells. Was das Modell später sagt, kann einen schon als deny notierten Schritt nicht in ein Senden verwandeln.

Was der Eintrag zeigtSchemaApp-Regel
issuerMatch ist false, credentialsSent aber trueBesteht. true ist ein Boolean.Setzen Sie es auf false zurück. Senden Sie weder refresh_token noch client_secret noch eine signierte Assertion.
issuerMatch ist false, decision aber allowBesteht. allow steht im Enum.Ändern Sie es zu deny und lassen Sie reason auf issuer_mismatch.
credential_check ist schon deny, und danach folgt eine tool_call-ZeileEin einzelnes Schema sieht die nächste Zeile nicht. step auf tool_call zu setzen lässt diesen Eintrag selbst scheitern.Das Protokoll endet bei credential_check. Hängen Sie keinen Tool-Aufruf an. Ein späterer Aufruf, dessen Issuer schon übereinstimmen, bekommt einen eigenen Rechteentscheid.

Welche drei Stellen schreiben Sie nach dem Versionsupgrade noch von Hand?

Heben Sie 1.x auf @modelcontextprotocol/sdk 1.31.0 oder höher. Heben Sie 2.x auf @modelcontextprotocol/client 2.2.0 oder höher. Das Paket-Upgrade erledigt die drei Zeilen unten nicht. Die Quelle bleibt dieselbe Meldung.

Was noch bleibtWas die Meldung verlangt
Gebündelte ProviderÜbergeben Sie expectedIssuer an ClientCredentialsProvider, PrivateKeyJwtProvider, StaticPrivateKeyJwtProvider und CrossAppAccessProvider, zum Beispiel https://auth.example.com. Ohne den Wert nutzen sie weiter den Autorisierungsserver, den der MCP-Server nennt.
Gespeicherte Daten ohne issuerToken und Client-Informationen, die vor dem Upgrade in Datei, Schlüsselbund oder Datenbank lagen, gehen beim ersten Gebrauch weiter an den dann benannten Autorisierungsserver. Ergänzen Sie issuer, oder löschen Sie sie, damit der Nutzer sich neu anmeldet. Ein eigener OAuthClientProvider speichert genau das, was saveTokens und saveClientInformation übergeben, einschließlich issuer. Vorregistrierte Zugangsdaten enthalten issuer in dem, was clientInformation zurückgibt.
Eine neue interaktive AnmeldungDiese Korrektur deckt eine neue interaktive Anmeldung nicht ab. Sie geht weiter zum Autorisierungsserver, den der MCP-Server nennt. Schließen Sie sie nur für Server ab, denen Sie vertrauen. Kann der Client sich mit einem nicht vertrauenswürdigen MCP-Server verbunden haben, rotieren Sie das Client-Secret oder den Signaturschlüssel und widerrufen Sie die Token. Können Sie noch nicht upgraden, verbinden Sie OAuth-Clients nur mit MCP-Servern, denen Sie vertrauen. 2.0.0 und 2.1.0 akzeptieren expectedIssuer bereits.

Direkte Aufrufe von refreshAuthorization oder exchangeAuthorization liegen außerhalb dieser Korrektur. Ebenso 2.x mit skipIssuerMetadataValidation auf true. Überlassen Sie keinen der beiden Fälle dem Modell, das entscheiden würde, ob Zugangsdaten gesendet werden.

Worin unterscheidet sich das von den beiden früheren Artikeln?

Warum ein Schema nötig istMCP-SicherheitDieser Artikel
AntwortWarum Ausgabe eine Form brauchtWer die Rechtegrenze halten mussBei unterschiedlichen Issuern: welcher Protokollschritt den Tool-Aufruf stoppt
Schreibt nichtNicht den Stopp dieser OAuth-MeldungNicht die Protokollreihenfolge nach der MeldungKeine wiederholbare Anfrage, und nicht noch einmal, ob ein Schema reicht

Wie prüfen Sie diesen Stopp in JSONNote?

  1. 1
    Die richtige Operation wählen

    Öffnen Sie JSON Schema. Stellen Sie Betrieb auf Überprüfen Sie mit Schema. Den Stopp oben ins erste Feld, das Schema ins zweite. step darf nur credential_check sein. decision nur allow, deny oder ask.

  2. 2
    Vier ungültige Dokumente prüfen

    Setzen Sie decision auf yes, credentialsSent auf die Zeichenkette false, löschen Sie decision, oder setzen Sie step auf tool_call. Alle vier scheitern. Der deny-Eintrag oben besteht. Bestehen heißt nur richtige Form.

  3. 3
    deny mit dem formgültigen allow vergleichen

    Öffnen Sie JSON Vergleiche Diff. Den deny-Stopp ins erste Feld. Im zweiten nur credentialsSent auf true und decision auf allow ändern. reason bleibt issuer_mismatch. Beide bestehen das Schema. Die App-Regel stoppt das Senden: unterscheiden sich die Issuer, muss der Entscheid deny sein, und es wird nichts gesendet.

Häufige Fragen

Muss ein stdio-Client diese Protokollzeile schreiben?

Nein. Die Meldung sagt, stdio-Clients und mit diesem SDK gebaute MCP-Server liegen außerhalb. Die Zeile credential_check ist für einen OAuth-Client über HTTP, der sich mit einem MCP-Server verbinden kann, dem Sie nicht voll vertrauen, und der Zugangsdaten für einen legitimen Autorisierungsserver hält.

Reicht das Upgrade auf 1.31.0 oder 2.2.0?

Nein. Gebündelte Provider brauchen expectedIssuer. Zugangsdaten ohne issuer brauchen das Feld oder müssen gelöscht werden. Eine neue interaktive Anmeldung schließen Sie weiter nur für Server ab, denen Sie vertrauen. Direkte Aufrufe von refreshAuthorization oder exchangeAuthorization und skipIssuerMetadataValidation auf true liegen außerhalb dieser Korrektur.

Darf der Tool-Aufruf laufen, sobald die Issuer übereinstimmen?

Nicht allein deshalb. Übereinstimmende Issuer bedeuten nur, dass die Zugangsdaten nicht an einen anderen Autorisierungsserver gingen. Der Tool-Aufruf braucht noch einen eigenen Rechteentscheid: Steht das Tool auf der Allowlist, und enthält dieser Grant die Fähigkeit? Das ist der vorherige Artikel. Dieser schreibt diesen Aufruf nach einem deny nicht.

Ist das derselbe Artikel wie der zur MCP-Sicherheit?

Nein. Jener Artikel schreibt, wer die Rechtegrenze halten muss. Dieser schreibt, welcher JSON-Protokollschritt nach der Meldung vom 30. September 2026 den Tool-Aufruf stoppt. Beide führen zu den Werkzeugen, der Schnitt ist ein anderer.

Soll das Protokoll das refresh_token speichern?

Nein. Der Stopp behält nur die beiden Issuer-Zeichenketten, ob sie übereinstimmen, ob etwas gesendet wurde, decision und reason. Token und Client-Secret kommen nicht in dieses JSON. Kann der Client sich mit einem nicht vertrauenswürdigen Server verbunden haben, rotieren Sie den Schlüssel und widerrufen Sie die Token, statt sie ins Protokoll zu kopieren.

Fazit

Zugangsdaten müssen zuerst an ihren ursprünglichen Issuer gebunden sein. Nennt der MCP-Server einen anderen Autorisierungsserver, stoppt das Protokoll bei credential_check: deny, nicht senden, keinen Tool-Aufruf schreiben. Das Versionsupgrade legt diesen Vergleich in den Client. expectedIssuer und alte Speicherstände füllen Sie weiterhin selbst.

Die Entscheidung: zuerst Issuer vergleichen; unterscheiden sie sich, deny; credentialsSent bleibt false; keinen Tool-Aufruf anhängen; expectedIssuer an gebündelte Provider schreiben; kann der Client einen nicht vertrauenswürdigen Server erreicht haben, Schlüssel rotieren und Token widerrufen.

Als Nächstes: diesen Stopp mit dem Schema festlegen, dann mit Diff den deny-Eintrag gegen den formgültigen Eintrag mit decision allow sehen.

Zurück zum Blog