# Autorisation des processus agent : révoquer l'accès des processus forkés

Les agents autonomes s'arrêtent rarement aussi proprement que le laissent penser leurs journaux de tâche. Un agent peut terminer une demande de programmation, afficher un joyeux message de réussite, puis laisser derrière lui un formateur, un outil de test, un tunnel, un shell ou un processus auxiliaire qui continue à fonctionner. Si ce processus résiduel peut encore appeler des API de production ou ouvrir des sessions SSH, la tâche ne s'est pas réellement terminée là où votre interface indique qu'elle a pris fin.

La solution n'est pas de supprimer les processus à la moindre occasion. Le nettoyage des processus est nécessaire, mais ce n'est pas un système d'autorisation. Définissez séparément la durée de vie de l'autorité et celle des processus Unix, rendez cette limite observable et révoquez l'accès avant de commencer à traquer les descendants. J'ai vu des équipes considérer un PID parent comme une preuve de confinement. Dès qu'un agent peut invoquer un shell, cela ne prouve plus grand-chose.

## L'arbre des processus ne définit pas une limite d'autorisation

Une relation parent-enfant indique qui a créé un processus. Elle ne dit pas si ce processus doit conserver son autorité après la fin du parent. Ce sont deux questions différentes. Les confondre produit le scénario classique : quelqu'un annule une tâche d'agent, voit disparaître le parent, puis découvre cinq minutes plus tard qu'un enfant effectue encore des requêtes.

Unix offre plusieurs façons de sortir de la structure prévue. Un enfant peut créer un nouveau processus à son tour. Il peut créer une nouvelle session avec `setsid`. Il peut demander à un gestionnaire de services de le superviser. Il peut rester dans un pipeline shell dont un seul élément reçoit le signal. Il peut aussi simplement survivre parce que le parent n'a envoyé aucun signal.

Un processus transporte bien plus que sa ligne de commande. Il peut hériter de variables d'environnement, du répertoire de travail courant, de fichiers ouverts, de tubes, de sockets et de descripteurs de fichiers. Si un parent conserve un jeton porteur dans une variable d'environnement, tout enfant qui hérite de cet environnement possède ce jeton. Tuer ensuite l'enfant ne remet pas le secret dans le coffre et n'annule pas les requêtes déjà effectuées.

C'est pourquoi une limite d'autorisation doit répondre à une question précise : quelle exécution de processus active peut demander des actions privilégiées, jusqu'à quand, et comment lui refuser l'accès immédiatement ? La réponse ne devrait pas être « tout ce qui descend du terminal ayant lancé la tâche ». Ce serait un accident de structure des processus, pas une décision de sécurité.

Pour un agent qui a besoin d'un accès HTTP ou SSH, gardez les identifiants hors du processus agent. Demandez à un composant qui détient les identifiants d'effectuer l'action après avoir reconnu un appelant autorisé. Le nettoyage cesse ainsi d'être une tentative désespérée d'effacer une autorité divulguée et devient une simple mesure d'hygiène opérationnelle.

## Définir la durée de vie avant de lancer la tâche

Une exécution d'agent doit avoir une condition de début, une condition de fin et une condition de révocation clairement définies. Écrivez-les avant de décider si un clic dans une interface, un groupe de processus ou un wrapper shell constitue un contrôle.

Pour un travail interactif, le choix raisonnable par défaut est généralement une autorisation pour un seul processus agent racine. L'autorisation commence lorsque vous approuvez cette exécution précise et se termine lorsque le processus racine s'arrête ou lorsque vous la révoquez. Un nouvel appel de l'agent reçoit une nouvelle décision, même s'il exécute le même binaire depuis le même répertoire.

Pour un travail sans surveillance, n'étendez pas discrètement cette approbation à toute la journée parce que les demandes répétées agacent les utilisateurs. Donnez à la tâche un responsable identifié, une durée limitée, un ensemble de destinations restreint et un moyen d'annulation utilisable pendant son exécution. Si la tâche doit continuer après la fin de la tâche parente, traitez-la comme une tâche distincte et demandez-lui une autorisation séparée.

Trois questions révèlent rapidement les conceptions floues :

- Quel exécutable et quelle instance de processus l'utilisateur a-t-il approuvés ?
- Quel événement met fin à son autorité si le processus n'envoie jamais de signal de fin propre ?
- Un descendant peut-il obtenir une nouvelle action privilégiée après cet événement ?

