|8 min de lecture|Yvann Lièvre

Intégrer Nuclei dans un pipeline CI/CD GitHub Actions pour un scan continu

Guide pas à pas pour l'intégration Nuclei CI/CD GitHub Actions : job post-déploiement, alerting ciblé et gestion des templates sans ralentir le build.

NucleiCI/CDGitHub ActionsDevSecOps
Intégrer Nuclei dans un pipeline CI/CD GitHub Actions pour un scan continu

Un audit de sécurité ponctuel donne une photo à un instant T. Trois semaines plus tard, un déploiement ajoute un endpoint mal protégé, une dépendance vulnérable ou un en-tête de sécurité oublié : la photo ne vaut plus rien. Pour une PME sans pentester dédié, la vraie question n'est pas « avons-nous fait un audit », mais « savons-nous, à chaque mise en production, si la surface d'attaque vient de se dégrader ». C'est exactement ce que Nuclei branché en CI/CD apporte : un scan qui s'exécute après chaque déploiement, sur staging comme en production, sans ajouter de friction au pipeline.

Ce guide couvre l'intégration complète : le job GitHub Actions, le découpage du scan pour rester rapide, l'alerting qui distingue le critique du bruit, et la gestion dans la durée du jeu de templates (le point que la plupart des tutoriels oublient, et qui condamne l'intégration en quelques mois).

Le job GitHub Actions : scan post-déploiement

Le principe : Nuclei ne tourne pas à chaque commit sur une branche de développement (trop tôt, l'URL cible n'existe pas encore), il tourne après que le déploiement a réussi, contre l'environnement réellement exposé.

name: post-deploy-scan
 
on:
  workflow_run:
    workflows: ["deploy-staging"]
    types: [completed]
 
jobs:
  nuclei-scan:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Cache templates Nuclei
        uses: actions/cache@v4
        with:
          path: ~/.local/nuclei-templates
          key: nuclei-templates-${{ github.run_id }}
          restore-keys: |
            nuclei-templates-
 
      - name: Installer Nuclei
        uses: projectdiscovery/nuclei-action@main
        with:
          target: "https://staging.exemple.com"
          templates: "cves/,exposed-panels/,misconfiguration/"
          severity: "critical,high"
          output: nuclei-results.json
          json-export: nuclei-results.json
 
      - name: Publier le rapport
        uses: actions/upload-artifact@v4
        with:
          name: nuclei-report
          path: nuclei-results.json

Le déclencheur workflow_run est volontaire : il découple le scan du workflow de déploiement lui-même. Si le déploiement échoue, le scan ne part pas, inutile de scanner une version qui n'a pas été livrée. Le cache des templates (actions/cache) évite de retélécharger plusieurs dizaines de mégaoctets à chaque exécution, un point qui pèse directement sur le temps de job et donc sur le coût du runner.

Scoper le scan : ne pas transformer le CI en goulot d'étranglement

Un scan Nuclei sans bornes, avec le jeu de templates complet et une sévérité non filtrée, peut prendre plusieurs dizaines de minutes. Sur un pipeline qui doit rendre un verdict en quelques minutes, c'est inacceptable. Trois leviers ramènent le scan à une durée compatible avec le CI :

  • Les tags. Restreindre aux catégories pertinentes pour l'application (cves, exposed-panels, misconfiguration, takeovers) plutôt que de lancer l'intégralité du dépôt de templates, qui couvre des technologies que vous n'exploitez probablement pas.
  • La sévérité. -severity critical,high élimine le bruit informatif dès la source, avant même la génération du rapport. Les sévérités basses ont leur place dans un scan hebdomadaire plus complet, pas dans le chemin critique du déploiement.
  • Le ciblage. Scanner l'URL de staging exposée, pas l'ensemble du réseau interne. Nuclei accepte une liste de cibles précises (-l targets.txt) plutôt qu'une plage entière, ce qui réduit mécaniquement le temps d'exécution.

Ajoutez systématiquement un timeout au niveau du job GitHub Actions (timeout-minutes: 10) : un scan qui bloque bloque tout le pipeline derrière lui, y compris les déploiements suivants dans la file.

    timeout-minutes: 10

Alerting : distinguer ce qui doit remonter de ce qui doit juste être archivé

Un job qui produit un fichier JSON dans un artefact que personne ne consulte n'est pas de l'alerting, c'est de l'archivage. La sortie structurée de Nuclei sert justement à automatiser le tri.

nuclei -u https://staging.exemple.com \
  -severity critical,high \
  -jsonl -o results.jsonl

À partir de ce fichier, un petit script (bash ou l'action github-script) décide de la suite : créer une issue GitHub si des résultats critiques ou élevés apparaissent, notifier un canal Slack via webhook, ou faire échouer le job (gate) si le seuil est dépassé.

      - name: Gate sur les résultats critiques
        run: |
          COUNT=$(jq '. | select(.info.severity=="critical")' results.jsonl | jq -s 'length')
          if [ "$COUNT" -gt 0 ]; then
            echo "::error::$COUNT vulnérabilité(s) critique(s) détectée(s)"
            exit 1
          fi
 
      - name: Notifier Slack
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "Scan Nuclei : vulnérabilité critique détectée sur staging, voir l'artefact du job."}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

La logique du gate est volontairement asymétrique : le critique et le élevé bloquent (ou au minimum alertent bruyamment), le reste s'archive pour une revue périodique. Sans cette hiérarchie, une équipe finit soit par ignorer toutes les alertes (le scan devient un rituel vide), soit par bloquer le déploiement pour un finding mineur (le scan devient un obstacle qu'on contourne).

