# Comment fonctionne l'attribution d'un compte API partagé entre les sessions d'agents

Un compte API partagé peut parfois être le bon choix opérationnel. Les fournisseurs peuvent délivrer un seul jeton pour toute l'organisation, les limites de débit peuvent dépendre de ce compte, et le remplacer par un ensemble de clés presque identiques risque de multiplier les secrets sans améliorer le contrôle.

L'erreur consiste à traiter ce compte comme un acteur. C'est un principal d'identification externe, c'est-à-dire l'identité reconnue par le fournisseur. Lorsque plusieurs agents locaux l'utilisent, il faut conserver un second enregistrement indiquant quel processus local a envoyé chaque requête, pour quel travail il était autorisé à agir et comment il a obtenu l'autorisation d'utiliser l'identifiant. Sans cet enregistrement au moment de l'exécution, vous devrez deviner plus tard.

C'est moins spectaculaire que de donner un nom et un tableau de bord coloré à chaque agent. C'est pourtant ce qui tient lorsque deux agents se précipitent en même temps, que l'un réessaie, qu'une personne révoque une exécution en cours et que la page d'audit du fournisseur affiche seulement `automation-service`.

## Un compte fournisseur et un acteur sont deux identités différentes

Un compte fournisseur partagé répond à la question « Quel identifiant le fournisseur a-t-il accepté ? » L'attribution à un acteur répond à la question « Quel principal local a provoqué cette opération précise ? » Les réponses diffèrent souvent. Les forcer dans un seul champ produit une mauvaise piste d'audit.

Conservez au moins quatre identités distinctes :

- **Compte externe** : le locataire du fournisseur, l'utilisateur de service, le client OAuth ou l'identité de la clé API visible par l'API distante.
- **Session d'exécution** : un processus d'agent lancé, avec un identifiant de session nouveau et impossible à deviner.
- **Principal initiateur** : la personne, la tâche CI ou le service parent qui a démarré cette session.
- **Référence de travail** : l'incident, la demande de changement, le déploiement, le dépôt ou la tâche explicite qui explique la raison de l'appel.

Le compte externe peut rester stable pendant des mois. La session d'exécution, elle, ne le devrait pas. Une référence de tâche peut réapparaître dans plusieurs sessions. L'initiateur peut être une personne devant son clavier un jour, puis une compilation automatisée le lendemain. Chaque champ a une durée de vie différente et répond à une question différente.

Cette distinction ne relève pas du vocabulaire. Supposons que `vendor-prod` supprime un déploiement distant. Le fournisseur peut affirmer avec raison que `vendor-prod` a effectué la suppression. Votre enregistrement local doit montrer si la session `s_7f3...` provenait d'un agent de mise en production lancé par Maya, si elle avait reçu l'approbation pour cette exécution et si la suppression résultait d'un appel direct ou d'une nouvelle tentative après un délai d'attente. Un simple champ `actor=vendor-prod` dissimule tous les faits qui pourraient modifier la réponse à l'incident.

La RFC 8693 établit une distinction voisine dans la délégation OAuth. Elle sépare le sujet au nom duquel l'autorité existe de l'acteur actuel qui utilise la revendication JWT `act`. Elle précise aussi que les serveurs de ressources devraient prendre leurs décisions d'accès à partir des revendications de premier niveau du jeton et de l'acteur actuel, plutôt qu'à partir d'acteurs historiques imbriqués. C'est une limite utile pour les systèmes d'agents locaux : conservez la filiation pour les investigations, mais prenez les décisions d'autorisation à partir d'une identité de session actuelle et claire, pas d'une longue histoire ambiguë d'appels précédents.

N'appelez pas un compte fournisseur « l'agent ». Le compte peut être utilisé par des agents, des scripts, des opérateurs d'urgence et des tâches de migration. Donnez-lui un nom précis comme `external_principal`, puis faites de la session locale l'acteur de votre propre enregistrement d'audit.

## L'identité de session doit venir du lanceur

Le processus qui lance un agent doit créer son identité de session avant que l'agent puisse demander une action externe. Ne laissez pas l'agent la choisir. Un agent capable de sélectionner `session_id=release-approved` peut rendre un examen ultérieur trompeur.

Un enregistrement de session pratique contient assez d'informations pour identifier l'exécutable et assez de contexte pour expliquer le travail :

