# Des agents IA qui ouvrent des pull requests avec des contrôles sûrs

Les agents IA qui ouvrent des pull requests via une API peuvent faire gagner un temps précieux aux équipes d'ingénierie, mais seulement si le dépôt traite chaque modification générée comme une contribution non fiable dont l'auteur peut être identifié. La limite utile est simple : un agent peut préparer une modification proposée ; les personnes et les contrôles du dépôt décident si elle a sa place dans le code.

J'ai vu des équipes rendre ce processus dangereux en se concentrant sur la qualité du code produit par le modèle et en négligeant les permissions qui l'entourent. Les incidents les plus graves sont souvent très banals. Une tâche destinée à un service de staging aboutit dans un dépôt de production. Un jeton trop large accorde discrètement un accès en écriture à tous les projets. Après un délai d'attente, un agent ouvre dix pull requests presque identiques. Quelqu'un en fusionne une parce que son titre semble plausible.

Une configuration sûre ne suppose pas que l'agent sera prudent. Elle limite les endroits où il peut agir, fait passer la modification par la revue habituelle et laisse une trace assez complète pour permettre une enquête ultérieure.

## Le périmètre du dépôt doit être explicite et imposé par la machine

Un agent doit recevoir des permissions pour un ensemble nommé de dépôts, et non un périmètre générique couvrant toute l'organisation que quelqu'un prévoit de réduire plus tard. Le périmètre répond à une question concrète : dans quels dépôts ce processus peut-il lire, écrire des branches et ouvrir des pull requests ?

Conservez la liste d'autorisation en dehors des instructions de l'agent. Les prompts peuvent guider son comportement, mais ils n'imposent pas l'autorisation. Le composant qui détient l'identifiant du dépôt ou effectue la requête API doit refuser tout dépôt absent de la liste.

Pour chaque dépôt autorisé, définissez les branches de base permises et l'espace de noms d'écriture autorisé. Voici un exemple utile :

```yaml
repositories:
  - name: acme/payments-api
    base_branches: ["main", "release/2025.1"]
    write_branch_prefix: "agent/"
    pull_request_drafts: true
  - name: acme/docs
    base_branches: ["main"]
    write_branch_prefix: "agent/"
    pull_request_drafts: false
```

Ce fragment empêche un incident courant : un agent reçoit la demande de « corriger le texte du paiement », trouve un fichier similaire dans un dépôt qu'il peut parcourir et écrit à cet endroit parce que son identifiant l'y autorise. La liste d'autorisation transforme cette erreur en requête refusée, plutôt qu'en tâche de nettoyage pour quelqu'un d'autre.

Le périmètre couvre aussi les opérations sur le dépôt. La plupart des agents doivent lire des fichiers, créer une branche, pousser des commits, lire les vérifications et créer ou mettre à jour une pull request. Ils ont rarement besoin de modifier les paramètres du dépôt, d'enregistrer des webhooks, de changer les règles de protection des branches, d'ajouter des clés de déploiement, de gérer les membres ou de fusionner des modifications. N'accordez pas ces permissions simplement parce qu'elles sont incluses dans un jeton large et pratique.

Utilisez une identité de bot distincte plutôt que le jeton d'accès personnel d'un développeur. Un jeton personnel rend l'attribution floue, résiste mal aux changements de poste et possède souvent des permissions que personne ne se souvient d'avoir accordées. Une identité de bot vous donne un acteur unique à suspendre en cas de comportement anormal.

L'accès en lecture mérite la même attention que l'accès en écriture. Un agent capable d'inspecter tous les dépôts privés peut exposer du code source ou de la configuration dans ses journaux, son contexte de tâche ou ses réponses. Donnez-lui le plus petit ensemble de dépôts utile, même s'il ne reçoit jamais d'identifiant d'écriture direct.

## Un préfixe de branche est une limite d'exécution, pas une préférence de nommage

L'agent doit créer des branches uniquement sous un préfixe dédié tel que `agent/`, et le serveur du dépôt doit imposer cette restriction. Une convention écrite dans un prompt finira par être contournée, à cause d'un appel d'outil mal formé, d'un bug de nouvelle tentative ou d'un agent cherchant à satisfaire une tâche trop large.

Protégez `main`, les branches de release, les branches d'environnement et toute branche qui déclenche un déploiement automatique. L'agent ne doit pas pouvoir y pousser, effectuer un push forcé ou modifier les règles qui les protègent.

Rendez les noms de branches suffisamment déterministes pour faciliter l'enquête et suffisamment uniques pour éviter les collisions. Incluez une référence à la tâche et un court suffixe aléatoire ou identifiant d'exécution :

```text
agent/OPS-1842-retry-payment-7f3a
```

