flawopen.com/Teardowns/cve-2026-84973-jupyterlab-snapshot-checkout-toctou
CVE-2026-84973 : course TOCTOU dans le workflow de snapshots JupyterLab
Une vérification temporelle à la seconde près pouvait permettre au workflow de récupérer et d’exécuter un commit de pull request non approuvé avec les permissions du job. Passez update-snapshots-checkout à la version 1.0.0 ou ultérieure.
Un responsable approuve un colis précis, mais le guichet de livraison ne vérifie que la seconde affichée à l’horloge. Si quelqu’un remplace le colis pendant cette même seconde, le remplacement paraît approuvé. L’action comparait des horodatages précis à la seconde et acceptait l’égalité : une mise à jour de pull request pouvait donc passer en même temps que le commentaire du responsable.
Concepts Clés et Termes
TOCTOU- Time-of-check to time-of-use : une décision de sécurité porte sur une ressource qui change avant son utilisation.
SHA de tête de pull request- Identifiant immuable du commit au sommet de la branche d’une pull request. Le workflow ne doit exécuter que le commit exact autorisé.
Permissions du jeton GitHub Actions- Opérations permises au GITHUB_TOKEN du job, par exemple lire ou modifier le contenu et les pull requests.
CWE-367 / CWE-829- L’avis classe la course temporelle en
CWE-367et l’exécution de contenu contrôlé par la pull request enCWE-829.
Correspondances de référentiels
Analyse de Cause Racine
Avant la version 1.0.0, update-snapshots-checkout récupérait les horodatages pushed_at de la pull request et created_at du commentaire déclencheur, les convertissait en secondes Unix et rejetait l’exécution uniquement si pushed_at était strictement supérieur. Ces deux horodatages de l’API sont précis à la seconde. Une poussée et un commentaire autorisé pendant la même seconde pouvaient donc être égaux et franchir le contrôle. L’action enregistrait ensuite le SHA de tête courant et le vérifiait après le checkout ; cette comparaison prouvait seulement que le checkout correspondait au SHA nouvellement récupéré, pas que le responsable avait autorisé ce commit. Le correctif amont remplace -gt par -ge et rejette l’égalité ainsi que les poussées ultérieures. Le risque nécessite une version vulnérable et des étapes qui exécutent ensuite le contenu de la pull request ; l’impact dépend des permissions et identifiants du workflow.
Déroulement de l'Attaque Étape par Étape
Un responsable de confiance déclenche le workflow
Une pull request est ouverte et un OWNER, COLLABORATOR ou MEMBER publie le commentaire qui lance le workflow de mise à jour des snapshots.
Une mise à jour contrôlée par l’attaquant coïncide avec le commentaire
Un contributeur met à jour la branche pendant la même seconde. La précision de l’API étant d’une seconde, les valeurs Unix peuvent être égales.
La comparaison stricte accepte l’égalité
L’action vulnérable rejette seulement si pushed_at est supérieur à created_at. L’égalité passe donc le contrôle et le nouveau SHA de tête est enregistré.
Le workflow récupère ce commit et peut l’exécuter
Le checkout correspond au SHA enregistré et passe la vérification ultérieure. Si les étapes suivantes exécutent des actions ou scripts issus de la pull request, ils héritent des permissions et identifiants configurés pour le job.
Mettez à jour et réduisez les privilèges
Passez à la version 1.0.0 ou ultérieure. Épinglez les actions à des SHA immuables, limitez les permissions du GITHUB_TOKEN et éloignez le code non fiable des secrets et jobs privilégiés.
Code Source : Vulnérable vs Sécurisé
Cet extrait correspond à la modification amont d’une ligne : -ge rejette l’égalité avec la précision temporelle de l’API. Il ferme le contournement documenté à la même seconde. L’autorisation devrait toutefois viser un SHA immuable et les permissions rester minimales ; l’ordre temporel ne remplace pas une décision d’autorisation liée au commit. Le diff amont conserve le message de diagnostic existant ; il dit « après », même si l’égalité est désormais rejetée.
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Mettez toutes les utilisations de update-snapshots-checkout à la version 1.0.0 ou ultérieure ; recherchez aussi les copies et forks.
- ✓Épinglez les actions à des SHA immuables révisés et examinez les changements avant de mettre ces références à jour.
- ✓N’autorisez pas une branche mobile au moyen des seuls horodatages ; liez l’approbation au SHA exact et échouez s’il change.
- ✓Définissez des permissions minimales explicites pour chaque job et omettez contents: write et pull-requests: write si inutiles.
- ✓N’exposez pas de secrets ni d’identifiants de déploiement privilégiés aux jobs exécutant du code de pull requests externes.
- ✓Ajoutez un test de régression qui rejette les horodatages égaux et les poussées ultérieures.