30.10.26
⚡ GitHub Actions : test des correctifs sécurité (réels)15 min de lecture2 995 mots

⚡ GitHub Actions : test des correctifs sécurité (réels)

Découvrez comment 11 dépôts GitHub Actions ont été testés pour les correctifs de sécurité automatisés : erreurs, correctifs, faux positifs et revue de code sécurisée.

correctifs de sécurité automatisésanalyse de sécurité GitHubGitHub Actions sécuritéfaux positifs cybersécuritérevue de code sécurisée
⚡ GitHub Actions : test des correctifs sécurité (réels)

Correctifs de sécurité automatisés : ce que révèle le test de Railo sur 11 dépôts

Un outil qui repère une vulnérabilité dans une pull request, c'est déjà utile. Un outil qui propose aussi un correctif peut faire gagner du temps - mais uniquement si ce correctif colle vraiment au contexte du projet. Sinon, on passe vite de « gain de temps » à « travail de tri ».

Railo, une GitHub Action qui détecte certains problèmes de sécurité et propose des modifications, a testé son propre fonctionnement sur onze dépôts publics qu'elle n'avait pas écrits. Résultat : 923 alertes examinées, 162 correctifs proposés au premier essai, puis 95 jugés inadaptés après lecture humaine. Et bonne nouvelle au moins sur ce point : aucun correctif n'a rendu les fichiers impossibles à analyser syntaxiquement. Le souci n'était donc pas là. Le problème, c'était la pertinence.

Ce test montre pourquoi la qualité d'un outil de sécurité ne se résume pas au nombre de vulnérabilités détectées. L'enjeu, c'est aussi : où l'outil agit, ce qu'il comprend du code, et ce qu'il finit par laisser dans les dépôts. Voici la méthode utilisée, les erreurs observées et ce qu'il faut retenir avant d'activer des correctifs automatiques.

Pourquoi tester les correctifs sur des dépôts réels ?

Railo est une action GitHub qui cherche des problèmes de sécurité dans le code et peut proposer un correctif sous forme de différence lisible. D'après son éditeur, ces changements reposent sur des transformations définies par des règles, pas sur une « génération libre » par un modèle. À entrée égale, l'action devrait donc produire le même correctif.

Ce déterminisme a un avantage : on peut relire précisément ce qui change et reproduire le résultat. Mais il ne dit pas tout. Une règle peut modifier un fichier de façon parfaitement syntaxique tout en ratant l'objectif, parce qu'elle ignore le rôle réel du fichier dans l'application.

Un banc d'essai maison ne suffit pas

Jusqu'alors, l'équipe mesurait la qualité des correctifs sur des exemples construits en interne. Ce genre de test sert à vérifier qu'une règle marche bien sur un cas attendu. C'est indispensable, mais facile à maîtriser : on connaît le contexte, les fichiers sont préparés, et les scénarios collent souvent aux hypothèses des auteurs.

Dans un dépôt réel, une chaîne qui « ressemble » à un secret peut être un exemple de cours, une valeur de test, ou une donnée prévue pour un outil tiers. Un correctif qui paraît logique dans un fichier isolé peut casser un comportement ailleurs. Le test sur du code inconnu sert justement à confronter les règles à des conventions et des structures qu'on ne choisit pas.

Les dépôts retenus visaient deux situations :

  • Des applications volontairement vulnérables, où des problèmes connus doivent être présents : OWASP NodeGoat, OWASP Juice Shop, PyGoat, DVWA, OWASP WebGoat et govwa.
  • Des projets maintenus, où une grande partie des alertes « attendues » devraient en réalité être du bruit : Flask, Express, Gin, Slim et Spring Petclinic.

Le contraste est utile : un outil doit savoir détecter des failles dans des environnements d'entraînement, mais aussi éviter de proposer des changements absurdes quand le code est maintenu par des équipes.

Une procédure reproductible, avec une limite importante

Pour chaque dépôt, l'équipe a fait un clonage superficiel, puis analysé l'ensemble du projet (pas seulement un diff de pull request). L'action a suivi son chemin normal : chargement de la configuration, analyse via Semgrep 1.102.0, génération des correctifs, puis collecte des fichiers modifiés.

Chaque fichier modifié a ensuite été vérifié avant/après correction avec l'outil adapté au langage : compilation Python, analyse syntaxique TypeScript pour JavaScript et TypeScript, gofmt -e pour Go, php -l pour PHP et javac arrêté après l'analyse syntaxique pour Java. Enfin, une personne a lu chaque proposition.

