Passer au contenu principal
GUIDE

Comment vérifier des requêtes signées d'agents IA

Un guide pratique pour vérifier des signatures RFC 9421 provenant de clients automatisés, ce qu'une signature valide prouve, et ce que vos journaux doivent capturer.

Toshendra Sharma

Par Toshendra SharmaFondateur et CEO, AgentCredia · Updated November 13, 2026

Avancé · 11 min de lecture

Que prouve une requête signée ?

La plupart du trafic automatisé s'identifie au moyen d'une chaîne user agent. Cette chaîne est une déclaration, et la copier ne coûte rien. Les requêtes signées changent la donne. Lorsqu'un client automatisé signe ses requêtes HTTP avec une clé privée et publie la clé publique correspondante à un emplacement lié à son opérateur, votre serveur peut vérifier que la requête a bien été produite par quelqu'un qui détient cette clé.

La norme qui sous-tend cela est HTTP Message Signatures, publiée sous le nom RFC 9421. Le groupe de travail de l'IETF Web Bot Auth working group s'appuie dessus avec des conventions pour les clients automatisés - comment un agent indique son répertoire de clés et quels composants de requête il doit signer. En novembre 2026, le document de protocole du groupe de travail est draft-ietf-webbotauth-httpsig-protocol, qui définit l'en-tête Signature-Agent, un format de répertoire de clés basé sur JSON Web Key Sets et une URI well-known pour le publier. Ces deux éléments méritent d'être compris en détail, car il est facile de se tromper subtilement lors de la vérification.

Avant la mécanique, soyez précis sur le résultat. Une signature valide montre que la requête a été signée par une clé associée à un identifiant publié, tel qu'un domaine d'opérateur, et que les parties signées de la requête n'ont pas été modifiées. Elle ne montre pas qui a instruit l'agent, si une personne lui a délégué une autorité, ni si l'action est sûre. L'évidence d'identité et l'autorisation sont des questions distinctes, et votre application répond à la seconde. Notre article de blog sur ce que prouvent les requêtes signées d'agents IA développe cette frontière avec des exemples.

Les en-têtes impliqués

Une requête signée transporte généralement trois en-têtes :

  • Signature-Input (RFC 9421, Section 4.1) décrit chaque signature : un libellé, la liste des composants couverts, et des paramètres tels que created, expires, keyid, alg, nonce et tag.
  • Signature (RFC 9421, Section 4.2) contient les octets de signature pour chaque libellé, encodés en base64, sous forme de champ structuré.
  • Signature-Agent (issu des conventions Web Bot Auth) indique où les clés publiques du client sont publiées, généralement une URL HTTPS sur le domaine de l'opérateur.

Exemple simplifié :

HTTP
GET /api/catalog?page=2 HTTP/1.1
Host: shop.example
Signature-Agent: "https://agent.example"
Signature-Input: sig1=("@method" "@authority" "@path" "signature-agent");created=1762934400;expires=1762934700;keyid="kq1";alg="ed25519";tag="web-bot-auth";nonce="d9f1c2"
Signature: sig1=:Base64SignatureBytes==:

L'exemple est simplifié pour la lisibilité. Dans le brouillon actuel du groupe de travail, Signature-Agent est lui-même un champ structuré indexé par le libellé de signature (par exemple sig1="https://agent.example"), alors vérifiez quelle forme votre bibliothèque de vérification attend.

Étape 1 : analyser correctement les champs structurés

Signature-Input et Signature sont des HTTP Structured Fields (RFC 9651, qui obsolète RFC 8941). Ne les analysez pas en séparant des chaînes. Les chaînes entre guillemets, les paramètres et les listes internes ont tous une grammaire précise, et un analyseur approximatif crée à la fois des rejets injustifiés et de réelles failles de sécurité.

Utilisez un analyseur de champs structurés maintenu, ou mieux encore, une bibliothèque RFC 9421 maintenue qui gère ensemble l'analyse et la vérification. Implémenter la spécification from scratch est un bon exercice d'apprentissage et un mauvais choix en production. Les étapes ci-dessous décrivent ce que fait une telle bibliothèque, afin que vous puissiez la configurer correctement et examiner sa sortie.

Étape 2 : choisir quelle signature évaluer

Une requête peut contenir plus d'un libellé de signature. Sélectionnez celui qui correspond à votre politique, par exemple le libellé dont tag est web-bot-auth. Rejetez la requête à des fins de vérification - et non de service - si :

  • aucune signature n'a les paramètres requis,
  • l'algorithme ne figure pas dans votre allowlist (par exemple ed25519), ou
  • les composants couverts n'incluent pas l'ensemble minimal requis.