Si la dernière réponse est oui parce que le descendant a hérité d'un jeton, vous avez délégué une autorité sans enregistrer cette délégation. Si elle est oui parce que la passerelle considère toujours tous les descendants comme fiables, vous avez transformé l'ascendance des processus en langage de politique. Ces deux choix deviennent difficiles à expliquer pendant un incident.

Ne confondez pas commodité et limite de sécurité. Un onglet de terminal, un répertoire de projet, un compte agent et une identité de signature du code sont tous des éléments de contexte utiles. Aucun ne désigne à lui seul une exécution précise. Une identité de signature du code indique qui a signé un exécutable. Elle ne permet pas de savoir si l'exécutable a lancé l'auxiliaire attendu, une ancienne copie de cet auxiliaire ou un enfant détaché après l'annulation.

## Examiner l'arbre actif avant de terminer quoi que ce soit

Sur macOS, commencez par les informations sur les processus exposées par le noyau au lieu de déduire leur état à partir d'un intitulé de tâche. Le manuel de `ps` documente `pid`, `ppid`, `pgid` et `sid` comme des champs distincts. Ils indiquent respectivement l'ascendance, l'appartenance à un groupe de processus et l'appartenance à une session. Vous avez besoin de ces quatre informations lorsqu'un agent peut invoquer des shells et des outils.

Exécutez cette commande et enregistrez sa sortie lorsque vous examinez une exécution :

```sh
ps -axo pid,ppid,pgid,sid,stat,etime,command
```

Voici à quoi ressemble une sortie utile :

```text
  PID  PPID  PGID   SID STAT ELAPSED COMMAND
48102 47790 48102 48102 S    00:18:04 agent-cli run build
48131 48102 48102 48102 S    00:17:59 /bin/sh -c make test
48144 48131 48102 48102 S    00:17:56 test-runner --watch
48209     1 48209 48209 S    00:16:02 helper --upload-results
```

Les trois premiers processus partagent un groupe et une session. Le dernier a un PPID égal à 1 et un groupe et une session différents. Il s'est peut-être détaché, ou un lanceur en a peut-être pris la responsabilité. Dans tous les cas, envoyer un signal uniquement au PID 48102 ne l'arrêtera pas.

Pour les enfants directs d'un PID racine connu, utilisez :

```sh
pgrep -P 48102 -alf
```

Cette commande ne trouve qu'un seul niveau de descendance. Répétez-la pour chaque enfant si vous avez besoin d'un parcours manuel rapide. Pour un dossier d'incident, capturez la sortie de `ps` avant et après la révocation, puis notez le PID racine exact, l'heure de démarrage, la commande, le groupe de processus et la session. Un simple nom de processus constitue une preuve médiocre, car les noms se répètent et les lignes de commande changent.

Vérifiez les connexions réseau ouvertes lorsque le risque concerne des appels externes. Sur macOS, `lsof` peut afficher les fichiers réseau d'un processus :

```sh
lsof -nP -p 48209 -i
```

Une socket en écoute, une connexion sortante établie ou un transport SSH de longue durée augmentent l'urgence. Cela ne prouve pas un comportement malveillant. Cela prouve qu'un processus que vous pensiez terminé possède encore un canal qui mérite d'être compris.

Ne construisez pas en production un contrôle de sécurité qui dépend de l'analyse de la sortie lisible par l'homme de `ps`. Utilisez-la pour les investigations et les tests. Un vrai lanceur doit enregistrer les identifiants au démarrage et conserver une poignée de révocation directe auprès de la passerelle d'autorisation.

## Les groupes de processus aident, mais les enfants détachés les contournent

Un groupe de processus dédié donne au lanceur un moyen pratique d'annuler un arbre de tâches normal. Créez le groupe avant le démarrage de l'agent, gardez la racine comme chef de groupe et envoyez les signaux au groupe plutôt qu'au seul processus racine. Cela couvre le cas courant des shells, compilateurs, outils de test et pipelines qui restent dans le même groupe.

Sur les systèmes qui prennent en charge la syntaxe habituelle des signaux, un identifiant de groupe négatif cible un groupe de processus :

```sh
kill -TERM -48102
sleep 3
kill -KILL -48102 2>/dev/null || true
```