```json
{
  "session_id": "ses_01JQ6EXAMPLE3K5A",
  "started_at": "2026-07-22T15:04:18Z",
  "initiator": {
    "kind": "human",
    "id": "maya@example.test"
  },
  "agent": {
    "process_id": 48192,
    "binary_authority": "signed-local-agent",
    "launch_path": "/workspace/payments"
  },
  "work": {
    "kind": "change_request",
    "id": "CR-1842"
  },
  "parent_session_id": null
}
```

Les noms exacts des champs comptent moins que leur responsable. Le lanceur contrôle `session_id`, l'heure de démarrage, l'identité de l'exécutable et la session parente. Une personne ou le système appelant fournit la référence de travail, mais la passerelle doit enregistrer qui l'a fournie. L'agent peut proposer une description de sa tâche, mais ce texte ne doit jamais remplacer l'identité fournie par le lanceur.

Les seuls identifiants de processus constituent un indice fragile. Les systèmes d'exploitation les réutilisent, les journaux leur survivent et un identifiant de processus dit peu de choses sur la personne qui a lancé le binaire. L'autorité de signature du code est plus utile sur une machine de développement locale, car elle relie la décision d'approbation à la famille de processus signés. Même dans ce cas, enregistrez le chemin de l'exécutable et le contexte de lancement lorsque c'est possible. Une signature connue ne prouve pas que chaque invocation avait le même objectif.

Utilisez une nouvelle session pour chaque nouveau processus d'agent. Réutiliser une session parce que le numéro de tâche est identique transforme l'approbation d'une courte expérience en un bloc d'autorisation durable. Les agents qui restent longtemps actifs exigent une décision explicite : conserver une session et rendre sa durée visible, ou renouveler la session à une limite définie, par exemple une nouvelle pull request ou la reprise d'une session de terminal. Ne faites pas les deux discrètement.

## L'attribution doit être capturée avant l'injection de l'identifiant

L'endroit le plus sûr pour lier un acteur à une requête se trouve juste avant qu'un composant de confiance applique l'identifiant partagé et envoie la requête. Tout ce qui précède peut être modifié par l'agent. Tout ce qui suit peut manquer, être résumé ou être remplacé par le fournisseur.

Construisez une enveloppe d'appel que l'agent ne peut pas rédiger entièrement. L'agent fournit l'action demandée. La passerelle ajoute l'identité de session, la décision d'autorisation, l'identifiant d'appel et la référence de l'identifiant externe. Enregistrez l'enveloppe avant de transmettre la requête, puis ajoutez le résultat à son retour.

```json
{
  "call_id": "call_01JQ6F9K4W7D",
  "session_id": "ses_01JQ6EXAMPLE3K5A",
  "external_principal": "vendor-prod",
  "channel": "http",
  "request": {
    "method": "POST",
    "host": "api.vendor.example",
    "path_template": "/v1/deployments/{id}",
    "operation": "create_deployment"
  },
  "authorization": {
    "vault_unlocked": true,
    "session_authorized": true,
    "per_call_approval": false
  },
  "work_id": "CR-1842",
  "attempt": 1,
  "created_at": "2026-07-22T15:08:34Z"
}
```

Remarquez ce qui manque : le jeton bearer, le corps complet de la requête et une affirmation libre selon laquelle l'agent serait digne de confiance. Un journal qui stocke des secrets pour prouver l'attribution a échoué dans sa première mission. Un journal qui conserve chaque octet d'un corps peut également exposer des données client, du code source ou des données réglementées. Capturez un nom d'opération normalisé, un modèle de route, certains identifiants non secrets et une empreinte du contenu lorsque le contenu lui-même compte pour l'examen.

Cette conception sépare aussi l'intention de l'effet. Un agent peut demander `create_deployment`, mais le service distant peut renvoyer une erreur de validation. Le journal des appels doit conserver les deux faits. Vous pourrez ensuite répondre à la question de savoir si l'agent a tenté l'action, sans affirmer qu'elle a réussi.

Sallyport applique ce principe : l'agent se connecte par l'intermédiaire de son shim MCP, tandis que l'application conserve le secret et exécute l'action HTTP ou SSH. Les enregistrements de session et d'activité qui en résultent peuvent donc relier une exécution locale à l'utilisation d'un identifiant partagé, sans placer cet identifiant dans le contexte de l'agent.

## Les en-têtes fournis par l'agent sont des indices, pas des preuves

Les équipes ajoutent souvent des en-têtes comme `X-Agent-Name`, `X-Task-ID` ou `X-Run-ID`, puis considèrent le problème réglé. Ces champs peuvent aider à relier les journaux distants, mais un agent qui contrôle la requête peut aussi les omettre, les modifier ou les rejouer. Ce sont des étiquettes, pas une frontière d'autorité.

