flawopen.com/AI Security/mcp-typescript-sdk-bearer-token-audience-validation
Le SDK TypeScript MCP ne vérifiait pas l’audience des jetons bearer
GHSA-rvq5-wwqv-78pq concerne les serveurs HTTP MCP qui acceptaient des jetons sans vérifier leur audience. Mettez à niveau, activez expectedResource et renvoyez l’audience vérifiée dans AuthInfo.resource.[1]
Un agent de sécurité accepte un badge valide sans vérifier quel bâtiment il autorise. Si le même émetteur délivre des badges pour plusieurs bâtiments, un badge destiné au bâtiment voisin peut passer. Le serveur MCP doit comparer la destination du badge à sa propre URL de ressource.[1]
Concepts Clés et Termes
Audience (`aud`)- La ressource ou le service auquel un jeton d’accès est destiné. Une signature valide ou une introspection réussie ne prouve pas à elle seule que le jeton visait ce serveur MCP.[1], [7]
`expectedResource`- Option activée explicitement dans le middleware corrigé. Elle compare l’URL de ressource configurée à celle fournie par le vérificateur du jeton et rejette toute différence avec HTTP 401.[1], [2], [7]
Correspondances de référentiels
- Référence CWE pour la faiblesse décrite dans ce guide : CWE-863
Déroulement de l'Attaque Étape par Étape
Un même émetteur sert plusieurs API
Le déploiement concerné utilise une authentification bearer HTTP pour MCP et son serveur d’autorisation émet des jetons pour plusieurs services. Un jeton destiné à une autre API peut être correctement signé et non expiré.[1]
Le jeton est valide, mais sa destination n’est pas vérifiée
Dans l’intégration concernée, requireBearerAuth() / verifyBearerToken() ne proposaient pas de contrôle intégré de la ressource attendue. Si verifyAccessToken() ne vérifiait pas non plus aud, le serveur MCP pouvait accepter un jeton destiné à un autre service.[1], [2], [5]
Mettez à niveau et liez la validation à cette ressource MCP
Utilisez la version corrigée, définissez expectedResource avec l’identifiant de ce serveur et faites renvoyer l’audience vérifiée par verifyAccessToken() dans AuthInfo.resource. La mise à niveau seule ne l’active pas; sans resource, toutes les requêtes reçoivent 401.[1], [2], [5], [3], [6], [7]
Paquets MCP TypeScript SDK concernés
| Paquet | Versions concernées | Version corrigée |
|---|---|---|
@modelcontextprotocol/sdk | 1.6.0 à 1.31.0 | 1.32.0 ou ultérieure |
@modelcontextprotocol/server | 2.0.0 à 2.2.0 | 2.3.0 ou ultérieure |
@modelcontextprotocol/express | 2.0.0 à 2.0.1 | 2.0.2 ou ultérieure |
@modelcontextprotocol/server-legacy | 2.0.0 à 2.3.0 | 2.3.1 ou ultérieure |
Tout paquet avec sa propre validation de aud dans verifyAccessToken() | L’application applique peut-être déjà le contrôle absent de l’assistant. | Vérifiez que la comparaison est stricte et utilise l’identifiant de ressource attendu. |
Code Source : Vulnérable vs Sécurisé
Cet exemple suit la forme documentée par le SDK et le correctif intégré. verifyJwt représente le validateur JWT ou l’appel d’introspection déjà utilisé par l’application. Avant le correctif, le middleware ne proposait pas expectedResource et ne comparait pas l’audience; la version corrigée compare la ressource configurée à AuthInfo.resource. Utilisez l’identifiant exact placé dans aud par le serveur d’autorisation; l’exemple sélectionne une URL d’audience de même origine et laisse le SDK comparer la ressource complète. Mettez le SDK 1.x à niveau vers 1.32.0 ou ultérieur; pour 2.x, utilisez @modelcontextprotocol/server 2.3.0 ou ultérieur. Avec Express, mettez aussi @modelcontextprotocol/express à niveau vers 2.0.2 ou ultérieur. Comme AuthInfo.resource est une URL, une valeur aud simple qui n’est pas une URL ne peut pas être comparée avec expectedResource : validez strictement cet identifiant dans verifyAccessToken() et laissez expectedResource non configuré pour ce déploiement. L’implémentation de verifyJwt est propre à l’application et omise; aucun faux jeton ni procédure d’exploitation n’est présenté.[1], [2], [5], [3], [6], [7]
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
const mcpServerUrl = new URL('https://api.example.com/mcp');
const auth = requireBearerAuth({
verifier: {
async verifyAccessToken(token) {
const payload = await verifyJwt(token); // Vérification du JWT par l’application
return {
token,
clientId: payload.sub,
scopes: payload.scopes,
expiresAt: payload.exp,
// Aucune ressource n’est renvoyée et le helper concerné ne proposait pas l’option expectedResource.
};
},
},
});
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
const mcpServerUrl = new URL('https://api.example.com/mcp');
const auth = requireBearerAuth({
verifier: {
async verifyAccessToken(token) {
const payload = await verifyJwt(token); // Vérification du JWT par l’application
// aud peut être une chaîne, une liste ou être absent. Indiquez la ressource de ce serveur.
const audience = [payload.aud ?? []].flat().find(
aud => URL.canParse(aud) && new URL(aud).origin === mcpServerUrl.origin,
);
const resource = audience ? new URL(audience) : undefined;
return {
token,
clientId: payload.sub,
scopes: payload.scopes,
expiresAt: payload.exp,
resource,
};
},
},
expectedResource: mcpServerUrl,
});
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Inventoriez les serveurs HTTP MCP utilisant
requireBearerAuth()ouverifyBearerToken()et vérifiez si l’émetteur fournit des jetons à plusieurs API.[1] - ✓Passez le SDK 1.x à 1.32.0 ou ultérieur; pour 2.x, utilisez
@modelcontextprotocol/server2.3.0 ou ultérieur et, avec Express,@modelcontextprotocol/express2.0.2 ou ultérieur; mettezserver-legacyà 2.3.1 ou ultérieur.[1], [3], [6] - ✓Définissez
expectedResourcesur l’identifiant exact de ce serveur MCP dans une configuration de confiance; ne le déduisez pas de la requête entrante.[1], [2], [7] - ✓Faites valider le jeton par
verifyAccessToken()auprès du serveur d’autorisation de confiance et renvoyer l’audience vérifiée dansAuthInfo.resource. Si l’émetteur utilise un identifiant d’audience simple qui n’est pas une URL, validez-le strictement dans le vérificateur et ne configurez pasexpectedResource; ne le devinez pas et ne le convertissez pas en URL.[1], [2], [7] - ✓Testez qu’un jeton destiné à ce serveur fonctionne, tandis qu’un jeton d’un autre service, une audience absente ou une URL avec schéma, hôte, port ou chemin différent reçoit 401.[1], [2], [7]
- ✓Ne considérez pas la mise à niveau comme suffisante :
expectedResourcereste désactivé tant qu’il n’est pas configuré, et sansAuthInfo.resourcetoutes les requêtes sont rejetées.[1] - ✓Surveillez les journaux d’accès et la télémétrie de validation pour repérer les audiences inattendues; révoquez ou faites tourner les identifiants seulement si les éléments indiquent un possible usage abusif.[1]
Sources
- Avis GitHub GHSA-rvq5-wwqv-78pq
- Pull request #2930 : expectedResource dans requireBearerAuth
- Notes de version MCP TypeScript SDK 1.32.0
- MITRE CWE-863 : autorisation incorrecte
- MCP TypeScript SDK v2 pull request #2929: add expectedResource to bearer-token check
- MCP TypeScript SDK release notes for 2.3.0
- MCP TypeScript SDK server guide: bearer-token audience validation