Ne laissez pas l'agent utiliser directement les titres des tickets comme noms de branches. Ces titres peuvent contenir des secrets, des noms de clients, des caractères dangereux ou une formulation trompeuse. Générez le nom de branche dans le contrôleur, puis transmettez-le à l'agent comme valeur immuable.

Le commit de base doit lui aussi suivre une règle explicite. Au début d'une exécution, le contrôleur doit résoudre la branche de base approuvée en SHA de commit et l'enregistrer. L'agent crée sa branche depuis ce SHA, et non depuis ce que `main` signifie après une longue génération de code. Cela ne supprime pas la dérive, mais la rend visible et reproductible.

Une branche ne doit contenir que les commits associés à la tâche qui lui a été attribuée. Pas de balayage de formatage opportuniste, de mise à jour de dépendance sans rapport ni tentative de « nettoyer » le code voisin parce qu'il semblait étrange. Les modifications générées paraissent souvent convaincantes, et les réviseurs manqueront plus facilement les changements sans rapport lorsqu'ils se trouvent dans un patch par ailleurs raisonnable.

Définissez un budget de modification avant le début du travail. Il peut limiter le nombre de fichiers modifiés, le nombre total de lignes ou les chemins situés hors du composant demandé. Ce plafond ne mesure pas à lui seul le risque. C'est un signal d'arrêt qui indique à l'agent de demander une nouvelle tâche au lieu de transformer discrètement une petite réparation en réécriture de tout le dépôt.

## La création d'une pull request nécessite une transaction API vérifiée

Une réponse HTTP réussie ne prouve pas que l'agent a ouvert la bonne pull request. Le contrôleur doit vérifier le dépôt, la branche source, la branche de base, le SHA du commit et l'identifiant de la pull request renvoyé avant d'annoncer la réussite.

Pour une API de type GitHub, les champs importants sont le titre proposé, `head`, `base`, le corps et le statut draft. Le point d'accès exact varie selon la forge, mais les contrôles de sécurité restent les mêmes :

```json
{
  "title": "OPS-1842: retry transient payment gateway failures",
  "head": "agent/OPS-1842-retry-payment-7f3a",
  "base": "main",
  "body": "Task: OPS-1842\nBase commit: 4b2c...\nTests: unit payment retry suite\nLimits: no configuration changes",
  "draft": true
}
```

Avant d'envoyer cette requête, interrogez la branche et vérifiez que son SHA de pointe correspond au commit enregistré par l'exécution. Après la réponse, récupérez la pull request et comparez son `head`, son `base` et son état avec la requête. Enregistrez le numéro immuable ou l'identifiant de nœud de la pull request fourni par la plateforme, et pas seulement son URL.

Les nouvelles tentatives nécessitent un traitement particulier. Les délais d'attente réseau créent le problème classique des pull requests en double : le serveur peut avoir créé la pull request 418 alors que le client n'a pas reçu la réponse et réessaie. Conservez dans votre contrôleur un enregistrement d'idempotence contenant l'identifiant de tâche, le dépôt, la branche, le SHA de base et le numéro de la pull request. Lors d'une nouvelle tentative, recherchez la branche et la pull request existantes avant d'appeler la création.

N'utilisez pas un titre de tâche comme seule valeur d'idempotence. Une demande récurrente telle que « mettre à jour la documentation générée » entrerait en collision avec une exécution précédente. L'identifiant d'exécution doit identifier une exécution unique, tandis que l'identifiant de tâche aide à relier les travaux associés.

Les pull requests en brouillon constituent une bonne valeur par défaut pour le travail des agents. Elles indiquent aux réviseurs que la modification existe, mais qu'elle n'a pas encore franchi le propre seuil d'achèvement déclaré par son créateur. Un agent ne peut marquer une pull request comme prête qu'après l'exécution des commandes obligatoires et l'enregistrement de leurs résultats. Si votre dépôt n'utilise pas les brouillons, appliquez une étiquette telle que `agent-created` via un contrôleur fiable, et non via du texte composé par l'agent.

## L'affectation des réviseurs doit suivre la propriété et le risque

Le premier réviseur doit être déterminé par les règles de propriété du dépôt, et non par un agent qui devine qui semble compétent. La documentation de CODEOWNERS de GitHub décrit une correspondance entre fichiers et propriétaires qui peut demander une revue pour les chemins modifiés. GitLab propose des mécanismes comparables d'approbation et de propriétaire de code. Ces fichiers sont utiles pour le routage, mais ils ne rendent pas automatiquement la revue obligatoire si la protection des branches ou les règles de fusion ne l'exigent pas.

Cette distinction compte. Un dépôt peut afficher une demande adressée à un propriétaire de code tout en autorisant une fusion sans son approbation, selon sa configuration. Traitez le routage et l'application de la règle comme deux contrôles distincts. Vérifiez-les avec une pull request de test volontairement non autorisée avant de faire confiance à la politique.

