Comment les signatures de requêtes HTTP encadrent les appels API des agents IA
Les signatures de requêtes HTTP lient les appels API pilotés par une IA à une méthode, une cible, un corps et une heure. Découvrez les champs sécurisés, la protection contre le rejeu et les règles liées au décalage d’horloge.

Un agent IA qui possède un bearer token dispose en pratique de la même liberté que tout autre processus capable de lire ce token. Il peut appeler immédiatement un endpoint autorisé, réessayer plus tard, envoyer le token vers un autre hôte si le code l’y autorise, et créer une requête dont le corps a peu de rapport avec l’instruction utilisateur à l’origine de l’exécution. Le serveur voit surtout que l’identifiant est détenu, et presque rien d’autre.
Les signatures de requêtes HTTP améliorent cette situation en liant l’autorisation à un message précis. Une conception solide peut rendre une signature valide uniquement pour POST https://api.example.test/v1/releases/42, uniquement avec le corps exact préparé par l’agent, uniquement pendant une courte période et uniquement sous une identité de signature donnée. C’est un contrôle utile. Il ne remplace ni l’autorisation, ni l’approbation, ni l’isolation des identifiants.
Les équipes se trompent de deux manières opposées. Certaines conservent les bearer tokens parce que la signature leur paraît compliquée, puis découvrent qu’un agent peut utiliser un token largement autorisé partout où il peut effectuer une requête sortante. D’autres signent chaque en-tête produit par leur bibliothèque HTTP et construisent un vérificateur si fragile que les proxys ordinaires, le décalage d’horloge ou une mise à jour de bibliothèque font tomber la production. La bonne conception lie les champs qui modifient le sens ou la destination d’une action, puis rend la gestion de la fraîcheur et de la vérification suffisamment simple pour être fiable au quotidien.
Les signatures contraignent un message, alors que les bearer tokens prouvent seulement leur possession
Un bearer token répond à une question : cette requête présente-t-elle un secret actuellement accepté ? Il ne lie normalement pas ce secret à la méthode, à la cible, au corps ou à l’heure de la requête. Les jetons d’accès OAuth peuvent contenir des portées, des audiences et une date d’expiration, ce qui aide, mais leur détenteur peut tout de même utiliser chaque portée autorisée jusqu’à l’expiration.
Une signature de requête répond à une question cryptographique plus précise : le détenteur de cette clé de signature a-t-il autorisé cet ensemble défini de composants de la requête ? Le serveur reconstruit les éléments signés, vérifie la signature avec la clé publique enregistrée ou le secret partagé, contrôle les limites temporelles, puis applique l’autorisation normale. Cet ordre compte. La vérification de la signature établit l’intégrité du message et l’identité du signataire. L’autorisation décide si cette identité peut effectuer l’action.
Prenons un service de déploiement avec un bearer token limité à la création de versions. Un agent crée d’abord une version de staging inoffensive. Plus tard, une injection de prompt lui demande de créer une version de production. Le token laisse passer les deux requêtes si sa portée couvre les deux environnements. Remplacer le token par une requête signée ne corrige pas à lui seul cette erreur d’autorisation. Si l’identité de signature peut créer des versions de production, elle peut signer une requête de production.
Les signatures donnent toutefois au service des protections qu’un en-tête bearer ordinaire n’offre pas :
- Une requête signée capturée expire rapidement et ne peut pas être rejouée indéfiniment.
- Une signature copiée ne peut généralement pas être déplacée de
POST /v1/staging/releasesversPOST /v1/production/releases. - Une modification du corps JSON échoue lors de la vérification lorsque le condensat du corps est signé.
- Le service peut identifier un identifiant de signature sans l’accepter comme valeur d’en-tête réutilisable.
- Un vérificateur peut enregistrer exactement les champs approuvés par le signataire.
Ne présentez pas cela comme un remplacement des tokens d’accès dans toutes les architectures. De nombreux systèmes utilisent les deux. Un token d’accès peut identifier l’utilisateur ou la charge de travail déléguée, tandis qu’une signature lie chaque message à un identifiant de signature propre à l’agent. Dans d’autres systèmes, une requête signée authentifie directement l’appelant et le serveur déduit les permissions de l’identité de signature.
Pour les agents IA, cette distinction a une conséquence pratique : une signature transforme l’action en objet concret que vous pouvez inspecter, approuver et consigner. Un bearer token ressemble surtout à une capacité ambiante. Cette différence rend la revue possible, mais seulement si la frontière de vos outils empêche l’agent d’extraire le secret de signature.
Utilisez RFC 9421 au lieu d’inventer une chaîne canonique
RFC 9421, HTTP Message Signatures, définit une manière structurée de déclarer les composants couverts et d’envoyer une signature. Elle évite le format privé habituel où un côté assemble les champs avec des retours à la ligne, un autre normalise une URL différemment et les deux accusent la cryptographie lorsque la vérification échoue.
La norme sépare deux notions. Signature-Input déclare une étiquette, les composants couverts et des paramètres comme created, expires, nonce, alg et keyid. Signature transporte la valeur cryptographique obtenue sous la même étiquette. Le vérificateur construit la base de signature à partir des composants et paramètres déclarés, puis la vérifie.
Une requête compacte pourrait ressembler à ceci :
POST /v1/releases/42?environment=staging HTTP/1.1
Host: api.example.test
Content-Type: application/json
Content-Digest: sha-256=:rPMyV6WTE4Duf0JApE9tXvDYy9EzrgFQbq3e2XTCwbs=:
Signature-Input: sig1=("@method" "@authority" "@path" "@query" "content-digest" "date");created=1735689600;expires=1735689660;keyid="agent-release-17";alg="ed25519"
Signature: sig1=:BASE64_SIGNATURE_BYTES:
Date: Wed, 01 Jan 2025 00:00:00 GMT
{"version":"2025.01.01","notes":"staging validation"}
La valeur de condensat ci-dessus illustre la forme transmise, mais ne correspond pas au condensat du JSON d’exemple. En production, le client calcule le condensat à partir des octets exacts qu’il va envoyer. S’il sérialise le JSON une seconde fois après la signature, il fabrique une source d’échecs.
RFC 9421 est volontairement flexible. Cette souplesse est utile pour les intermédiaires et les différentes versions de HTTP, mais elle signifie que votre contrat d’API doit définir un profil précis. Indiquez l’algorithme autorisé, les composants obligatoires couverts, la durée maximale de la signature, le format de keyid, l’algorithme de condensat accepté et le caractère obligatoire ou non du nonce. Si le contrat dit simplement que les requêtes doivent être signées, chaque auteur de client fera des suppositions différentes.
Ed25519 est un choix par défaut raisonnable lorsque votre service peut enregistrer des clés publiques. Le serveur conserve une clé publique de vérification, et la perte de cet enregistrement n’expose pas de secret de signature. Les signatures HMAC peuvent convenir lorsqu’un composant de confiance et l’API partagent un secret, mais ce secret doit exister des deux côtés. Pour les flux de travail avec des agents, cela augmente souvent le nombre d’endroits où un secret réutilisable peut fuir.
Évitez un schéma propriétaire, sauf si une contrainte de protocole l’impose. Les formats propriétaires signent souvent l’URL brute dans un client, le chemin décodé dans un autre et une représentation différente de l’hôte dans le vérificateur. RFC 9421 fournit des identifiants de composants définis et des champs structurés. Utilisez-les, puis testez le profil exact que vous publiez.
Liez la méthode, la destination et la requête qui définissent l’action
Un agent devrait signer chaque champ qui modifie la destination de la requête ou l’opération exécutée par le serveur. Pour la plupart des appels API, l’ensemble minimal utile est @method, @authority, @path et @query. Vous pouvez utiliser @target-uri lorsque vous préférez un composant couvrant toute l’URI cible, mais ne signez pas les deux formats sans raison claire pour les personnes qui les implémenteront.
@method empêche de réutiliser une signature prévue pour GET avec DELETE. Cela paraît évident, mais les schémas écrits à la main l’omettent souvent parce qu’un ingénieur suppose que le chemin implique l’opération. Les API REST réutilisent fréquemment un chemin avec différentes méthodes. La méthode modifie l’action.
@authority lie l’hôte et le port. Il empêche une signature émise pour une origine API de valider une requête destinée à une autre origine qui accepterait le même identifiant. C’est important dans les organisations qui utilisent des hôtes de prévisualisation, de staging et de production. Une identité de signature prévue pour le staging ne doit pas obtenir une autorité de production parce qu’un agent ou une redirection a changé l’hôte.
@path et @query exigent la même attention. De nombreuses API placent des paramètres importants dans la chaîne de requête :
POST /v1/invoices/817/refund?amount=2500\u0026currency=USD
Si la signature ne couvre que le chemin, un attaquant capable de modifier la requête en transit peut changer le montant ou la devise. Si le serveur lit dry_run, environment, force, page_size, include_deleted ou un sélecteur de tenant dans la requête, ces valeurs font partie de l’action. Signez @query.
RFC 9421 définit aussi @query-param, qui peut couvrir un paramètre nommé précis. Il a sa place dans les protocoles où certains paramètres de requête sont explicitement exclus de la décision de sécurité, comme les données de traçage. Pour une API interne utilisée par des agents, signer toute la requête est généralement plus prévisible. Chaque paramètre devient partie de la requête approuvée, et les réviseurs n’ont pas à se souvenir d’une liste d’exceptions.
Ne traitez pas la normalisation des URL à la légère. Votre vérificateur doit utiliser la sémantique des composants spécifiée par RFC 9421 et votre bibliothèque choisie. Ne décodez pas les échappements de pourcentage pour les réencoder vous-même. Ne triez pas les paramètres de requête répétés, sauf si la définition du composant sélectionné le prévoit. Une cible de requête est constituée d’octets sur le réseau avant de devenir un objet pratique pour l’application.
Les redirections exigent une règle stricte : ne transmettez pas automatiquement une requête signée à travers une redirection vers une autre autorité. Une signature couvrant l’autorité d’origine doit échouer sur le nouvel hôte, ce qui est normal. Le client doit recevoir la redirection, appliquer une liste d’autorisation explicite, construire une nouvelle requête et signer cette nouvelle requête. Pour les requêtes qui modifient des données, de nombreuses équipes devraient refuser les redirections sans exception.
Un corps signé a besoin d’un condensat, pas d’hypothèses sur le JSON
Signez content-digest lorsque le corps influence le résultat. Cela inclut presque toutes les requêtes de mutation JSON, les envois multipart, les formulaires et les opérations en masse. Signer content-type peut également être pertinent lorsque le serveur interprète différemment les mêmes octets selon le type de média.
L’IETF définit Content-Digest dans RFC 9530. Ce champ contient le condensat du contenu du message HTTP selon la syntaxe Structured Fields. La signature couvre l’en-tête de condensat plutôt qu’un corps volumineux directement, tandis que le destinataire calcule le condensat du corps et le compare avant d’accepter la signature. La signature dispose ainsi d’une représentation de taille fixe du contenu exact.
La séquence d’envoi sûre est simple et doit rester dans cet ordre :
- Construisez l’objet final de la requête, y compris les paramètres de requête et les en-têtes qui influencent son interprétation.
- Sérialisez une seule fois le corps en octets et conservez ces octets pour la transmission.
- Calculez
Content-Digestsur ces octets. - Créez
Signature-Inputsur les composants choisis, puis signez sa base de signature. - Envoyez les mêmes octets et les mêmes en-têtes, sans modification.
L’échec courant est plus banal qu’une attaque cryptographique. Une application sérialise un objet pour calculer le condensat, le signe, puis un utilitaire HTTP sérialise de nouveau l’objet. L’ordre des membres JSON, les échappements, les espaces, le format des nombres ou un champ d’horodatage changent. Le vérificateur signale correctement une différence de condensat. Les développeurs suppriment alors la protection du corps pour terminer le déploiement. C’est la mauvaise correction.
Transmettez au transport un tampon d’octets, un flux ou un corps de requête immuable. Si le flux empêche de calculer un condensat complet avant la transmission, utilisez un protocole conçu pour ce cas et testez-le sérieusement. N’omettez pas discrètement le condensat du corps d’une opération à fort impact parce que le flux était peu pratique.
Pour les en-têtes, adoptez une règle plus étroite. Signez un en-tête lorsqu’un destinataire ou un intermédiaire peut l’utiliser pour modifier le sens de sécurité de la requête. content-type est un candidat. Un en-tête de tenant en est un autre si le serveur l’utilise pour choisir un compte. Un en-tête d’idempotence est pertinent si les nouvelles tentatives et les effets en double comptent. Un en-tête de diagnostic ne l’est généralement pas.
Signer user-agent, accept, les identifiants de trace, les en-têtes de connexion et chaque en-tête produit par la bibliothèque rend les clients fragiles. Les proxys peuvent ajouter, combiner ou réécrire les en-têtes ordinaires. HTTP autorise ces transformations. Vous voulez que la signature refuse les modifications sémantiques, pas qu’elle transforme une variation de transport inoffensive en panne.
Les fenêtres de fraîcheur doivent tolérer les écarts tout en refusant le travail mis en file
Les limites temporelles rendent les signatures capturées rapidement inutilisables. Elles provoquent aussi des incidents inutiles lorsque les équipes font comme si chaque poste, conteneur et VM avait une horloge parfaite. La réponse consiste à définir une règle d’acceptation bornée et une bonne synchronisation, pas à accorder une tolérance de deux heures.
Utilisez created et expires dans Signature-Input. Une durée de soixante secondes convient bien aux appels d’action interactifs lorsque l’agent signe juste avant l’envoi. Quelques minutes peuvent être adaptées à un réseau peu fiable ou à un flux qui réessaie après une panne temporaire. L’API doit documenter une durée maximale et l’appliquer. Ne laissez pas les clients choisir une expiration arbitraire parce qu’ils la trouvent contraignante.
Le vérificateur doit traiter séparément trois cas :
- Refuser une requête dont l’heure
createdest trop éloignée dans le futur, au-delà d’une petite tolérance configurée. - Refuser une requête dont l’heure
expiresest dépassée. - Refuser une requête dont la durée,
expires - created, dépasse le maximum de l’API, même si elle n’est pas encore expirée.
Un serveur peut accepter une horloge client légèrement en retard ou en avance sans accepter un travail obsolète. Par exemple, un service peut autoriser un léger décalage futur et une courte période d’expiration. Les valeurs exactes dépendent de l’environnement des clients, mais le principe reste le même : la tolérance de dérive n’est pas une fenêtre de rejeu laissée ouverte tout l’après-midi.
N’utilisez pas l’en-tête HTTP Date comme seul mécanisme de fraîcheur. Il peut être utile comme composant couvert pour la compatibilité et les diagnostics, mais created et expires figurent directement dans les paramètres de signature et sont moins ambigus. Si vous signez les deux, indiquez quelles valeurs le serveur utilise pour l’application de la règle lorsqu’elles diffèrent. Un vérificateur qui accepte l’une ou l’autre donne un avantage inutile aux attaquants.
Les tâches d’agent mises en file révèlent une erreur de conception cachée. Supposons qu’un agent crée des requêtes signées à 09:00, qu’une approbation humaine attende jusqu’à 09:20 et qu’un worker envoie l’ancienne requête après l’approbation. Le service doit la refuser. La tâche a besoin d’un nouvel événement de signature après l’approbation, car l’action approuvée doit avoir une heure actuelle et une destination actuelle pertinentes.
Pour les opérations pouvant être rejouées, placez un identifiant d’idempotence dans un en-tête ou un champ du corps signé, puis couvrez-le avec la signature. Le client peut créer une nouvelle signature pour chaque tentative, tandis que le serveur reconnaît l’opération logique et évite les effets en double. Réutiliser une requête signée expirée n’est pas une stratégie de nouvelle tentative.
Les nonces n’arrêtent le rejeu que si le serveur s’en souvient
Une expiration courte limite le rejeu, mais n’empêche pas un attaquant de rejouer plusieurs fois une requête capturée pendant cette fenêtre. L’importance dépend de l’endpoint. Rejouer une lecture inoffensive a peu d’effet. Rejouer un transfert d’argent, une suppression de compte ou une modification d’infrastructure peut être grave.
Un nonce répond à ce risque lorsque le serveur considère chaque nonce comme utilisable une seule fois pour une identité de signature. Le client génère une valeur imprévisible, la place dans les paramètres de signature ou dans un en-tête couvert, et le serveur enregistre son utilisation jusqu’à l’expiration de la signature. Une seconde requête avec le même signataire et le même nonce échoue, même si sa signature reste valide.
C’est la partie que les équipes oublient lorsqu’elles disent utiliser des nonces. Un nonce que le serveur ne conserve pas n’est qu’une chaîne aléatoire supplémentaire. Il ne prouve pas l’unicité. Un cache ou une table de base de données partagée a besoin d’une création atomique pour que des rejeux simultanés ne passent pas tous deux la vérification.
Utilisez un stockage de nonces lorsque les dommages d’un rejeu justifient le coût opérationnel. Vous devrez choisir sa durée de conservation, ses limites de taille, son comportement en cas de panne et son partitionnement. Conservez un nonce accepté jusqu’au dernier moment où le serveur pourrait accepter la requête. Définissez les enregistrements par identité du signataire et nonce, pas par nonce seul, car des identités de signature différentes peuvent générer la même valeur sans conséquence de sécurité.
Ne rendez pas le nonce obligatoire pour chaque appel peu risqué et très fréquent simplement parce que cela semble plus sûr. Ce choix peut transformer une panne du stockage de nonces en panne pour des lectures inoffensives. Un profil pratique peut l’exiger pour les mutations irréversibles et s’appuyer ailleurs sur une expiration courte et des contrôles d’idempotence. Écrivez cette règle endpoint par endpoint.
Les contrôles de nonce ne remplacent pas non plus l’idempotence. Le nonce indique que ce message signé doit être accepté une fois. Un identifiant d’idempotence indique que plusieurs tentatives de nouvelle signature représentent une seule opération métier voulue. Ils corrigent des problèmes différents.
La vérification doit échouer par défaut avant que le code applicatif ne voie la requête
La passerelle API ou le point d’entrée de l’application doit vérifier la requête avant qu’un gestionnaire de route n’analyse les paramètres d’action, ne démarre une tâche ou ne consulte des services en aval. Un gestionnaire qui lit le corps et effectue un travail avant la vérification a déjà abandonné la propriété de sécurité que la signature devait fournir.
Un vérificateur a besoin d’une séquence prévisible :
- Analysez
Signature-InputetSignaturecomme des Structured Fields, en refusant la syntaxe mal formée et les ambiguïtés dues aux doublons. - Sélectionnez une étiquette de signature autorisée et refusez les algorithmes inconnus, les composants obligatoires manquants ou les combinaisons de composants interdites.
- Associez
keyidà une identité de signature active et récupérez son matériel de vérification. - Reconstruisez la base de signature conformément à RFC 9421, en utilisant la requête reçue plutôt qu’une URL d’application reconstruite.
- Vérifiez la signature cryptographique, le condensat du corps, les limites temporelles, l’état du nonce lorsque nécessaire, puis l’autorisation.
Séparez dans vos journaux les échecs cryptographiques et les échecs d’autorisation. Les réponses publiques peuvent rester volontairement simples. Un 401 ou 403 accompagné d’un code d’erreur stable suffit aux appelants. En interne, indiquez si le service a refusé la requête à cause d’un keyid inconnu, d’une signature expirée, d’un condensat invalide, d’une autorité différente, d’un nonce réutilisé ou de permissions insuffisantes.
Ne consignez pas les octets de signature comme s’il s’agissait de simples données de débogage. Les signatures à clé publique ne sont pas secrètes comme les identifiants HMAC, mais les journaux complets de requêtes contiennent souvent des en-têtes d’autorisation, des données personnelles et des corps. Consignez un identifiant de requête, l’identité du signataire, les noms des composants couverts, une valeur de condensat si votre politique de conservation l’autorise et la décision prise. Limitez la capture brute à une procédure d’incident définie.
Les vecteurs de test comptent davantage que la documentation. RFC 9421 contient des exemples, mais votre profil d’API a besoin de ses propres jeux de test. Conservez des requêtes qui doivent être vérifiées, ainsi que des modifications qui doivent échouer : méthode modifiée, requête modifiée, octet du corps modifié, signature expirée, valeur created future, autorité incorrecte, keyid altéré et nonce répété. Exécutez-les dans chaque implémentation client prise en charge.
Un vérificateur de signature doit refuser les ambiguïtés, même si un analyseur permissif pouvait deviner l’intention de l’émetteur. Les en-têtes en double, les sérialisations incohérentes des composants et les algorithmes non pris en charge sont des erreurs de protocole. Un agent n’a pas besoin que le serveur soit accommodant, mais qu’il soit exact.
Gardez le matériel de signature hors du contexte de l’agent
Donner à un agent de programmation IA une clé de signature privée ou un secret HMAC annule une grande partie de l’intérêt de la signature. La clé peut apparaître dans la sortie d’un outil, l’historique du shell, des fichiers temporaires, des rapports d’erreur ou un prompt demandant à l’agent d’afficher son environnement. Même un agent bien intentionné dispose d’une surface indirecte trop grande pour un identifiant réutilisable.
Exposez plutôt une frontière d’action étroite. L’agent fournit la méthode, la destination autorisée, les en-têtes et le corps à un composant local ou distant de confiance. Ce composant valide la cible autorisée, obtient une approbation lorsque votre flux l’exige, construit la liste des composants couverts, signe juste avant l’envoi et renvoie la réponse. L’agent ne reçoit ni secret en clair, ni faux substitut qu’il pourrait transmettre accidentellement.
Cette frontière rend aussi l’examen de l’autorité concret. Une identité de signature peut être limitée à un service, un environnement, une famille de routes et une classe d’actions. Si l’agent doit seulement ouvrir une version de staging, ne lui accordez pas un identifiant capable de signer des ajustements de facturation ou des suppressions en production. L’autorisation côté serveur reste chargée d’appliquer ces limites après la vérification de la signature.
Sallyport applique cette séparation aux actions HTTP : l’application conserve les identifiants API dans son coffre chiffré et effectue elle-même l’appel HTTP. Un agent compatible MCP reçoit donc le résultat plutôt que l’identifiant en clair.
Ne confondez pas une passerelle d’action avec une norme de signature de requête. RFC 9421 explique à deux parties HTTP comment authentifier certains composants d’un message. Une passerelle d’action décide où résident les éléments de signature, quand un humain voit une approbation et quelle trace d’audit accompagne une exécution d’agent. Vous pouvez utiliser l’une sans l’autre, mais leur combinaison est utile lorsque des outils autonomes agissent sur des API sensibles.
Si vous utilisez un signataire local, traitez son interface locale comme une frontière d’autorisation. Liez si possible les requêtes au processus appelant, refusez les destinations arbitraires et assurez-vous qu’un processus agent ne peut pas utiliser silencieusement la session approuvée d’un autre processus. Un service local qui signe n’importe quelle URL soumise par n’importe quel programme local a simplement déplacé une capacité bearer derrière un socket.
L’approbation et les signatures répondent à des questions différentes
Une approbation humaine indique que quelqu’un a autorisé une catégorie précise d’action d’agent. Une signature de requête indique qu’une identité de signature a autorisé un message HTTP défini. Aucun de ces enregistrements ne prouve l’autre, sauf si votre conception les lie explicitement.
Pour les appels à fort impact, faites apparaître dans l’écran d’approbation les champs que la signature couvrira : méthode, autorité, chemin, paramètres de requête importants, condensat du corps ou résumé lisible du corps, identité de signature et expiration. Si un utilisateur approuve POST /v1/releases/42?environment=staging, le signataire ne doit pas pouvoir remplacer ensuite staging par production au moyen d’un paramètre de requête non signé.
C’est ici que le fait de ne condenser qu’un corps opaque peut nuire à la revue humaine. Le condensat cryptographique prouve l’identité des octets, mais n’apprend presque rien à une personne. Conservez les deux éléments : une représentation canonique de la requête pour la revue et un condensat pour l’intégrité. L’écran de revue doit dériver de l’objet de requête immuable exact que le signataire enverra, et non d’un plan rendu séparément.
Une approbation de session peut convenir à une courte exécution d’agent comportant de nombreux appels peu risqués. Une approbation par appel est préférable pour les suppressions, les publications externes, les changements financiers ou toute opération qu’un attaquant pourrait dissimuler parmi le trafic courant. Ne demandez pas une approbation pour chaque lecture simplement pour revendiquer un contrôle humain. Les utilisateurs cliqueront machinalement, créant une lassitude d’approbation sans décision réelle.
La piste d’audit doit enregistrer l’identité du signataire, les composants couverts, la durée de validité de la signature, la décision d’autorisation, la référence d’approbation lorsqu’elle existe, le statut de la réponse et un identifiant de requête. Une piste lisible aide l’opérateur à répondre à une question simple après un incident : qu’a envoyé l’agent, sous quelle autorité et le serveur l’a-t-il accepté ?
Les échecs à répéter sont surtout des échecs ordinaires d’ingénierie
Les bugs d’implémentation les plus dangereux ne concernent pas des courbes elliptiques cassées. Ce sont des champs non signés, une canonisation incohérente, du travail obsolète et des secrets placés là où les agents peuvent les lire.
Un échec survient lorsqu’une équipe signe @method, @path et date, mais oublie @query. Son API de versions accepte normalement ?environment=staging. Plus tard, une modification de maintenance ajoute ?environment=production au même endpoint. Un bug de proxy ou un composant local hostile modifie le paramètre après la signature. La signature est valide, le gestionnaire voit production et le journal d’audit affirme à tort que l’agent a envoyé une requête signée valide. La signature a fait exactement ce que demandait la liste des composants. C’est cette liste qui était incomplète.
Un autre échec apparaît lorsque les développeurs acceptent des signatures de dix minutes pour réduire les tickets liés aux horloges. Un agent signe une requête de suppression, écrit tous les en-têtes dans un journal de débogage et un développeur copie le journal dans un ticket. Toute personne ayant accès à ce ticket peut rejouer la requête pendant la majeure partie de la journée de travail. Une expiration courte n’efface pas la fuite initiale, mais en limite fortement l’utilité. Exiger un nonce pour les suppressions supprime la fenêtre de rejeu restante après la première acceptation.
Un troisième échec survient avec des identifiants HMAC partagés. Plusieurs agents utilisent le même secret parce que le provisionnement d’identités individuelles semblait trop lourd. Lorsqu’un audit découvre un appel destructif, l’équipe peut identifier l’intégration partagée, mais pas l’exécution de l’agent, l’approbation de l’utilisateur ou le processus à l’origine de l’appel. Donnez des identités de signature distinctes aux différentes frontières d’autorité. L’attribution fait partie de la réponse aux incidents, ce n’est pas un luxe de reporting.
Commencez par un endpoint de mutation et rédigez le profil avant de choisir une bibliothèque. Précisez son autorité autorisée, ses composants obligatoires, la règle du condensat du corps, la durée de signature, la tolérance de dérive, la règle anti-rejeu, l’algorithme de signature et la correspondance avec l’autorisation. Construisez ensuite des tests négatifs qui modifient chaque champ couvert. Si un test peut modifier un champ significatif de la requête tout en restant valide, ne déployez pas le profil.
Une requête signée doit pouvoir être refusée facilement pour la bonne raison. Cette exigence oriente la conception vers une autorité étroite, des messages de courte durée, des corps immuables et des journaux qui montrent ce qui s’est passé après l’action de l’agent.
FAQ
Quelle est la différence entre un bearer token et une signature de requête HTTP ?
Un bearer token autorise quiconque le possède. Une signature de requête prouve que le détenteur d’une clé de signature a approuvé une forme précise de requête, par exemple une méthode, une cible, un horodatage et un condensat de corps donnés. Elle réduit les risques de rejeu et de substitution de requête, mais ne décide pas si l’agent était autorisé à agir.
Les signatures de requêtes HTTP rendent-elles les agents autonomes sûrs ?
Non. Une signature lie une requête à une clé de signature, mais un agent capable d’utiliser cette clé peut toujours signer une requête destructive dans les limites de son autorité. Il faut toujours des identifiants limités, une autorisation côté serveur, une approbation pour les actions sensibles et une piste d’audit.
Quels champs HTTP un agent doit-il signer ?
Commencez par @method, @target-uri ou @authority avec @path et @query, content-digest pour les requêtes avec un corps, ainsi qu’une valeur de fraîcheur comme date ou @created. Ajoutez des en-têtes uniquement lorsque le serveur prend une décision de sécurité à partir de ceux-ci. Ne signez pas des en-têtes sans rapport simplement parce qu’un SDK les envoie.
Une signature de requête doit-elle inclure le corps de la requête HTTP ?
Signez content-digest chaque fois que le corps modifie l’action, ce qui couvre la plupart des requêtes POST, PUT, PATCH et DELETE contenant du JSON. Sans condensat, un intermédiaire ou un bug entre la signature et l’envoi peut remplacer le corps tout en laissant la signature valide. Une méthode et un chemin signés ne protègent pas un contenu non signé.
Comment les API doivent-elles gérer le décalage d’horloge des requêtes signées ?
Utilisez une courte période de validité et prévoyez une tolérance explicite limitée pour les erreurs normales d’horloge. Refusez les requêtes anciennes, celles dont l’heure est trop éloignée dans le futur, et demandez aux clients de corriger leur horloge avec une synchronisation normale plutôt que d’élargir indéfiniment la fenêtre d’acceptation. Si les agents mettent les tâches en file pendant longtemps, signez juste avant l’envoi.
Les requêtes API signées ont-elles besoin d’un nonce ?
Un nonce peut empêcher le rejeu pendant une fenêtre de validité si le serveur conserve chaque nonce accepté pour chaque identifiant ou identité de signature. Ce stockage a un coût et doit gérer l’expiration. De nombreuses API utilisent donc des signatures de courte durée pour les appels peu risqués et ajoutent des nonces pour les transferts, suppressions et autres actions où un seul rejeu est inacceptable. Le nonce doit être signé, sinon un attaquant peut le remplacer.
RFC 9421 est-elle la bonne norme pour signer les requêtes API ?
RFC 9421 définit les signatures de messages HTTP et fournit des noms de composants standard tels que @method, @authority, @path et @query. C’est un bon choix de format réseau lorsque le client et le serveur contrôlent l’intégration. La norme ne définit ni votre modèle d’autorisation, ni votre stockage anti-rejeu, ni la distribution des clés de signature.
Un agent IA doit-il détenir la clé de signature de l’API ?
En général, non. Un secret brut placé dans le contexte d’un agent transforme chaque injection de prompt, fuite de journal et erreur d’outil en incident lié aux identifiants. Donnez à l’agent une interface d’action limitée, laissez un composant de confiance conserver l’identifiant et enregistrez la requête exacte envoyée par ce composant.
Pourquoi des signatures de requêtes valides sont-elles quand même refusées ?
Commencez par distinguer les signatures mal formées des signatures valides mais dépourvues d’autorisation, et utilisez des codes d’état et des codes internes différents. Gardez les erreurs publiques peu détaillées afin de ne pas permettre l’exploration de votre logique de vérification, mais consignez côté serveur la liste des composants en échec, l’identifiant du signataire, les valeurs d’horloge et l’entrée canonique. Ne consignez jamais le secret de signature.
Les signatures de requêtes HTTP peuvent-elles remplacer TLS ?
Non. TLS protège la connexion pendant son existence, tandis qu’une signature de message voyage avec la requête et permet au destinataire de vérifier certains champs. La plupart des appels API nécessitent de toute façon TLS, car les signatures ne masquent ni le contenu des requêtes, ni celui des réponses, ni les bearer tokens transportés avec elles.