Semgrep est un analyseur statique open source qui cherche dans le code des motifs correspondant à des défauts de sécurité : injection, désérialisation risquée, cryptographie faible, secret écrit en dur. En CI, il se lance sur chaque pull request avec des jeux de règles publics et l’option --error, qui fait échouer le job au premier résultat. C’est le contrôle qui lit le code que l’équipe écrit, là où l’audit de dépendances ne lit que le lockfile.

Pourquoi une analyse statique en plus des autres contrôles ?

Parce qu’aucun autre contrôle de la chaîne ne lit le code applicatif sous l’angle de la sécurité. Chaque outil couvre une surface distincte :

ContrôleCe qu’il litCe qu’il trouve
Gitleaksl’historique gitdes secrets commités
pnpm audit, OSV-Scannerle lockfiledes dépendances vulnérables connues
Scan d’imagesles paquets des images livréesdes paquets système vulnérables
Semgreple code source du dépôtdes motifs de code dangereux

Un linter de style ne remplit pas ce rôle : ses règles visent la lisibilité et la cohérence, rarement les chemins d’attaque. Un contrôle d’analyse statique qui existe dans la liste des étapes sans rien vérifier est pire qu’un contrôle absent, parce qu’il donne l’illusion d’une couverture.

Qu’apporte Semgrep OSS ?

Semgrep OSS est le moteur open source de Semgrep, utilisable sans compte. Ses règles s’écrivent en YAML avec une syntaxe proche du code ciblé, ce qui les rend lisibles par un développeur qui ne connaît pas l’outil. Le moteur open source analyse chaque fichier séparément ; l’analyse qui suit les données d’un fichier à l’autre relève des offres commerciales de l’éditeur.

L’alternative la plus citée sur GitHub est CodeQL. Sur un dépôt privé, CodeQL suppose l’offre GitHub Advanced Security. Semgrep OSS tourne dans son image Docker officielle, sans jeton ni inscription, ce qui le rend indépendant de l’abonnement de la forge.

La sous-commande semgrep ci est pensée pour la plateforme Semgrep et son jeton d’application. Sans compte, on appelle directement l’analyseur avec des options --config, ce que fait l’exemple ci-dessous.

Quels jeux de règles choisir pour un projet TypeScript ?

Les jeux de règles du registre Semgrep se désignent par un préfixe p/. Pour une application Node.js écrite en TypeScript, trois jeux couvrent l’essentiel :

  • p/typescript : motifs propres à TypeScript et aux frameworks front courants ;
  • p/nodejs : API Node.js risquées (exécution de commandes, chemins de fichiers, expressions régulières construites dynamiquement) ;
  • p/secrets : secrets écrits en dur, en recouvrement partiel avec un scanner de secrets dédié.

Ces règles sont téléchargées depuis le registre à chaque exécution. Figer l’image de l’analyseur ne fige donc pas les règles : une règle ajoutée en amont peut faire échouer le job sans qu’une ligne du dépôt ait changé. Ce rouge se traite comme un nouvel avis de vulnérabilité sur une dépendance : on corrige, ou on qualifie par écrit.

Comment faire échouer la CI sur un résultat ?

Avec l’option --error, Semgrep retourne un code de sortie non nul dès qu’il produit un résultat, ce qui met le job en échec. Sans elle, les résultats s’affichent dans le journal et le job reste vert, ce qui revient à un contrôle informatif que personne ne lit.

Voici l’étape telle qu’elle peut figurer dans un workflow GitHub Actions, l’image étant épinglée par empreinte dans une variable :

docker run --rm \
  -v "${PWD}:/src" -w /src \
  "${SEMGREP_IMAGE}" \
  semgrep \
    --config=p/typescript \
    --config=p/nodejs \
    --config=p/secrets \
    --metrics=off \
    --error \
    --exclude=node_modules \
    --exclude=dist \
    --exclude=build \
    --exclude='*.min.js'