Utilisez la liste des fichiers modifiés après le commit final, et non les chemins que l'agent prévoyait de modifier. Un patch généré peut atteindre tardivement une bibliothèque partagée, un répertoire de déploiement ou un dossier de migration. Le calcul des réviseurs doit porter sur ce qui a réellement changé.

Ajoutez une personne responsable lorsque la tâche concerne des zones où les règles de propriété sont trop larges ou absentes. Cette personne porte l'intention de la tâche. Un propriétaire de code peut confirmer que l'implémentation convient à un composant ; la personne responsable peut confirmer que le comportement demandé convient au produit. N'affectez pas une douzaine de personnes simplement parce que le diff traverse plusieurs frontières. Les longues listes produisent le résultat habituel : chacun suppose qu'un autre réviseur s'est occupé du point difficile.

Certains chemins doivent imposer un parcours plus strict, par exemple les migrations de base de données, le code d'autorisation, les définitions de build et de release, les manifestes de dépendances, la configuration d'infrastructure et les artefacts générés. La bonne réponse n'est pas toujours « bloquer l'agent ». C'est souvent « exiger le propriétaire qui comprend les conséquences ». Une migration peut réussir les tests unitaires tout en rendant une restauration impossible.

Affectez les réviseurs via l'API uniquement après la création de la pull request et vérifiez l'affectation obtenue. Si un groupe de propriétaires ne peut pas recevoir de demande, le contrôleur doit marquer la pull request comme bloquée plutôt que de la confier discrètement à un développeur choisi au hasard. Un remplacement silencieux transforme une règle de propriété en simple décoration.

## Les tests décrivent des preuves, l'approbation décide de l'acceptation

L'agent doit préciser exactement ce qu'il a exécuté, ce qu'il n'a pas exécuté et pourquoi. « Les tests ont réussi » ne veut rien dire sans les commandes, le code de sortie et le SHA du commit testé. Conservez également ces preuves en dehors du texte de la pull request, car un agent peut modifier ce texte plus tard.

Utilisez un petit rapport structuré pour chaque exécution :

```json
{
  "run_id": "run_01J...",
  "repository": "acme/payments-api",
  "head_sha": "8c71...",
  "commands": [
    {"command": "npm test -- payment-retry", "exit_code": 0},
    {"command": "npm run lint", "exit_code": 0}
  ],
  "not_run": ["integration suite requires payment sandbox approval"]
}
```

Le contrôleur doit refuser le passage à l'état prêt pour revue lorsque les preuves mentionnent un SHA différent de celui en tête de branche. Cela détecte une séquence subtile mais fréquente : l'agent exécute les tests, apporte encore une « petite » correction, puis ouvre la pull request sans rien relancer.

Les vérifications obligatoires doivent rester du côté du dépôt. L'agent ne doit pas pouvoir les ignorer, approuver sa propre pull request, modifier la protection des branches ou fusionner. Une vérification peut établir qu'une commande connue a réussi. Elle ne peut pas établir qu'une nouvelle règle d'autorisation est correcte, que le besoin a été compris ou que la tâche aurait dû être tentée dans ce dépôt.

Ne remplacez pas la revue du diff par un résumé généré. Les bons résumés aident les réviseurs à s'orienter, mais ce sont les affirmations de l'auteur. Les réviseurs doivent voir les modifications réelles, les tests, le contexte du ticket concerné et les omissions volontaires.

## Chaque modification créée doit laisser une trace d'audit qui résiste aux incidents

Une URL de pull request n'est pas un journal d'audit. Elle peut disparaître lorsque les dépôts sont déplacés, les branches supprimées, les accès modifiés ou la description éditée. Conservez un relevé d'événements en ajout seulement, permettant de répondre à ces questions : qui a lancé l'exécution, quel processus a effectué chaque appel, quel dépôt a changé et qu'a renvoyé le système ?

Enregistrez au minimum :

- un identifiant d'exécution et la référence du ticket ou de la tâche d'origine ;
- l'identité du bot et celle du processus agent authentifié ;
- le dépôt, la branche de base, le SHA de base, la branche source et chaque SHA de commit créé ;
- le type de requête API, l'identifiant immuable de la pull request, les horodatages et le statut du résultat ;
- les demandes de revue, les approbations, les résultats des vérifications, les événements de fermeture, de fusion ou de rejet.

Ne placez pas par défaut dans le journal d'audit des identifiants en clair, des fichiers source complets ou des prompts de tâche arbitraires. Les enquêteurs ont besoin de faits d'action fiables, pas d'une copie incontrôlée supplémentaire de données sensibles. Si vous conservez le contenu du patch, enregistrez une empreinte et appliquez vos règles habituelles de conservation et d'accès.