Le signal `TERM` laisse aux outils ordinaires la possibilité de fermer leurs fichiers et de signaler l'annulation. Le signal `KILL` envoyé ensuite traite les processus qui refusent de s'arrêter ou qui n'y parviennent pas. Ne copiez pas ce code dans une automatisation avant d'avoir vérifié que 48102 est bien le groupe de processus visé. Un identifiant erroné peut arrêter votre propre shell ou un travail sans rapport.

Cette méthode a ses limites. Un enfant peut appeler `setsid`, ce qui crée une nouvelle session et généralement un nouveau groupe de processus. Une tâche peut transmettre du travail à un service local, à un système de compilation distant ou à une file d'attente. Un shell peut lancer un processus d'arrière-plan en dehors du groupe. Une fois cela fait, l'arrêt du groupe devient du nettoyage, pas du confinement.

Sur Linux, un gestionnaire de services peut placer une tâche dans un cgroup dédié et arrêter l'ensemble du cgroup. C'est généralement plus fiable que le nettoyage par groupe de processus, car le noyau suit l'appartenance au-delà de la simple relation parent-enfant. Ne prétendez pas qu'une application macOS dans la barre des menus dispose de ce contrôle. macOS et Linux ont des modèles de supervision des processus différents, et une conception d'agent portable ne doit pas faire semblant du contraire.

La recommandation « tuez simplement l'arbre des processus » reste populaire parce qu'elle fonctionne dans les démonstrations. Elle échoue précisément dans les situations qui rendent l'accès d'un agent risqué : tâches longues, auxiliaires en arrière-plan, wrappers et annulations partielles. Utilisez les groupes de processus pour réduire les résidus. N'en faites pas votre seul mécanisme de révocation.

## Garder les secrets entièrement hors de l'arbre

Le processus enfant le plus sûr reste celui qui ne peut pas lire un identifiant. Transmettre un jeton par une variable d'environnement le rend accessible à tous les descendants qui héritent de l'environnement. Il peut aussi apparaître dans les diagnostics, les rapports de plantage ou des journaux imprudents. Un fichier temporaire n'est que légèrement moins mauvais si l'enfant peut le copier avant sa suppression.

Évitez ces modèles pour les commandes lancées par un agent :

```sh
export DEPLOY_TOKEN='token-value'
agent-cli run deploy
```

```sh
agent-cli run deploy --token "$(cat ~/.config/deploy-token)"
```

Dans les deux cas, l'autorité brute se retrouve dans l'environnement d'exécution de l'agent. Le second risque aussi de la placer dans les arguments du processus, l'historique du shell ou les journaux. Faire tourner le jeton après un problème peut être nécessaire, mais la rotation est une mesure de récupération, pas une voie normale d'annulation.

Utilisez plutôt une passerelle d'actions locale. L'agent doit demander une opération, par exemple une requête HTTP vers une destination approuvée ou une commande SSH, sans jamais recevoir les identifiants eux-mêmes. La passerelle injecte l'identifiant approprié, exécute l'action et renvoie le résultat. Vous disposez ainsi d'un endroit où bloquer l'appel suivant, même si un processus enfant égaré continue de fonctionner.

Sallyport applique cette approche aux API HTTP et à SSH : son coffre chiffré reste dans l'application, tandis que l'agent se connecte via le shim `sp mcp` et reçoit les résultats des actions plutôt que les secrets. Cela compte davantage que n'importe quel script sophistiqué de suppression de processus, car un enfant ne peut pas hériter d'un jeton qu'il n'a jamais détenu.

Ne surestimez pas cet avantage. Un processus disposant d'une session de passerelle approuvée peut encore demander des actions jusqu'à la fin de cette session ou jusqu'à sa révocation. Garder les secrets hors de l'arbre des processus limite le vol d'identifiants, mais ne rend pas inoffensif un agent autorisé.

## L'approbation doit s'attacher à une exécution, pas à un nom de famille

Une autorisation fondée uniquement sur le nom d'un exécutable est faible. N'importe qui peut copier un binaire vers un autre emplacement, l'envelopper dans un script shell ou lancer plus tard une autre instance. Une autorisation fondée uniquement sur le signataire est meilleure pour l'attribution, mais reste trop large si elle approuve silencieusement toute exécution future signée par la même partie.

