Les agents IA locaux et les jobs CI ont besoin de modèles d’accès différents
Les agents IA locaux et les jobs CI ont besoin de modèles d’accès différents, car l’autorisation humaine, l’identité du workload et l’exposition des identifiants créent des risques différents.

Un poste de développeur et un runner de compilation peuvent tous deux exécuter des commandes shell, appeler des API et pousser du code. Les traiter comme un même environnement de sécurité conduit les équipes à placer un identifiant de déploiement dans un endroit où ni une personne ni un service ne peuvent correctement en rendre compte.
Les agents IA locaux fonctionnent dans un environnement accompagné. Une personne peut voir une demande, examiner un diff, refuser une requête inhabituelle et révoquer un processus qui commence à se comporter de manière suspecte. Les jobs CI s’exécutent parce qu’un événement s’est produit : push, pull request, tag, exécution planifiée ou déclenchement manuel. Le job a besoin d’une autorité vérifiable par une machine et liée à cet événement. Il ne peut pas attendre qu’un développeur approuve chaque appel, et il ne devrait pas hériter des accès permanents d’un développeur simplement parce que le workflow s’est exécuté.
Cette distinction n’a rien d’académique. Elle détermine si un agent reçoit un secret, si un jeton existe pendant quelques minutes ou quelques mois, ce qu’un journal d’audit doit indiquer et si une dépendance compromise peut se transformer en incident de production.
La présence humaine change le sens d’une autorisation
Une autorisation sur un poste de travail peut constituer un véritable contrôle de sécurité, car une personne est présente pour juger l’action immédiate. La demande doit décrire suffisamment bien l’appelant pour que ce jugement soit utile. « Un agent veut effectuer une requête HTTP » n’apporte pas beaucoup d’éléments. « Un processus signé par cette autorité, lancé dans cette session, veut utiliser l’identifiant de déploiement de production » donne à l’opérateur quelque chose de concret à accepter ou à refuser.
Ce modèle a une limite claire : l’autorisation suppose qu’une personne assume la responsabilité d’un processus en cours. Elle ne remplace pas l’identité. Si un processus malveillant peut usurper l’appelant approuvé, dissimuler la destination ou réutiliser une autorisation après modification de sa tâche, la demande n’est plus qu’une mise en scène.
Sur une machine de développement, je veux distinguer trois faits :
- le coffre protégé reste verrouillé jusqu’à ce que l’utilisateur local le déverrouille ;
- la première demande d’un nouveau processus exige une décision pour la session ;
- les identifiants sensibles peuvent exiger une décision à chaque utilisation.
Ces contrôles répondent à des questions différentes. Le verrou indique si une action peut avoir lieu. L’autorisation de session indique si ce processus peut agir pendant cette exécution. L’autorisation par appel indique si un identifiant donné est trop important pour être réutilisé sans précaution. Les équipes regroupent souvent ces trois contrôles derrière un unique bouton « autoriser l’accès de l’agent », puis découvrent qu’il a approuvé bien plus que ce que le développeur avait prévu.
Un agent local devrait aussi recevoir des résultats, pas les éléments d’un identifiant. S’il doit interroger une API, un composant extérieur à l’agent peut joindre l’identifiant, effectuer la requête et renvoyer la réponse. Ainsi, une injection de prompt dans l’agent ne peut pas simplement afficher une clé API dans un terminal, un patch ou une conversation. Masquer un secret après son entrée dans le contexte de l’agent n’offre pas la même protection. L’agent peut l’encoder, l’envoyer vers un autre hôte ou l’utiliser dans une requête avant que le système de masquage ne le voie.
Sallyport utilise ce modèle accompagné sur macOS : son coffre reste derrière une barrière absolue et peut exiger une autorisation pour une session de processus ou pour chaque utilisation d’un identifiant sélectionné. C’est logique lorsqu’un développeur est présent. Ce serait le mauvais mécanisme pour un runner sans surveillance.
Un job CI a besoin d’une identité qui résiste à l’examen
Un job CI ne peut pas fournir une intention humaine à la demande. Il a besoin d’une identité de workload, c’est-à-dire d’une identité dérivée de faits vérifiables sur le job plutôt que d’un secret copié dans son environnement.
Pour un workflow de déploiement, ces faits incluent généralement l’émetteur CI, le dépôt ou le projet, le commit ou la référence, l’identité du workflow, l’environnement et l’audience visée. Le service cible vérifie l’assertion signée et l’échange contre un identifiant courte durée. C’est l’intérêt de la fédération OIDC : le runner prouve l’origine du job sans transporter une clé cloud réutilisable dans son magasin de secrets.
GitHub Actions documente ce modèle avec son endpoint de jeton OIDC et la permission id-token: write. Le nom de cette permission peut prêter à confusion. Elle permet au workflow de demander un jeton d’identité, mais n’accorde pas à elle seule le droit de déployer. Le rôle cloud ou le service cible doit toujours refuser les jetons dont l’émetteur, l’audience, le sujet ou les autres revendications ne correspondent pas au workflow prévu.
C’est cette seconde partie qui fait échouer de nombreuses configurations. Un rôle qui accepte n’importe quel jeton provenant d’un dépôt a délégué trop d’autorité à tous les workflows admissibles de ce dépôt. Un aperçu de documentation, un workflow de publication et un déploiement en production ne devraient pas devenir équivalents simplement parce qu’ils partagent le même système de gestion du code.
Utilisez les revendications pour décrire une seule catégorie de jobs dans le rôle. La syntaxe exacte dépend du fournisseur CI et du cloud, mais la politique doit répondre à des questions simples :
- Quel dépôt peut demander ce rôle ?
- Quel fichier de workflow ou quel environnement protégé peut le demander ?
- Quelle branche, quel tag ou quelle condition de publication peut le demander ?
- Quelle audience l’assertion doit-elle nommer ?
- Combien de temps l’identifiant délivré peut-il rester utilisable ?
N’écrivez pas de conditions de politique que vous n’avez pas inspectées dans un vrai jeton. Affichez les revendications dans un environnement de test sûr, comparez-les à la politique de confiance et testez les refus. Les équipes testent les déploiements réussis, puis laissent le scénario dangereux, une pull request provenant d’une branche non fiable, au rang de simple hypothèse.
Les secrets et les identités ne résolvent pas le même problème
Un secret prouve qu’un élément est détenu. Une assertion d’identité formule une affirmation sur le workload qui a demandé l’accès. Les deux peuvent aboutir à un jeton porteur, mais leurs chemins d’échec sont très différents.
Un secret CI stocké ne garde généralement aucune trace de la raison pour laquelle le job l’a reçu. Si un workflow peut lire DEPLOY_TOKEN, un script modifié, une action compromise, un chemin de pull request malveillant ou une commande de journalisation peut utiliser ce jeton partout où ses permissions l’autorisent. La rotation limite la durée d’utilité du secret, mais ne limite pas le contexte de chaque utilisation.
La fédération courte durée ne rend pas la CI sûre par magie. Un job compromis peut encore utiliser son jeton valide pendant sa période de validité. Le gain vient de la réduction du rayon d’impact : l’attaquant doit exécuter un job admissible, respecter les règles de l’émetteur et des revendications, puis agir avant l’expiration de l’identifiant. Vous pouvez aussi révoquer ou modifier le rôle qui l’accepte sans rechercher chaque copie d’un secret.
Ne confondez pas identifiants courte durée et privilèges faibles. Un jeton valable dix minutes qui peut supprimer toutes les bases de données de production reste inacceptable. Les limites de temps réduisent la persistance ; le périmètre d’autorisation limite les dégâts. Il faut les deux.
L’accès d’un agent local pose le problème inverse. Un développeur peut utiliser le même outil local avec de nombreux dépôts et pour de nombreuses tâches. Une seule clé API trop large devient donc une cible intéressante pour un agent compromis. La conception locale la plus sûre conserve cette clé hors du contexte de l’agent et n’autorise que l’appel API ou la commande SSH précise que l’utilisateur a approuvée. Si le service externe prend en charge des jetons à permissions fines, utilisez-les également. Un coffre protégé ne peut pas corriger un jeton surpuissant après le départ de la requête.
Le raccourci dangereux : un jeton pour les deux environnements
Utiliser le jeton personnel d’un développeur dans la CI est tentant, car cela débloque rapidement un déploiement bloqué. C’est aussi l’une des pires façons d’effacer la responsabilité.
Un jeton personnel va souvent plus loin que nécessaire pour le pipeline. Il peut appartenir à une personne qui change d’équipe, quitte l’entreprise, l’utilise depuis son ordinateur portable et dispose d’accès définis par ses appartenances personnelles plutôt que par ses responsabilités de déploiement. Lorsqu’il apparaît dans la CI, le journal d’audit peut montrer que le jeton a agi, mais il ne peut pas dire honnêtement si l’action venait du développeur ou d’un job de publication.
L’erreur inverse existe aussi. Les équipes exposent un secret de déploiement CI à un agent local pour qu’il puisse « tester la même chose ». Elles donnent ainsi à du code généré de manière interactive une capacité de production sans surveillance, souvent avec moins de contrôles que le workflow de publication. La réponse à incident devient également pénible : l’agent a-t-il utilisé l’identifiant, un script shell s’en est-il servi ou une valeur copiée a-t-elle fui vers un autre outil ?
Donnez à chaque environnement sa propre frontière d’autorisation. Un développeur local peut disposer d’un accès interactif et révocable à un endpoint de développement. Un job de publication peut recevoir un rôle fédéré limité à un environnement de production protégé. Un job de pull request peut ne disposer d’aucune autorité d’écriture. Ce ne sont pas des contraintes à gommer. Ce sont les éléments qui permettront plus tard d’expliquer pourquoi une action a été autorisée.
Une règle de nommage utile consiste à faire apparaître dans le nom de l’identifiant l’acteur et le but. ci-release-prod-deploy renseigne bien davantage un réviseur que deploy-token. Mieux encore, le job CI ne stocke jamais un jeton portant ce nom. Il demande un rôle lié à son identité, dont la politique de confiance encode le même objectif.
L’état du runner transforme l’accès temporaire en résidu
Un job peut utiliser un identifiant temporaire et malgré tout laisser derrière lui un problème permanent. Les fuites courantes passent par les journaux, le traçage du shell, les répertoires personnels mis en cache, les couches Docker, les artefacts de l’espace de travail et les fichiers écrits par des actions tierces.
Les runners auto-hébergés exigent une vigilance particulière, car ils peuvent conserver leur état entre les jobs. Un job qui récupère du code non fiable peut installer un exécutable modifié, altérer un cache partagé, inspecter un espace de travail oublié ou attendre qu’un workflow privilégié réutilise l’hôte. Isoler les jobs par nom de dépôt ne suffit pas s’ils partagent le même compte du système d’exploitation, le même socket de conteneur ou le même système de fichiers.
Les runners éphémères suppriment une grande partie des résidus puisque le runner disparaît après le job. Ils ne dispensent pas de contrôler les entrées du workflow. Un job privilégié qui exécute des scripts provenant d’une pull request non fiable remet toujours son autorité à du code non fiable, même sur une machine neuve.
GitHub Actions avertit que pull_request_target s’exécute dans le contexte du dépôt de base et peut accéder aux secrets ou aux permissions d’écriture. Cet événement a un usage légitime : les mainteneurs peuvent vouloir ajouter une étiquette ou un commentaire à une pull request provenant d’un fork. Le problème apparaît lorsqu’un workflow déclenché par cet événement récupère le commit de tête de la pull request et exécute ses scripts. Le workflow vient alors de combiner des identifiants de confiance avec du code contrôlé par un attaquant.
Gardez un modèle simple :
- Exécutez le code non fiable des pull requests sans autorité de déploiement.
- Réservez l’accès aux environnements protégés aux références examinées et aux chemins de workflow contrôlés.
- Épinglez les actions tierces sur des références de commit immuables lorsque votre processus le permet.
- Gardez les secrets hors des caches, des artefacts et des sorties de diagnostic.
- Détruisez les instances de runner sensibles après le job.
Le premier et le quatrième point évitent davantage d’incidents réels que des conventions élaborées de nommage des jetons. Un identifiant parfaitement limité fuit tout de même si une commande shell l’affiche ou si un artefact contient son fichier de configuration.
Une identité de déploiement doit être lisible dans un seul fichier
Un workflow doit rendre visible la demande d’autorité externe. Cet exemple GitHub Actions demande une identité OIDC uniquement dans le job de déploiement et déclare un environnement de production. Il ne contient volontairement aucun identifiant cloud stocké.
name: deploy
on:
push:
tags:
- "v*"
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@<full-commit-sha>
- name: Request deployment identity
run: |
token=$(curl -sS \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=deploy.example.internal")
test -n "$token"
- name: Deploy
run: ./scripts/deploy.sh
En pratique, l’échange utilise généralement une action ou une CLI cloud plutôt que de conserver la réponse dans une variable shell. L’intérêt de l’exemple est la forme de la demande : GitHub fournit un jeton et une URL de demande à usage unique, le workflow demande une audience précise, puis la cible décide si cette identité peut assumer un rôle. Le runner ne doit jamais traiter le jeton d’identité obtenu comme un identifiant API générique.
La politique de confiance côté cloud doit refuser tout jeton dont les revendications n’identifient pas ce contexte de publication. Ne copiez pas un exemple de fournisseur en vous arrêtant là. Ces exemples commencent souvent par une politique large pour fonctionner chez de nombreux utilisateurs. Limitez le dépôt, l’environnement, la branche ou le tag et l’audience avant d’autoriser le rôle à modifier la production.
Si une cible ne peut pas valider directement OIDC, placez un petit broker d’identifiants devant elle. Le broker valide l’assertion CI, associe les revendications à des actions précisément définies, délivre un identifiant temporaire pour la cible et journalise cette association. Ne résolvez pas cette limitation en plaçant un secret permanent dans chaque workflow qui a besoin du service.
SSH rend cette différence particulièrement évidente
Les clés SSH se prêtent mal à un usage CI partagé. Une clé privée copiée dans un secret CI peut authentifier n’importe quel job autorisé à la lire, et sa partie publique n’indique généralement pas quelle révision du dépôt a demandé la connexion. Les commandes forcées, les restrictions de source et les comptes séparés peuvent limiter les dégâts, mais l’identité de base reste une clé privée réutilisable.
Pour le travail local, SSH peut nécessiter une barrière interactive, car un développeur peut voir qu’un agent veut se connecter à un hôte précis. La clé privée doit rester dans un stockage protégé et l’agent doit demander une commande précise plutôt que d’obtenir un accès direct à la clé. L’hôte doit toutefois appliquer ses propres permissions de compte et restrictions de commande. L’autorisation locale contrôle le point de départ, elle ne rend pas sûre une commande distante dangereuse.
Pour la CI, préférez des certificats SSH courte durée lorsque votre autorité de certification SSH et votre parc de cibles les prennent en charge. Le job utilise la fédération de workload pour demander un certificat à validité courte, avec un principal limité et éventuellement une commande forcée. La cible vérifie l’autorité de certification plutôt que d’accepter indéfiniment une clé privée copiée.
Si les certificats ne sont pas disponibles, utilisez un compte de déploiement distinct et une clé privée dédiée à chaque catégorie de déploiement. Limitez ce compte dans authorized_keys et sur le serveur. Une restriction minimale ressemble à ceci :
command="/usr/local/bin/receive-release",no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAA... ci-release
Cette ligne empêche la clé d’ouvrir un shell interactif arbitraire et force le serveur à exécuter un seul programme de réception. Elle n’identifie pas à elle seule le dépôt source. Associez-la à une clé courte durée fournie par un broker ou à un autre signal de workload vérifié si la décision de publication dépend du contexte du dépôt.
Ne définissez pas StrictHostKeyChecking=no dans un script CI pour faire fonctionner SSH. Cela désactive la vérification de l’identité du serveur précisément là où les runners sont faciles à rediriger. Fournissez les clés d’hôte connues par un mécanisme contrôlé, faites-les tourner délibérément et échouez lorsqu’un changement inattendu est détecté lors de la vérification.
Les journaux d’audit doivent répondre à des questions différentes
Un journal qui dit simplement « déploiement réussi » est une sortie opérationnelle, pas une preuve de sécurité suffisante. Vous devez pouvoir reconstituer qui ou quoi a reçu l’autorité, quelle demande a été effectuée et si le journal lui-même a été modifié depuis.
Pour un agent local accompagné, enregistrez l’identité du processus local, le début de la session, la décision d’autorisation, le libellé de l’identifiant ou la catégorie d’action, la destination, l’heure de la demande et le résultat. N’enregistrez ni les valeurs secrètes ni les charges utiles sensibles complètes. Une signature de processus ou une autorité de signature de code est plus utile qu’un nom arbitraire, car les noms sont faciles à copier.
Pour la CI, enregistrez le fournisseur CI, l’identifiant d’exécution, le dépôt, la référence du workflow, le SHA du commit, l’acteur ayant déclenché l’exécution lorsque cette information est disponible, la classe du runner, le sujet et l’audience OIDC, le rôle cible, les paramètres d’action qu’il est possible de conserver sans risque et le résultat. Cela permet à un enquêteur de distinguer une publication taguée d’une exécution relancée manuellement, ainsi qu’un workflow de confiance d’une attribution trop large accidentelle.
Conservez les journaux d’autorisation séparément des journaux applicatifs. Ces derniers peuvent être modifiables, échantillonnés ou supprimés dans le cadre des opérations normales. Une piste d’autorisation doit être en ajout uniquement et faire l’objet d’une vérification indépendante. Le chaînage des hachages détecte les modifications de la séquence si vous conservez la chaîne et la comparez aux enregistrements attendus ; il n’empêche pas un attaquant de bloquer les futurs enregistrements. Lorsque l’architecture le permet, envoyez donc les journaux hors de la machine compromise.
Sallyport projette les journaux de session et d’action individuelle depuis un journal d’audit chiffré, chaîné par hachage et protégé en écriture, et sp audit verify vérifie cette chaîne hors ligne sans clé de coffre. C’est une preuve utile pour les actions d’agents accompagnés. La CI doit produire une piste équivalente, liée au workload, dans les systèmes qui délivrent et acceptent ses identités.
La fatigue liée aux autorisations signale un défaut de conception locale
L’autorisation à chaque appel peut protéger les identifiants locaux à fort impact, mais une demande pour chaque lecture anodine apprend aux utilisateurs à accepter les demandes sans les lire. Quand cela se produit, le contrôle devient un rituel et un attaquant n’a plus qu’à attendre une tâche habituelle.
Utilisez l’autorisation par appel pour les actions dont les conséquences sont difficiles à annuler : écritures en production, modifications DNS, administration à l’échelle de l’organisation, push forcés dans le système de gestion du code ou commandes sur des hôtes sensibles. Utilisez l’autorisation de session pour les actions répétitives et peu risquées qu’un développeur peut raisonnablement déléguer pendant une exécution d’agent. Gardez la barrière du coffre séparée afin qu’une machine verrouillée refuse toute action, même si une ancienne autorisation de session existe encore.
La CI connaît sa propre forme de fatigue : les barrières d’approbation manuelle qui apparaissent sur chaque job et finissent par être acceptées parce que le calendrier de publication devient prioritaire. L’autorisation d’un environnement protégé peut convenir à un déploiement en production, mais elle doit approuver un artefact et une cible clairement identifiés. Elle ne doit pas compenser un workflow qui accepte du code non fiable, un rôle aux revendications trop larges ou un runner dont l’état est inconnu.
Une vérification pratique consiste à poser une question inconfortable : si cette autorisation était automatiquement validée pendant un mois, que pourrait-on exécuter ? Pour les agents locaux, réduisez la surface d’action approuvée jusqu’à obtenir une réponse acceptable. Pour la CI, retirez l’approbation des actions machine ordinaires et liez plutôt l’autorité à l’identité du job.
Séparez les chemins avant la prochaine demande d’identifiant
Lorsqu’un agent ou un pipeline demande un accès, classez l’appelant avant de choisir un mécanisme de secret. Une personne est-elle présente et capable d’examiner l’action ? S’agit-il d’un workload répétable dont vous pouvez valider les revendications ? L’appelant peut-il recevoir un résultat limité plutôt qu’un identifiant ? Les permissions de la cible correspondent-elles à une tâche nommée ?
Si l’appelant est un agent local, protégez les identifiants hors de son contexte, conservez un parcours d’autorisation utile et journalisez chaque action sous une forme que le développeur peut examiner. Si l’appelant est la CI, utilisez une fédération courte durée, liez les rôles aux revendications du workflow, isolez le runner et consignez les faits sur le workload qui ont autorisé l’appel.
Ne laissez pas un jeton partagé effacer cette séparation simplement parce qu’il fait gagner du temps aujourd’hui. Le prochain incident vous obligera à la reconstruire tout en essayant de déterminer si l’action venait d’une personne, d’un agent ou d’un runner.
FAQ
Pourquoi les agents de programmation IA et les pipelines CI doivent-ils utiliser des identifiants différents ?
Un agent local s’exécute dans la session de travail d’un développeur, où une personne peut examiner une demande et interrompre le processus. Un job CI s’exécute sans surveillance après un événement dans le système de gestion du code. Ses permissions doivent donc provenir d’une identité de workload strictement limitée et expirer avec le job.
Puis-je utiliser le même processus d’autorisation pour la CI et les agents locaux ?
Non. Une demande d’autorisation humaine prouve seulement qu’une personne a cliqué à un instant donné. Elle ne décrit ni le dépôt, ni le commit, ni le workflow, ni le rôle cloud visé par le job. Utilisez l’autorisation pour le travail local interactif et une autorisation liée au workload pour la CI.
Les clés API longue durée sont-elles parfois acceptables dans la CI ?
C’est généralement un mauvais compromis. Un secret longue durée transforme tout runner compromis, toute fuite dans les journaux, toute archive de cache ou toute dépendance malveillante en voie d’accès durable. Préférez des jetons courte durée créés à partir d’une assertion OIDC, avec des contraintes sur le dépôt, la référence, le workflow et l’audience.
Qu’est-ce que l’identité de workload OIDC dans la CI ?
Un jeton de workload OIDC est une assertion du fournisseur CI concernant le job en cours. Un service cloud ou de gestion des secrets vérifie cette assertion et délivre un identifiant temporaire pour un rôle précis, au lieu de demander au job de transporter un secret cloud stocké.
Un agent IA local devrait-il parfois voir une clé API ?
Dans la mesure du possible, il ne devrait recevoir aucun identifiant. Donnez à l’agent local une interface d’action qui exécute la requête en dehors de son contexte et lui renvoie le résultat, tout en conservant les éléments API ou SSH dans un stockage local protégé.
Comment empêcher un job CI d’accéder au mauvais environnement ?
Non. Le workflow doit déclarer chaque destination externe dont il a besoin, et chaque destination doit avoir sa propre identité et ses propres permissions. Un jeton de déploiement générique est pratique jusqu’au jour où une compilation de documentation compromise peut déployer en production.
Quel est le principal risque de sécurité des runners CI auto-hébergés ?
Considérez l’état du runner comme un résidu hostile. Utilisez des runners éphémères pour les jobs sensibles, verrouillez les dépendances, évitez de restaurer des répertoires contenant des secrets, masquez les journaux et partez du principe qu’un job privilégié peut être détourné par le code qu’il a récupéré.
Comment auditer un identifiant CI ?
Commencez par examiner la source de l’identifiant. Vérifiez ensuite l’audience, la revendication de dépôt ou de projet, la contrainte de branche ou d’environnement, la durée de validité et les permissions cibles. Si vous ne pouvez pas expliquer chacun de ces champs, le jeton bénéficie d’un niveau de confiance supérieur à celui que mérite le job.
Que doit enregistrer un journal d’audit pour les actions d’un agent ?
Séparez les enregistrements. Le journal d’une session interactive doit identifier le processus local et chaque action approuvée. Les enregistrements CI doivent associer chaque action à l’identifiant d’exécution du fournisseur, à la révision du dépôt, au workflow, à la classe du runner et au rôle délivré. Un simple message de réussite n’est pas une piste d’audit.
Sallyport peut-il fonctionner dans un runner CI Linux ?
Utilisez une identité cloud gérée, un broker de déploiement dédié ou un compte de service strictement limité derrière une fédération courte durée. Sallyport est conçu pour le côté macOS accompagné de cette séparation, pas comme service d’identifiants CI sans interface.