Il est utile de distinguer le journal d'activité du journal des décisions. Le premier indique qu'un appel API a créé la pull request 418. Le second indique qui a approuvé la session de l'agent, qui a modifié son autorisation et qui l'a révoquée. En cas d'incident, les deux sont importants. Vous devez savoir ce qui s'est passé et pourquoi l'acteur disposait de l'autorité nécessaire à ce moment-là.

Sallyport peut conserver les sessions des agents ainsi que les appels HTTP ou SSH individuels dans des journaux projetés depuis son journal d'audit chiffré et chaîné par hachage. La commande `sp audit verify` vérifie la chaîne hors ligne sur le texte chiffré. Cette approche convient lorsque l'agent demande une action sans jamais recevoir lui-même l'identifiant du dépôt.

Le chaînage par hachage rend les modifications ultérieures détectables ; il ne rend pas complet un relevé d'événements faible. Enregistrez les identités du dépôt et des commits au moment de l'action. Un relevé parfaitement vérifié de « requête HTTP envoyée » ne vous dira pas si la requête a créé la mauvaise pull request.

## Gardez les identifiants hors de l'agent et séparez création et fusion

L'agent ne doit jamais détenir un jeton de dépôt étendu dans son prompt, son environnement, un fichier de travail ou la sortie d'un outil. Dès que le jeton entre dans ce contexte, il peut fuiter par les journaux, l'historique du shell, les messages d'erreur, des transcriptions copiées ou une instruction qui persuade l'agent de l'afficher. Une suppression ultérieure ne remet pas véritablement le secret sous contrôle.

Utilisez une passerelle d'action ou un contrôleur limité qui accepte une demande précise : créer une branche dans ce dépôt, pousser ces commits vers ce préfixe autorisé, créer une pull request en brouillon vers cette branche de base approuvée ou demander ces réviseurs. La passerelle injecte les identifiants et renvoie le résultat. Elle doit refuser les appels qui ne respectent pas le périmètre déclaré.

Les droits de création et de fusion sont différents. Un service capable de créer une pull request peut proposer des milliers de mauvaises modifications. Un service capable de fusionner peut en envoyer une seule dans la production. Ne les combinez pas pour rendre une démonstration plus fluide. Conservez l'action de fusion sous le contrôle des règles de protection des branches et d'une personne responsable, même lorsque l'agent a produit un patch irréprochable.

L'autorisation par exécution est également préférable à un processus d'agent approuvé en permanence. Les outils d'agent peuvent lancer des sous-processus, être réutilisés dans des tâches inattendues et rester actifs après la fin de la tâche qui justifiait l'accès. Donnez à un processus une session limitée, enregistrez l'approbation et rendez la révocation immédiate.

## Un exercice d'échec révèle les lacunes que le texte des politiques dissimule

Effectuez un exercice contrôlé avant de vous fier à la création automatisée de pull requests. Utilisez un dépôt de test ou une politique de branche temporaire et donnez à l'agent une tâche qui tente de franchir chaque limite. L'objectif est de vérifier les refus et leur enregistrement, pas d'admirer une démonstration du parcours nominal.

Commencez par un dépôt autorisé et un dépôt interdit. Vérifiez que le contrôleur peut créer la pull request en brouillon attendue dans le premier et refuse la seconde avant l'apparition de toute branche. Tentez ensuite un push vers `main`, un push forcé vers une branche `agent/` et une pull request vers une branche de release non approuvée. Examinez les événements du dépôt, pas uniquement les messages du contrôleur.

Simulez ensuite un délai d'attente après l'arrivée de la requête de création auprès de l'API du dépôt. Redémarrez l'exécution et vérifiez qu'elle retrouve la pull request d'origine au lieu d'en ouvrir une nouvelle. Modifiez la branche après l'enregistrement des résultats de test et vérifiez que le passage à l'état prêt s'arrête à cause du décalage de SHA.

Enfin, révoquez la session de l'agent pendant une exécution et tentez un nouvel appel API. L'appel doit échouer, le refus doit apparaître dans le relevé des décisions et aucun identifiant ne doit être visible dans la sortie de l'agent. Si l'un de ces tests dépend de l'intervention d'une personne qui remarquerait un message de chat, le contrôle n'existe pas encore.

La première mesure concrète consiste à inventorier le jeton utilisé aujourd'hui par votre agent. Listez chaque dépôt qu'il peut toucher, chaque branche qu'il peut modifier et sa capacité éventuelle à fusionner ou à changer les paramètres. La plupart des équipes découvrent que le jeton est plus large que nécessaire. Réduisez ce périmètre avant de demander à l'agent de produire une nouvelle pull request.