Un minimum raisonnable pour les clients automatisés est @method, @authority et @path, plus signature-agent lorsque cet en-tête est présent. Sans @authority, une signature capturée pour un site pourrait être rejouée contre un autre. Sans @path, elle pourrait être réutilisée sur plusieurs endpoints. Le draft de protocole Web Bot Auth exige que les agents couvrent @authority ou @target-uri, et avertit qu'une signature ne couvrant que l'autorité se vérifie contre n'importe quelle méthode ou chemin sur cet hôte jusqu'à son expiration.

Étape 3 : vérifier les paramètres temporels

Lisez created et expires, les paramètres temporels définis dans RFC 9421, Section 2.3. Puis :

  1. Rejetez les signatures dont l'heure created est dans le futur au-delà d'une faible tolérance de dérive d'horloge.
  2. Rejetez les signatures dont expires est dépassé.
  3. Si expires est absent, imposez votre propre âge maximal sur la base de created.

Des fenêtres de validité courtes limitent la durée pendant laquelle une requête capturée peut être rejouée.

Étape 4 : découvrir la clé

L'en-tête Signature-Agent indique le répertoire de clés de l'opérateur. Récupérez-le via HTTPS, et uniquement via HTTPS. Le draft de protocole Web Bot Auth décrit le répertoire comme un JSON Web Key Set (clés au format JWK) servi depuis l'URI well-known http-message-signatures-directory sur cette origine. Trouvez ensuite la clé dont l'identifiant correspond au paramètre keyid.

TS
type Jwk = { kid: string; kty: string; crv?: string; x?: string };

const keyCache = new Map<string, { keys: Jwk[]; fetchedAt: number }>();
const KEY_TTL_MS = 60 * 60 * 1000;

export async function getKey(directoryUrl: string, keyId: string): Promise<Jwk | null> {
  const url = new URL(directoryUrl);
  if (url.protocol !== "https:") return null;

  const cached = keyCache.get(url.origin);
  const fresh = cached && Date.now() - cached.fetchedAt < KEY_TTL_MS;
  const keys = fresh ? cached.keys : await fetchKeys(url.origin);
  return keys.find((k) => k.kid === keyId) ?? null;
}

async function fetchKeys(origin: string): Promise<Jwk[]> {
  const res = await fetch(`${origin}/.well-known/http-message-signatures-directory`, {
    headers: { accept: "application/http-message-signatures-directory+json, application/json" },
    signal: AbortSignal.timeout(2000),
  });
  if (!res.ok) return [];
  const body = (await res.json()) as { keys?: Jwk[] };
  const keys = Array.isArray(body.keys) ? body.keys : [];
  keyCache.set(origin, { keys, fetchedAt: Date.now() });
  return keys;
}

Règles pratiques pour la découverte de clé :

  • Mettez les clés en cache avec une durée de vie bornée afin que la vérification n'ajoute pas un appel réseau à chaque requête.
  • Définissez un délai d'attente court et traitez un échec de récupération comme "impossible à vérifier", jamais comme "vérifié".
  • Restreignez les origines depuis lesquelles vous récupérez si vous le pouvez, ou bloquez au minimum les plages d'adresses privées pour éviter une server-side request forgery. Le brouillon de protocole cite la server-side request forgery dans ses considérations de sécurité précisément pour cette raison.
  • Liez l'identité à l'origine, et non au nom en texte libre dans le user agent. L'identifiant que vous consignez est l'origine du répertoire.

Étape 5 : reconstruire la base de signature

Le signataire n'a pas signé la requête brute. Il a signé une chaîne canonique appelée signature base (RFC 9421, Section 2.5), construite à partir des composants couverts dans l'ordre, suivie de la ligne @signature-params. Votre vérificateur reconstruit la même chaîne à partir de la requête reçue.

Pour l'exemple ci-dessus, la base ressemble à ceci :

TEXT
"@method": GET
"@authority": shop.example
"@path": /api/catalog
"signature-agent": "https://agent.example"
"@signature-params": ("@method" "@authority" "@path" "signature-agent");created=1762934400;expires=1762934700;keyid="kq1";alg="ed25519";tag="web-bot-auth";nonce="d9f1c2"

Faites attention aux détails qui cassent la vérification derrière des proxys :

  • @authority doit être l'hôte visé par le client. Si un load balancer réécrit Host, utilisez la valeur d'origine qu'il transmet.
  • @path exclut la chaîne de requête. Si le client a couvert @query, reconstruisez-la exactement telle qu'elle a été reçue.
  • Les valeurs d'en-tête sont canonisées comme spécifié : trimées, avec combinaison des valeurs multiples. Une bonne bibliothèque le fait pour vous.