Vous pouvez transmettre des en-têtes d'attribution lorsque le fournisseur les accepte, à condition que la passerelle respecte trois règles. Premièrement, supprimez les versions de ces en-têtes réservés fournies par l'agent. Deuxièmement, générez les valeurs finales à partir de la session enregistrée et de l'enveloppe d'appel. Troisièmement, considérez la réception de l'en-tête par le fournisseur comme un indice supplémentaire, et non comme votre source de vérité.

Par exemple, réservez ce petit ensemble d'en-têtes à l'intérieur de la passerelle :

```text
X-Execution-Session: ses_01JQ6EXAMPLE3K5A
X-Action-Call: call_01JQ6F9K4W7D
X-Work-Reference: CR-1842
```

Ne placez pas une adresse e-mail, une invite, un nom de branche contenant des données client ou une commande brute dans un en-tête simplement parce que c'est pratique. Les en-têtes traversent les proxys, les systèmes de traçage, les rapports d'erreur et les outils d'assistance du fournisseur. Utilisez des identifiants opaques, puis associez-les à votre journal local protégé.

Certains fournisseurs rejettent les en-têtes inconnus, les suppriment ou ne les affichent pas dans leurs vues d'audit. C'est normal. L'API distante n'a pas à devenir votre système d'identité. Votre passerelle doit fonctionner même lorsque le fournisseur n'accepte que son mécanisme d'autorisation habituel.

Il existe un autre piège : un en-tête signé ne remplace pas la journalisation locale. Une signature de requête peut prouver qu'une passerelle a signé une requête, mais elle ne conserve ni l'approbation humaine, ni l'identité du processus, ni le contexte de la tâche, ni le résultat si vous n'enregistrez pas ces informations localement. Les signatures protègent les affirmations transportées. Elles ne créent pas à elles seules un dossier d'investigation.

## Les nouvelles tentatives ont besoin d'une filiation, pas d'un seul horodatage

Les agents autonomes réessaient. Les clients HTTP réessaient. Les commandes SSH peuvent être relancées après la déconnexion d'un terminal. Si votre piste d'audit écrit une seule ligne par action prévue, elle masque les mécanismes susceptibles de provoquer des changements en double. Si elle écrit uniquement les requêtes brutes, elle donne l'impression qu'une seule action prévue correspond à plusieurs décisions sans rapport.

Modélisez les deux niveaux. Attribuez un `operation_id` à l'opération prévue, puis un `call_id` différent à chaque tentative réseau. Associez les nouvelles tentatives à la tentative précédente et indiquez leur raison.

```json
{
  "operation_id": "op_01JQ6F8P0Z",
  "call_id": "call_01JQ6F9K4W7D",
  "attempt": 2,
  "retries_call_id": "call_01JQ6F79S2M1",
  "retry_reason": "connection_closed_before_response",
  "idempotency_key": "idem_94c2e1",
  "vendor_request_id": "req_8d71"
}
```

La valeur de `retry_reason` compte. Une réponse `429` signifie que le fournisseur a reçu la première requête et l'a refusée en raison de la limitation de débit. Un délai d'attente après le départ des octets de votre passerelle ne permet pas de savoir si le fournisseur a terminé l'action. Ces cas exigent des réponses opérationnelles différentes et ne doivent pas être réduits à `failed`.

Utilisez des clés d'idempotence pour les opérations qui créent ou modifient un état distant lorsque le fournisseur les prend en charge. Générez la clé dans la passerelle de confiance ou demandez au lanceur de la fournir avec l'enregistrement de travail. Ne laissez pas un modèle créer une nouvelle clé chaque fois qu'il révise son propre plan, car vous ne pourrez plus reconnaître une opération rejouée.

Voici un échec courant. La session A demande la création d'un déploiement, la connexion est interrompue et son client réessaie. Quelques instants plus tard, la session B commence avec la même référence de tâche, ne voit aucun déploiement visible et en demande un autre. Le fournisseur se retrouve alors avec deux déploiements. Un journal utile montre les deux sessions, les deux opérations prévues, leurs tentatives respectives et toute clé d'idempotence partagée. Un journal faible montre quatre lignes `POST /deployments` sous un seul compte de service et laisse l'équipe reconstituer le reste à partir des horodatages.

## L'approbation doit être liée à un processus, pas à un nom convivial