Une fiche d'approbation utile doit identifier le processus demandeur avec des éléments qu'une personne peut vérifier : son autorité de signature du code, son chemin d'exécutable, son PID racine et son heure de démarrage. La décision doit s'appliquer à cette seule exécution. Un processus enfant ne doit pas obtenir un droit d'action sans fin simplement parce qu'il a un ancêtre approuvé quelque part dans son historique.

Deux modèles raisonnables existent pour les descendants. Le modèle strict demande une approbation pour chaque demandeur distinct. Le modèle pratique autorise les appels qui surviennent pendant une exécution racine approuvée, puis rejette tout nouvel appel lorsque cette exécution prend fin. Le second convient bien aux agents qui lancent légitimement des outils de courte durée, à condition que la passerelle puisse détecter la fin de l'exécution racine et que l'autorisation ne puisse pas être rattachée à un processus ultérieur portant le même nom.

Sallyport utilise par défaut une autorisation par session : lors du premier appel d'un nouveau processus agent, une fiche d'approbation présente en premier l'autorité de signature du code du processus, et l'approbation dure jusqu'à la fin de cette exécution. La porte du coffre de Sallyport refuse toute action lorsqu'elle est verrouillée, et un réglage par appel peut demander une approbation pour un identifiant précis à chaque utilisation. Ce sont volontairement de petits contrôles. Une accumulation de règles ne ferait que masquer la question de savoir qui a approuvé quoi.

L'approbation à chaque appel convient aux identifiants pour lesquels chaque action mérite un examen attentif, comme un compte de déploiement en production ou une API d'administration destructive. Elle ne convient pas à toutes les requêtes en lecture seule. Les utilisateurs finissent par tout approuver lorsque chaque action inoffensive exige leur attention. Placez les frictions là où les conséquences le justifient, puis gardez la limite de session courte et claire ailleurs.

## Révoquer l'autorisation avant de traquer le processus

Lorsqu'une tâche parente se termine de façon inattendue, révoquez d'abord sa capacité à effectuer de nouveaux appels privilégiés. Terminez ensuite le groupe racine, examinez les survivants et nettoyez ce qui s'est échappé. Inverser cet ordre crée une faille : l'enfant que vous n'avez pas encore trouvé peut continuer à appeler l'extérieur pendant que vous inspectez les tables de processus.

Une séquence d'intervention solide ressemble à ceci :

1. Révoquez la session ou verrouillez le coffre au niveau de la passerelle d'actions.
2. Conservez le PID racine, les informations sur les processus, les enregistrements d'actions récents et l'heure de l'annulation.
3. Envoyez `TERM` au groupe de processus connu et examinez les processus restants.
4. Passez à `KILL` uniquement pour les processus qui appartiennent encore à la tâche et refusent de s'arrêter.
5. Vérifiez si des processus locaux détachés ou des tâches distantes nécessitent une annulation distincte.

La première action doit fonctionner même si le PID racine a déjà disparu. Elle doit aussi fonctionner si un agent essaie délibérément de maintenir un auxiliaire en vie. Une passerelle qui exige la présence de l'appelant pour permettre la révocation fonctionne à l'envers.

La révocation instantanée doit bloquer les requêtes futures, pas réécrire l'historique. Conservez les enregistrements qui montrent l'autorisation précédente et les appels effectués. Si une passerelle n'enregistre que les actions réussies, elle masque des éléments utiles pendant une investigation. Les appels refusés après la révocation montrent que quelque chose a continué à essayer d'agir.

Le verrouillage du coffre est le frein d'urgence pour toutes les sessions actives, ce qui convient lorsque vous ne pouvez pas identifier rapidement l'exécution compromise. La révocation de session est la réponse plus ciblée lorsque vous le pouvez. Gardez ces opérations distinctes afin qu'un opérateur n'ait pas à choisir entre ne rien faire et interrompre le travail de toute l'équipe.

## Une piste d'audit doit être reliée aux preuves des processus

Un enregistrement d'activité sans contexte de processus ne répond qu'à la moitié de la question. Il peut indiquer qu'une requête HTTP a eu lieu, mais pas quelle exécution approuvée l'a initiée. Un enregistrement de session sans les appels individuels pose le problème inverse. Il faut les deux vues et un moyen de vérifier que personne ne les a discrètement modifiées après un incident.