Un point distinct de l'usage habituel mérite d'être noté : le banc d'essai n'a pas exécuté les commandes de test propres à chaque dépôt. Normalement, l'action peut tenter de lancer les tests avant et après modification, mais ici, ils n'ont rien exécuté depuis les projets étudiés. Ça réduit les risques liés à l'exécution de code tiers, mais ça veut aussi dire que l'évaluation ne valide pas le comportement complet des applications.

Ce que les onze dépôts ont révélé

Au premier passage : 923 alertes et 162 correctifs proposés. Après lecture humaine, 95 de ces correctifs ne devaient pas être appliqués. Souvent, il s'agissait de code tiers, de données de test ou de valeurs de démonstration.

Dépôt Alertes Correctifs avant Correctifs après
WebGoat3819515
Juice Shop216116
PyGoat1321616
DVWA942724
NodeGoat24106
govwa800
Flask2210
Gin2010
Express1400
Spring Petclinic910
Slim300
Total92316267

Le détail le plus parlant n'est pas seulement la baisse de 162 à 67. C'est la concentration des erreurs : WebGoat explique à lui seul 80 des 95 correctifs écartés. Environ 77 concernaient deux fichiers liés à l'éditeur Ace, livrés avec le projet.

Valide syntaxiquement ne veut pas dire correct

Aucun des 162 correctifs initiaux n'a introduit d'erreur de syntaxe dans les fichiers vérifiés. C'est un résultat positif - mais pas suffisant.

Un programme peut rester « analysable » et pourtant changer de comportement de façon indésirable. Par exemple : remplacer des valeurs différentes par une seule variable d'environnement peut supprimer des distinctions métier. Écrire ${VAR} dans un fichier qui ne substitue jamais cette expression peut laisser ces caractères tels quels dans l'application. Ou remplacer du HTML par du texte brut peut dégrader le rendu attendu d'une page.

Donc, les contrôles syntaxiques posent une question limitée : « Le fichier peut-il encore être analysé ? » La vraie question est plutôt : « Le changement correspond-il à la façon dont ce projet fonctionne ? »

En pratique, il faut au moins distinguer trois niveaux : validité syntaxique, cohérence avec le rôle du fichier (et les conventions du projet), puis impact réel sur le comportement.

Les dépôts maintenus posent un autre problème

Sur les projets considérés comme bien maintenus, l'équipe attendait en théorie un nombre de correctifs pertinents égal à zéro. Après les premiers changements, Railo ne proposait plus rien pour Flask, Gin, Express, Spring Petclinic ou Slim. C'est mieux que des modifications erronées - mais ça ne veut pas dire que l'analyse était « silencieuse ».

Lors d'une version de suivi, le bruit a diminué sur certains dépôts : Flask est passé de 22 à 11 alertes, et NodeGoat de 24 à 21. NodeGoat a aussi reçu un correctif jugé correct de plus, passant de six à sept. Ces chiffres proviennent d'un passage ultérieur, donc il ne faut pas les confondre avec ceux du tableau.

Le point opérationnel reste simple : même un outil qui ne propose aucune mauvaise modification peut user les équipes si elle continue de signaler trop de cas sans intérêt. Une alerte non corrigée reste un coût de triage, surtout si elle revient à chaque pull request.

Les erreurs de contexte et les garde-fous ajoutés

Les problèmes observés avaient un fil conducteur : chaque proposition semblait plausible « à petite échelle », mais la règle ne disposait pas du contexte nécessaire pour comprendre le fichier. Les ajustements ont donc consisté autant à mieux exclure certains zones qu'à rendre les règles plus prudentes.

Bibliothèques intégrées et fichiers de test

Dans WebGoat, une règle sur les secrets a interprété des chaînes comme token: "keyword" dans les règles de coloration syntaxique d'Ace comme des identifiants sensibles. Elle les a remplacées par process.env.TOKEN, alors que le code n'avait rien à voir avec un secret. Juice Shop a aussi eu droit à des modifications de fichiers liés à la bibliothèque Three.js.

La réponse a été de traiter certains chemins comme du code tiers : répertoires lib/ et libs/ sous des dossiers de ressources statiques, fichiers minifiés ou assemblés, et fichiers JavaScript de plus de 5 000 lignes. Ce seuil n'est pas une preuve « mathématique » que c'est du tiers, mais c'est un garde-fou pratique contre des modifications risquées.