Une invite qui demande « Autoriser l'agent de mise en production à utiliser la production ? » semble raisonnable jusqu'à ce que deux agents de mise en production existent, l'un lancé depuis un dépôt approuvé et l'autre depuis un répertoire copié. Un nom est un texte de présentation. L'approbation doit être liée à la session d'exécution et à l'autorité du processus qui l'a créée.

L'approbation par session constitue un bon choix par défaut pour les agents qui effectuent plusieurs appels liés. Elle permet à l'opérateur de voir qui demande l'accès, puis évite de transformer une courte tâche en une page d'invites identiques. L'approbation doit expirer à la fin du processus. Un nouveau processus, même avec le même texte de tâche, doit demander une nouvelle approbation.

Utilisez une approbation par appel pour les opérations qui ont de lourdes conséquences ou une portée inhabituellement large. Elle ne remplace pas l'approbation de session. Elle répond à une question plus précise : cette utilisation de cet identifiant doit-elle être autorisée maintenant ? Une équipe qui demande une approbation par appel pour chaque lecture sans risque finira par approuver sans lire. Cette fatigue d'approbation est alors un choix de conception, pas une défaillance humaine.

Enregistrez la décision comme un événement doté d'une référence stable :

```json
{
  "approval_id": "apr_01JQ6G3C",
  "session_id": "ses_01JQ6EXAMPLE3K5A",
  "scope": "session",
  "decision": "approved",
  "decided_at": "2026-07-22T15:06:11Z",
  "process_authority": "signed-local-agent"
}
```

N'enregistrez pas un simple booléen sur chaque appel en prétendant qu'il prouve le consentement. Le booléen indique seulement qu'une approbation existait. L'événement d'approbation indique aux lecteurs quand elle a eu lieu, ce qu'elle couvrait et quelle session elle autorisait. Si un opérateur révoque ensuite la session, conservez cette révocation comme un nouvel événement. Supprimer l'ancienne approbation rend le dossier plus propre, mais l'incident plus difficile à comprendre.

## Les journaux d'audit du fournisseur doivent corroborer votre enregistrement

Les journaux du fournisseur sont utiles, mais ils décrivent généralement son propre modèle d'identité, pas le vôtre. Un jeton partagé peut apparaître comme un utilisateur de service, une application OAuth, une empreinte de jeton, une installation ou une adresse IP. Cela peut confirmer qu'un appel externe a eu lieu, mais indique rarement quelle session d'agent locale a choisi l'action.

La documentation de GitHub fournit un exemple concret de cette distinction. Les appels effectués avec un utilisateur d'application GitHub à l'aide d'un jeton serveur peuvent afficher l'utilisateur comme acteur de l'audit tout en indiquant le type d'accès programmatique associé à cette forme de jeton. Les événements d'audit GitHub Enterprise exposent également, pour de nombreux types d'événements, des champs tels que l'acteur, les informations du jeton, l'identifiant de requête et l'agent utilisateur. Ces éléments sont utiles comme preuves côté fournisseur, mais leur signification appartient au modèle d'autorisation de GitHub, pas à celui de votre session d'agent locale.

Reliez les enregistrements distants et locaux avec des corrélateurs stables lorsque c'est possible :

- Enregistrez l'identifiant de requête du fournisseur renvoyé dans un en-tête ou un corps de réponse.
- Enregistrez votre identifiant d'appel sortant et un nom d'opération normalisé.
- Notez le statut de la réponse, l'heure de fin et les identifiants sûrs des ressources.
- Conservez le principal externe utilisé pour l'appel.
- Gardez l'identifiant de session local comme identité d'exécution de référence.

Évitez de faire la corrélation principalement à partir de l'heure. Le décalage des horloges, le traitement asynchrone du fournisseur, les nouvelles tentatives et les files d'attente rendent un horodatage proche moins fiable qu'il n'y paraît. Les horodatages restent utiles pour réduire le périmètre d'une recherche, mais ne doivent pas décider de l'attribution lorsqu'un identifiant de requête ou une clé d'idempotence existe.

Certains fournisseurs peuvent délivrer des jetons délégués de courte durée, des jetons OAuth au nom d'un utilisateur ou des jetons d'installation d'application. Utilisez ces possibilités lorsqu'elles donnent au fournisseur une visibilité utile sur l'acteur et qu'elles correspondent à votre modèle de privilèges. GitHub, par exemple, documente la différence entre une application agissant au nom d'un utilisateur et d'autres types d'accès programmatique. Cette attribution côté fournisseur est meilleure qu'un jeton statique partagé, mais elle ne dispense pas d'identifier le processus local qui a lancé l'appel.