Trois options méritent attention :

  • --metrics=off désactive l’envoi de télémétrie à l’éditeur ;
  • les --exclude retirent le code tiers et le code généré, qui ne relèvent pas de l’équipe et noient les résultats utiles ;
  • l’épinglage de l’image par empreinte (semgrep/semgrep@sha256:…) évite qu’une nouvelle version de l’analyseur change le verdict entre deux exécutions.

Rendre le contrôle bloquant dès son introduction suppose de mesurer d’abord. Si le premier passage produit des dizaines de résultats, un job bloquant sera contourné ; s’il n’en produit aucun, le premier rouge signalera une régression réelle et non une dette héritée.

Comment traiter un faux positif ?

En le qualifiant au plus près du code, avec la raison écrite. Semgrep reconnaît un commentaire nosemgrep sur la ligne signalée, éventuellement suivi de l’identifiant de la règle :

// Chemin construit à partir d'une liste fermée de clés, jamais d'une entrée utilisateur.
const file = path.join(TEMPLATES_DIR, key); // nosemgrep: <identifiant-de-la-regle>

Les bonnes pratiques associées :

  • viser la règle, pas la ligne entière : nommer l’identifiant évite de masquer une autre règle qui se déclencherait au même endroit ;
  • écrire pourquoi dans un commentaire adjacent, pour que la relecture puisse contester la qualification ;
  • le mentionner dans la pull request, pour que l’exception soit vue au moment où elle entre ;
  • préférer la correction quand elle est peu coûteuse : un faux positif sur un motif dangereux signale souvent un code qu’on peut rendre plus évident.

Pour exclure des fichiers entiers, un fichier .semgrepignore ou des options --exclude conviennent, à réserver au code généré ou tiers.

Comment saas-core l’intègre

saas-core lance Semgrep dans un workflow dédié, .github/workflows/sast.yml, job semgrep, sur chaque push sur main, chaque pull request et sur déclenchement manuel. Le jeton du workflow est en lecture seule (permissions: contents: read).

Ce que contient le dépôt :

  • Semgrep OSS plutôt que CodeQL : l’en-tête du workflow explique que CodeQL exigerait GitHub Advanced Security sur un dépôt privé, alors que l’image officielle de Semgrep est publiée pour arm64 et ne demande ni compte ni jeton.
  • Image épinglée par empreinte dans la variable SEMGREP_IMAGE, pour que l’analyseur ne change pas entre deux exécutions.
  • Trois jeux de règles : p/typescript, p/nodejs et p/secrets, avec --metrics=off, --error et les exclusions montrées plus haut.
  • Introduit bloquant, mesure faite : selon l’en-tête du workflow, le premier passage a appliqué 74 règles à 183 fichiers et n’a produit aucun résultat.
  • Workflow séparé de ci, pour qu’un rouge d’analyse, qui peut venir d’une règle nouvelle du registre, se distingue d’un rouge de test.

Le dépôt ne contient ni règle Semgrep personnalisée ni annotation nosemgrep. La fiche outil developpeur/docs/architecture/outils/chaine-logicielle/semgrep.md de la documentation prescrit l’annotation nosemgrep avec sa raison pour un faux positif, et une correction accompagnée d’un test pour un vrai défaut.

Ce qui est bloquant et ce qui ne l’est pas : --error met le workflow sast en échec au premier résultat. En revanche, sast n’est pas lu par le gate de livraison de .github/workflows/release-images.yml, qui ne consulte que ci et e2e. La fiche de contrôle C25 classe donc ce contrôle comme partiel : le résultat est visible sur chaque pull request et consulté avant une étiquette, mais il n’arrête pas automatiquement une livraison.

Pour aller plus loin

Semgrep lit le code ; les secrets commités relèvent d’un scanner dédié, présenté dans Gitleaks : scanner tout l’historique git en CI. Les vulnérabilités des dépendances et leur qualification par VEX sont traitées dans OSV-Scanner, pnpm audit et VEX.

La liste de ce que comprend le kit saas-core, dont ses workflows de sécurité, figure sur la page tarifs.