Évasion par la CI aval : la PR de la lane code fait exécuter le code de l'agent avec le jeton du job #13

Closed
opened 2026-10-13 23:37:52 +02:00 by mathieu · 1 comment
Owner

Constat

La lane code exécute les vérifications de l'agent sans aucun secret (job verifier, sous node, env -i). Mais la PR qu'elle ouvre déclenche ensuite le workflow de tests du dépôt sur pull_request. Ce workflow exécute le code écrit par l'agent (tests, scripts, dépendances) avec le jeton automatique du job, qui a l'écriture sur le dépôt quand la branche vit dans le dépôt lui-même. La référence Forgejo le dit : seule une PR venue d'un fork reçoit un jeton en lecture.

Scénario : un ticket piégé amène l'agent à écrire un test qui, lancé par la CI du dépôt, utilise FORGEJO_TOKEN pour pousser sur main, créer une étiquette ou modifier une autre branche. Le contrôle des chemins de publier ne l'empêche pas : le fichier n'est ni un workflow ni un secret.

Signalé par une revue de sécurité automatique le 2026-10-13, après l'équipement d'atelier. Concerne tout dépôt équipé dont la CI tourne sur pull_request.

Ce qui ne marche pas

  • permissions: contents: read : la référence Forgejo Actions (version courante) ne documente aucune clé permissions. On ne peut pas restreindre le jeton depuis le workflow.

Pistes

  1. Protéger la branche par défaut des dépôts équipés : pas de push direct, fusion par PR seulement. Le jeton du job ne peut plus écrire sur main. C'est le réglage minimal, à imposer dans le README (prérequis d'installation) et à vérifier par outils/installer.py --etiquettes.
  2. Ouvrir la PR depuis un fork du bot, pour que la CI du dépôt reçoive un jeton en lecture seule. Plus lourd : fork à tenir à jour, fix à revoir.
  3. Faire sauter la CI du dépôt sur les PR du bot et s'appuyer sur verifier. Écarté a priori : on perd la CI réelle.

Critères d'acceptation

  • README et CONTRAT.md décrivent le risque et le prérequis retenu.
  • outils/installer.py signale une branche par défaut non protégée.
  • Le choix est éprouvé sur essai-actions : un test qui tente git push sur main avec le jeton du job échoue.
## Constat La lane `code` exécute les vérifications de l'agent sans aucun secret (job `verifier`, sous `node`, `env -i`). Mais la PR qu'elle ouvre déclenche ensuite le workflow de tests **du dépôt** sur `pull_request`. Ce workflow exécute le code écrit par l'agent (tests, scripts, dépendances) avec le jeton automatique du job, qui a l'écriture sur le dépôt quand la branche vit dans le dépôt lui-même. La référence Forgejo le dit : seule une PR venue d'un fork reçoit un jeton en lecture. Scénario : un ticket piégé amène l'agent à écrire un test qui, lancé par la CI du dépôt, utilise `FORGEJO_TOKEN` pour pousser sur `main`, créer une étiquette ou modifier une autre branche. Le contrôle des chemins de `publier` ne l'empêche pas : le fichier n'est ni un workflow ni un secret. Signalé par une revue de sécurité automatique le 2026-10-13, après l'équipement d'`atelier`. Concerne tout dépôt équipé dont la CI tourne sur `pull_request`. ## Ce qui ne marche pas - `permissions: contents: read` : la référence Forgejo Actions (version courante) ne documente aucune clé `permissions`. On ne peut pas restreindre le jeton depuis le workflow. ## Pistes 1. **Protéger la branche par défaut** des dépôts équipés : pas de push direct, fusion par PR seulement. Le jeton du job ne peut plus écrire sur `main`. C'est le réglage minimal, à imposer dans le README (prérequis d'installation) et à vérifier par `outils/installer.py --etiquettes`. 2. Ouvrir la PR depuis un fork du bot, pour que la CI du dépôt reçoive un jeton en lecture seule. Plus lourd : fork à tenir à jour, `fix` à revoir. 3. Faire sauter la CI du dépôt sur les PR du bot et s'appuyer sur `verifier`. Écarté a priori : on perd la CI réelle. ## Critères d'acceptation - [ ] README et CONTRAT.md décrivent le risque et le prérequis retenu. - [ ] `outils/installer.py` signale une branche par défaut non protégée. - [ ] Le choix est éprouvé sur `essai-actions` : un test qui tente `git push` sur `main` avec le jeton du job échoue.
Author
Owner

Atténué et documenté, risque résiduel suivi par #20.

  • #18 (606be5c) : section « CI aval » de CONTRAT.md ; installer.py signale une CI non confinée et une branche par défaut mal protégée.
  • Risque mesuré sur essai-actions (run 460, essai-jeton.yml) : le jeton du job d'une PR interne est dans l'environnement de chaque étape, écrit des refs/… hors branches, lance un workflow_dispatch (HTTP 204) ; absent sous env -i.
  • #19 (879a8a9) : runuser sans l'environnement de root, python3 -I.
  • Dépôts équipés réépinglés sur 879a8a9 ; tests.yml de fabrique-pdf, mathieu.lecrivain.fr, atelier exécutent le code sous node sans jeton, CI vertes (runs 497, 498, 499). main protégée partout.

Parade structurelle (PR du bot depuis un fork de code-bot) : #20.

Atténué et documenté, risque résiduel suivi par #20. - #18 (`606be5c`) : section « CI aval » de CONTRAT.md ; `installer.py` signale une CI non confinée et une branche par défaut mal protégée. - Risque mesuré sur essai-actions (run 460, `essai-jeton.yml`) : le jeton du job d'une PR interne est dans l'environnement de chaque étape, écrit des `refs/…` hors branches, lance un `workflow_dispatch` (HTTP 204) ; absent sous `env -i`. - #19 (`879a8a9`) : `runuser` sans l'environnement de root, `python3 -I`. - Dépôts équipés réépinglés sur `879a8a9` ; `tests.yml` de fabrique-pdf, mathieu.lecrivain.fr, atelier exécutent le code sous `node` sans jeton, CI vertes (runs 497, 498, 499). `main` protégée partout. Parade structurelle (PR du bot depuis un fork de `code-bot`) : #20.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mathieu/lanes#13
No description provided.