Les données de test ont aussi posé problème. Gin stocke, dans testdata/, un certificat et une clé utilisés pour ses tests TLS. Railo les a traités comme des secrets à remplacer. Depuis, les dossiers testdata, __tests__, __mocks__, mocks, e2e, cypress et bower_components, ainsi que des répertoires similaires, sont exclus des propositions.

Données, variables et code exécuté dans le navigateur

NodeGoat génère plusieurs comptes avec des mots de passe distincts dans son script de réinitialisation de base de données. La règle les a tous remplacés par process.env.PASSWORD. Dans Juice Shop, 27 utilisateurs d'un fichier YAML ont été remplacés par la chaîne littérale ${PASSWORD}. Dans les deux cas, l'automatisation effaçait une information utile au lieu de sécuriser une configuration de production.

Une heuristique ajoutée ensuite considère que plusieurs valeurs différentes associées à la même clé sont plutôt des données structurées que « un seul secret ». Ça réduit le risque, sans promettre qu'aucun cas ne passera à travers.

Autre point important : remplacer par ${VAR} n'est pertinent que si le format concerné interprète réellement cette syntaxe. L'équipe a donc limité cette transformation aux fichiers Docker Compose et aux configurations Spring Boot. Ailleurs (autres YAML, manifestes Kubernetes), cette expression peut rester du texte brut.

Le navigateur, lui aussi, impose des contraintes. process.env est courant dans du code serveur (ou dans certains processus de compilation), mais ça n'existe pas tel quel côté client. Des composants Angular et des scripts de leçon exécutés dans le navigateur ont pourtant été visés par des réécritures vers process.env.SECRET. Railo s'appuie désormais sur des indices de code comme document, window ou localStorage, plutôt que sur process.env dans ce contexte. Une première version a produit un faux classement quand un fichier de configuration serveur mentionnait document.write dans une chaîne ; le contrôle a été ajusté ensuite. Bref : les garde-fous ajoutent aussi leurs propres cas limites.

Les noms trompeurs et les transformations de sécurité

Un nom qui contient token n'est pas forcément un secret. BeeTokenAddress peut désigner une adresse de contrat intelligent. Dans DVWA, $token = "userid:2" était une charge d'attaque de démonstration, pas une donnée à protéger.

Railo met de côté plusieurs suffixes souvent « descriptifs », comme address, id, url, host, name ou type. Les valeurs qui ressemblent à des formats de fournisseurs connus - AWS, GitHub, Slack, Stripe, Google - restent détectables, où qu'elles apparaissent. L'idée est de réduire les faux positifs évidents, sans renoncer à repérer des clés reconnaissables.

Enfin, certaines transformations ont été retirées ou limitées parce qu'elles modifient le comportement :

  • Remplacer innerHTML par textContent ne convient pas quand la valeur contient du HTML généré intentionnellement, par exemple après conversion de Markdown.
  • Remplacer Markup(value) par escape(value) dans Flask peut encoder deux fois un contenu déjà considéré comme sûr, si les données ne viennent pas d'une requête.
  • Appliquer htmlspecialchars à un rappel JSONP ne sécurise pas forcément le rappel en question. Un changement qui donne l'apparence d'une protection peut masquer le vrai risque.

La règle générale est moins « magique » qu'un correctif automatique universel : quand la transformation dépend du sens des données ou du comportement du projet, mieux vaut signaler et demander une revue humaine.

Ce qui reste à résoudre et comment essayer Railo

Le test n'a pas supprimé les limites de l'outil. Il les a rendues plus visibles - et c'est exactement l'intérêt d'un test public et reproductible.

Les limites reconnues

Après l'amélioration du suivi, Flask produisait encore 11 alertes que l'équipe jugeait du bruit : sept liées à une règle générique de redirection ouverte, deux à une règle concernant compile(), une à Markup, et une à une détection liée à SHA-1. Dans ce cas, aucun correctif n'était proposé, mais les alertes pouvaient quand même remonter dans les pull requests en mode avertissement.

L'analyse peut aussi être incomplète. Dans view_help.php de DVWA, une branche if était détectée et corrigée, alors que la branche else (identique) ne l'était pas. Les outils basés sur des règles ne couvrent pas automatiquement toutes les variantes d'un même motif.

Enfin, un changement souhaitable « en général » peut être mauvais dans une application donnée. Rendre un cookie de jeton inaccessible au JavaScript avec httpOnly est souvent une bonne idée. Mais Juice Shop lit ce cookie depuis son interface web ; appliquer le changement automatiquement casserait le fonctionnement attendu. Railo le présente donc comme une suggestion à examiner, pas comme une modification automatique.