Étape 6 : vérifier la signature

Avec la base et la clé publique, la vérification est un simple appel, en suivant les étapes de RFC 9421, Section 3.2. Pour Ed25519 dans Node.js, en utilisant le crypto module intégré :

TS
import { createPublicKey, verify } from "node:crypto";

export function verifyEd25519(jwk: Jwk, base: string, signatureB64: string): boolean {
  const key = createPublicKey({ key: jwk as any, format: "jwk" });
  return verify(null, Buffer.from(base, "utf8"), key, Buffer.from(signatureB64, "base64"));
}

Si le contrôle échoue, la requête n'est pas vérifiée. Elle peut quand même être servie. Elle n'obtient simplement pas le fondement "vérifié".

Étape 7 : empêcher le rejouement

Une signature valide peut être capturée et renvoyée jusqu'à son expiration. RFC 9421, Section 7.2.2 traite du rejouement de signature, et le brouillon de protocole Web Bot Auth s'y réfère au lieu d'ajouter ses propres règles de nonce. Deux contrôles ferment la majeure partie de cette fenêtre :

  1. Expiration courte, appliquée à l'étape 3.
  2. Suivi des nonces. Lorsqu'un nonce est présent, stockez-le avec son identifiant de clé jusqu'à l'expiration de la signature, puis rejetez une seconde utilisation.
TS
const seen = new Map<string, number>();

export function acceptNonce(keyId: string, nonce: string, expiresAt: number): boolean {
  const id = `${keyId}:${nonce}`;
  const now = Date.now() / 1000;
  for (const [k, exp] of seen) if (exp < now) seen.delete(k);
  if (seen.has(id)) return false;
  seen.set(id, expiresAt);
  return true;
}

Dans un déploiement multi-instance, conservez les nonces dans un magasin partagé comme Redis avec un time to live égal à la validité restante.

Étape 8 : consigner le résultat avec son fondement

La vérification produit de l'évidence, pas un verdict. Consignez-la dans une forme que vos analyses et vos politiques peuvent utiliser :

TS
const result = {
  actor: "agent",
  basis: "verified",
  operator: "https://agent.example",
  keyId: "kq1",
  coveredComponents: ["@method", "@authority", "@path", "signature-agent"],
  verifiedAt: new Date().toISOString(),
};

Lorsque la vérification échoue ou ne peut pas s'exécuter, consignez-le aussi, avec la raison : en-têtes manquants, algorithme non pris en charge, clé introuvable, expiration, rejouement ou incohérence de signature. Ces raisons sont précieuses. Une hausse de "key not found" provenant d'un opérateur signifie souvent qu'il a fait tourner ses clés, et non qu'il vous attaque.

Que faut-il consigner à partir de la vérification de signature ?

Consignez l'origine de l'opérateur, l'identifiant de clé, les composants couverts, les paramètres temporels, le résultat et la raison de l'échec. Ne consignez pas les octets complets de la signature ni les corps de requête entiers. Conservez les journaux suffisamment longtemps pour étudier les tendances, et suffisamment peu longtemps pour respecter vos engagements de confidentialité.

Comment une signature vérifiée doit-elle changer votre politique ?

Une fois les requêtes vérifiables, vous pouvez les traiter différemment de manière utile. Notre article sur la conception de politiques d'application pour les humains et les agents IA couvre le modèle de politique plus large :

  • Autorisez les opérateurs vérifiés sur les routes en lecture seule avec une limite de débit plus élevée que pour l'automatisation non vérifiée.
  • Proposez-leur des réponses structurées, telles que JSON ou Markdown, au lieu de pages HTML complètes.
  • Gardez les actions à fort impact derrière votre autorisation normale. Une signature vérifiée vous indique quel client appelle, pas qu'il peut modifier un compte.

Commencez en mode observation. Mesurez la part de votre trafic automatisé qui est signée, quels opérateurs apparaissent, et à quelle fréquence la vérification échoue, avant que toute politique ne s'appuie dessus.

Où se place Proof of Actor

La vérification de signature est une source d'évidence parmi plusieurs. Proof of Actor la combine avec d'autres signaux et garde le fondement visible sur chaque résultat, afin qu'un agent vérifié ne soit jamais confondu avec un agent qui s'est seulement déclaré. Votre application décide de la suite.

Pour intégrer cela dans une application de production avec nos ingénieurs, demandez un accès anticipé. Pour avoir la vue d'ensemble de ce que l'évidence peut ou ne peut pas montrer, lisez Detection & Limitations.

Sources

Construisez-le avec nous.

Rejoignez le programme fondateur développeur de Proof of Actor.

Vous préférez en parler d'abord ? Parler aux fondateurs