## Ne séparez les identifiants que s'ils créent une vraie frontière

Le conseil habituel est « donnez à chaque agent sa propre clé API ». Il est populaire parce qu'il est facile à expliquer et parce qu'il rend l'écran d'audit du fournisseur plus ordonné. Ce n'est pas toujours le bon contrôle.

Des identifiants distincts justifient leur coût opérationnel lorsqu'ils créent une frontière réelle. Il peut s'agir de portées différentes pour un agent de découverte en lecture seule et un agent de déploiement, d'une révocation indépendante côté fournisseur, d'une facturation séparée ou d'un enregistrement d'audit fournisseur qui identifie un principal distinct de manière exploitable par les intervenants. Si chaque clé possède la même portée étendue, le même responsable de rotation et le même chemin d'exécution local, vous avez surtout multiplié l'inventaire des secrets, sans améliorer réellement l'attribution.

Utilisez ce test avant de créer une nouvelle identité fournisseur :

1. Le nouvel identifiant peut-il recevoir moins de privilèges que l'identifiant partagé ?
2. Pouvez-vous le révoquer sans interrompre les travaux sans rapport ?
3. Le fournisseur l'enregistrera-t-il comme un acteur distinct, d'une manière utile aux personnes qui répondent aux incidents ?
4. Pouvez-vous le faire tourner et le retirer sans laisser de copies oubliées dans les outils locaux ?
5. Supprime-t-il une décision d'autorisation importante de la passerelle locale ?

Si la réponse est négative pour la plupart de ces questions, conservez le compte externe partagé et améliorez l'enregistrement de l'acteur local. Cette approche donne aux intervenants les informations dont ils ont réellement besoin : quelle session d'agent a effectué l'appel, à l'initiative de qui, pour quel élément de travail, avec quelle approbation et avec quel résultat.

Cet argument a ses limites. Si un identifiant donne des droits d'administration sur la production et qu'un agent expérimental n'a pas besoin de ce pouvoir, ne le partagez pas simplement parce que vos journaux sont excellents. L'attribution explique une action après coup. La limitation de portée détermine quelles actions peuvent avoir lieu.

## Un journal inviolable doit préserver l'ordre et les refus

Un enregistrement d'audit qui ne conserve que les appels fournisseur réussis offre un récit incomplet. Les appels refusés, les tentatives d'ouverture d'un coffre verrouillé, les approbations rejetées et les sessions révoquées expliquent souvent pourquoi un incident n'a pas empiré. Ils révèlent aussi qu'un agent a continué à demander une action après avoir perdu son autorité.

Écrivez un événement au démarrage de la session, lors de chaque décision d'autorisation, lors de la préparation d'un appel, à la fin de l'action externe et lors de la révocation ou de la fin d'une session. Reliez les enregistrements par leurs identifiants au lieu de recopier un grand objet mutable sur chaque ligne. La chaîne doit rendre la séquence visible sans obliger chaque lecteur à reconstituer l'état à partir d'un texte libre.

La preuve d'altération compte, car l'attribution locale est souvent la seule source permettant de distinguer des agents concurrents utilisant le même compte fournisseur. Si une personne ayant accès au système local peut modifier un enregistrement d'appel après un incident, l'équipe a recréé le même problème de confiance un niveau plus près. Les journaux chaînés par hachage aident les lecteurs à détecter les modifications, mais ne décident pas quels champs enregistrer. Il faut toujours un modèle d'événements complet.

Sallyport construit ses journaux Sessions et Activity à partir d'un même journal d'audit chiffré et chaîné par hachage. Il peut vérifier cette chaîne hors ligne avec `sp audit verify`. L'important n'est pas le nom de la commande, mais le fait qu'une approbation de session, une action précise et une révocation ultérieure puissent être vérifiées dans le même enregistrement ordonné.

Ne répondez pas par le libellé d'un compte fournisseur lorsqu'un responsable d'incident demande qui a effectué une action. Suivez l'enregistrement du principal externe à l'identifiant d'appel, de l'identifiant d'appel à la session d'exécution, de la session à l'initiateur et à l'approbation, puis de la réponse à l'identifiant de requête du fournisseur. Si l'un de ces liens manque, corrigez le point de capture avant que le prochain identifiant partagé ne devienne un mystère.