Certaines failles sont aussi détectées et expliquées sans être corrigées par l'action. Dans NodeGoat, par exemple, eval(req.body.preTax) fait partie des cas exposés ainsi. Là encore, ce n'est pas forcément un échec : si plusieurs solutions dépendent du comportement recherché, une personne doit choisir et valider.

Commencer en mode avertissement

Railo indique prendre en charge Python, JavaScript et TypeScript, Java, Go et PHP. L'action est annoncée comme gratuite, sans compte ni clé dédiée. Pour un premier test, le mode warn est le plus prudent : il commente les propositions dans la pull request sans modifier le dépôt.

Exemple de configuration GitHub Actions à adapter :

- uses: actions/checkout@v4

with:

ref: ${{ github.head_ref }}

fetch-depth: 0

- uses: IWEBai/railo-action@v2

with:

mode: warn

base_branch: ${{ github.base_ref }}

env:

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Avant d'activer sur un dépôt important, vérifiez les permissions requises dans la documentation de l'action Railo. Ensuite, relisez les diffs proposés sur votre propre code, surtout dans les fichiers de test, les ressources compilées, les manifestes et les données de démonstration.

Une démarche raisonnable :

  1. Lancez l'analyse sur quelques pull requests représentatives, en mode avertissement.
  2. Classez les résultats : correctif pertinent, alerte utile sans automatisation, faux positif ou manque de contexte.
  3. Vérifiez que vos tests et contrôles de sécurité passent après chaque modification proposée.
  4. Ouvrez un ticket avec le diff quand un cas est mal traité, pour en faire un test de régression.
  5. N'activez l'automatisation que pour les règles dont les résultats restent prévisibles sur vos dépôts.

L'équipe Railo indique relancer son banc d'essai et relire les diffs avant chaque version de l'action. Les mainteneurs des dépôts concernés peuvent consulter le jeu d'éléments de preuve de NodeGoat. La publication originale, consacrée au test de Railo sur onze dépôts, détaille aussi la démarche.

Conclusion : automatiser avec un contrôle humain

Le test de Railo met en évidence une différence simple (et facile à oublier) : détecter une vulnérabilité et savoir la corriger automatiquement, ce sont deux capacités distinctes. Sur onze dépôts, aucun des 162 correctifs initiaux n'a cassé la syntaxe des fichiers analysés, mais 95 n'auraient pas dû être proposés. Le problème venait surtout du manque de contexte : code tiers, données de test, code navigateur, ou formats qui n'interprètent pas les variables comme l'outil l'avait supposé.

Du coup, pour une équipe, la bonne question n'est pas seulement « combien de problèmes l'outil trouve ? ». Il faut plutôt demander : combien de modifications passent une revue ? combien d'alertes inutiles reviennent en pull request ? quels cas doivent rester sous la responsabilité d'un ingénieur.

Si vous évaluez Railo, commencez en mode avertissement sur un dépôt représentatif. Lisez les propositions, gardez les tests en place, et traitez les faux positifs comme des informations à intégrer. Un correctif automatique utile, c'est celui que votre équipe peut comprendre, vérifier et assumer - pas celui qui change le plus de fichiers.

Questions frequentes

Non. L’action propose des transformations pour certains problèmes fondés sur des motifs connus. Quand le correctif dépend du contexte ou risque de modifier le comportement de l’application, elle peut se limiter à signaler la vulnérabilité et à demander une revue humaine.
Le banc d’essai décrit ici a contrôlé la syntaxe des fichiers et fait relire chaque proposition, sans exécuter les commandes de test des dépôts. L’action peut tenter d’exécuter les tests du projet selon sa configuration, mais cela ne remplace pas la revue du diff.
Le premier passage a généré 162 propositions sur onze dépôts. Après analyse manuelle, 95 ont été jugées inadaptées et des garde-fous ont été ajoutés. Les chiffres de Flask et NodeGoat mentionnés ensuite proviennent d’une version de suivi distincte.
Configurez l’action avec le mode warn. Elle commente alors les propositions dans les pull requests sans appliquer les changements. Vérifiez les permissions requises dans la documentation et examinez les diffs avant d’envisager une automatisation plus poussée.
Parce que l’analyse syntaxique ne comprend pas nécessairement le rôle du fichier ni les attentes de l’application. Une donnée de test, une bibliothèque tierce ou du HTML généré peut être modifié sans erreur de syntaxe, tout en rendant le code ou les données incorrects.

Articles similaires