Gérer et versionner le jeu de templates dans le pipeline

C'est le point que la plupart des guides d'intégration passent sous silence, et c'est pourtant celui qui décide si l'intégration tient dans la durée. Deux réflexes à installer dès le premier jour :

Épingler la version des templates utilisée par le pipeline. Un scan qui télécharge silencieusement « la dernière version » à chaque exécution introduit une variable non maîtrisée : un template mis à jour hier peut faire apparaître un nouveau faux positif ce matin, sans qu'aucun changement n'ait eu lieu côté application. Fixer une référence (un tag de version, un commit du dépôt de templates) et ne la faire évoluer que via une mise à jour planifiée et testée donne un comportement reproductible.

Planifier la mise à jour, séparément du scan de déploiement. Un job cron distinct, hebdomadaire, met à jour le jeu de templates, l'exécute contre un environnement de référence, et ne promeut la nouvelle version que si elle ne dégrade pas le taux de faux positifs connu.

on:
  schedule:
    - cron: "0 3 * * 1"

Un pipeline qui scanne avec un jeu de templates figé depuis six mois manque les CVE récentes. Un pipeline qui absorbe chaque mise à jour sans contrôle accumule les faux positifs jusqu'à ce que l'équipe désactive le job. La solution n'est ni l'un ni l'autre : c'est un flux de templates maintenu, versionné et validé en amont, que le pipeline consomme en confiance.

Éviter les faux positifs qui cassent le build

Un scan qui échoue le vendredi soir sur un faux positif, et qui bloque un déploiement urgent, tue la confiance de l'équipe dans l'outil en une seule occurrence. Deux garde-fous limitent ce risque :

  • Une allowlist explicite. Certains findings connus et acceptés (un en-tête absent volontairement sur un endpoint interne, par exemple) doivent pouvoir être exclus du gate sans modifier le template lui-même. Une liste d'identifiants de templates exclus, versionnée dans le dépôt, documente ce choix au lieu de le cacher dans une exception silencieuse.
  • Des matchers stricts côté templates. Un template qui déclare une vulnérabilité sur la seule présence d'un code de statut, sans vérifier le contenu de la réponse, génère du bruit dès qu'une page d'erreur générique renvoie le même code. La rigueur se joue en amont, dans la qualité du template lui-même, pas seulement dans la configuration du pipeline qui l'exécute.

En résumé

Brancher Nuclei sur un pipeline GitHub Actions demande trois choses : un job qui s'exécute au bon moment (après le déploiement, pas à chaque commit), un scoping qui garde le CI rapide (tags, sévérité, cibles précises, timeout), et un alerting qui hiérarchise plutôt que de tout remonter au même niveau. Mais l'intégration technique ne suffit pas : un tutoriel qui s'arrête là perd sa valeur en deux mois, dès que le jeu de templates devient obsolète ou que les faux positifs s'accumulent sans contrôle.

C'est cette continuité que couvre le flux de règles Nuclei ThreatClaw : un jeu de templates validé sur le moteur réel, priorisé par exploitation active et score de probabilité, mis à jour selon un calendrier maîtrisé, prêt à être consommé par votre pipeline sans que votre équipe ait à suivre elle-même chaque nouvelle CVE.

Articles liés