Enregistrez au minimum l'événement d'autorisation, l'identité du processus demandeur, le début et la fin de la session, chaque action privilégiée, la révocation et tout refus après la révocation. Ajoutez des horodatages et des identifiants de corrélation stables. N'enregistrez pas les secrets bruts. Ne supposez pas non plus qu'une ligne de commande peut être stockée sans risque, car elle contient souvent des valeurs qui n'auraient jamais dû y être transmises.

Sallyport génère ses journaux Sessions et Activity à partir d'un même journal d'audit chiffré et chaîné par hachage. Sa commande `sp audit verify` vérifie la chaîne hors ligne sur le texte chiffré, sans nécessiter de clé du coffre. Cette vérification est utile lorsque vous devez transmettre un enregistrement exporté à quelqu'un qui doit en confirmer l'intégrité sans pouvoir lire les identifiants stockés.

Une chaîne de hachage ne rend pas complet un journal incomplet. Si votre lanceur n'a jamais enregistré le PID racine, le journal d'audit ne pourra pas le reconstituer plus tard. Si une tâche distante a reçu une requête API et a continué sur une autre machine, les preuves issues des processus locaux ne montreront pas le processus distant. Auditez la requête qui a lancé le travail distant, puis demandez au système distant d'exposer ses propres enregistrements d'annulation et d'événements.

## Un échec courant appelle deux corrections distinctes

Imaginez un agent de programmation chargé d'exécuter des tests d'intégration et de publier un rapport. Il démarre un shell, qui lance le testeur, qui lance à son tour un auxiliaire pour téléverser les résultats. L'utilisateur voit un échec de test et annule l'agent. L'agent parent se termine. Le shell disparaît. L'auxiliaire s'est déjà détaché, conserve une connexion sortante et envoie un rapport après l'annulation.

Si l'identifiant de téléversement se trouvait dans une variable d'environnement, l'auxiliaire peut le posséder même après la révocation de l'approbation locale. Vous devez alors faire tourner l'identifiant, examiner les journaux et éventuellement lancer une réponse à incident. Le nettoyage des processus est arrivé trop tard, car l'autorité avait déjà été transmise à l'enfant.

Si l'auxiliaire demandait le téléversement par l'intermédiaire d'une passerelle, la révocation de la session parente bloque toute nouvelle demande de téléversement. Si le téléversement avait déjà commencé, l'audit de la passerelle le montre. Vous terminez tout de même l'auxiliaire, mais vous ne comptez plus sur son arrêt comme seul moyen de contrôle.

Le cas délicat est celui d'un auxiliaire qui doit délibérément survivre à l'agent, comme un serveur d'aperçu local ou une tâche de publication mise en file. Ne le considérez pas comme un enfant qui conserverait éternellement l'autorité. Donnez-lui un responsable explicite, un enregistrement d'autorisation distinct, une expiration définie et un contrôle d'arrêt visible. Dès qu'un travail dépasse la tâche qui l'a lancé, il devient son propre objet opérationnel.

## Tester l'annulation avec un descendant récalcitrant

Un contrôle que vous ne testez jamais échouera au moment le moins pratique. Construisez un agent de test inoffensif qui lance un enfant dormant, ouvre une connexion locale sans danger et tente une action via la passerelle après la fin du parent. Annulez ensuite le parent à plusieurs moments : avant le démarrage de l'enfant, pendant son fonctionnement, après son détachement et pendant qu'une action est en cours.

Le résultat attendu doit être précis. L'enfant normal se termine avec le groupe de processus. Un enfant détaché peut rester visible, ce qui montre pourquoi il faut continuer à inspecter. Après la révocation, toute nouvelle requête privilégiée reçoit un refus. L'enregistrement de session montre l'approbation et la révocation, tandis que l'enregistrement d'activité montre les appels effectués avant et après la limite.

Ne considérez pas qu'un test réussi signifie seulement que l'interface a changé d'état. Vérifiez l'état des processus du système d'exploitation, la décision de la passerelle et l'enregistrement d'audit. Ce sont trois observations différentes. Un bouton d'annulation qui masque uniquement une carte de tâche n'est qu'une mise en scène.

Le premier changement concret est simple : enregistrez l'identité du processus racine chaque fois que vous autorisez un agent et faites expirer l'autorisation indépendamment lorsque cette exécution se termine. Testez ensuite l'enfant récalcitrant avant de confier à une fonction d'annulation des identifiants capables de modifier la production.
