Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

SAST : analyser le code source pour trouver les failles

Par mis à jour le

Améliorer cette page

Le SAST (Static Application Security Testing) lit le code source d'une application sans l'exécuter et y repère les failles que vous avez vous-même écrites : injections, données non validées, appels dangereux. Cette leçon s'adresse à un DevOps ou un développeur qui veut comprendre ce qu'un scanner SAST voit réellement, ce qu'il ne voit pas, et comment le placer dans un pipeline sans noyer l'équipe sous les alertes. Les exemples s'appuient sur Semgrep 1.178.0, relevé sur une petite application Flask volontairement fautive.

  • Comprendre comment un scanner suit une donnée de sa source à son sink
  • Distinguer ce que voient le SAST, la SCA et le DAST, et ce qui leur échappe
  • Interpréter un vrai résultat SAST et repérer le bruit qu'il contient
  • Placer le SAST dans un pipeline avec un blocage gradué
  • Traiter un faux positif sans désactiver le contrôle pour tout le monde
  • Rattacher ce contrôle à l'exigence SOCLE-APP-AST-1 du référentiel SOCLE

Le SAST est une analyse statique : l'outil lit le code comme un relecteur très patient, sans lancer l'application. Il répond à une question précise, « mon code contient-il des constructions dangereuses ? », et c'est cette précision qui définit à la fois sa force et ses limites. Il voit tout le code, y compris une branche jamais exécutée par les tests, mais il ne voit ni l'application en marche ni le code des bibliothèques qu'elle importe.

Un scanner moderne ne se contente pas de chercher des mots dans le texte. Il construit une représentation du programme, puis suit le chemin des données. Le vocabulaire est simple : une source est un point d'entrée contrôlé par l'extérieur (un paramètre HTTP), un sink est un appel dangereux (une requête SQL, une commande système), et le taint tracking consiste à vérifier si une donnée « teintée » par une source atteint un sink sans être assainie.

Le schéma montre ce raisonnement sur le cas de l'injection SQL : la donnée part d'un paramètre HTTP, traverse une variable, et ne produit un constat que si elle atteint le sink sans avoir été assainie en chemin.

Suivi de flux d'un scanner SAST : une donnée part d'une source, un paramètre HTTP, traverse une variable intermédiaire ; si elle n'est pas assainie, elle atteint un sink, requête SQL ou commande système, et la règle taint produit un constat ; si elle est assainie, il n'y a pas de constat.nonouiSource :paramètre HTTPVariableintermédiaireDonnéeassainie ?Sink : requête SQL,commande systèmeConstat SAST(règle taint)Pas de constat
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %
  1. Analyse syntaxique : le code devient un arbre syntaxique (AST), ce qui permet de reconnaître une concaténation ou un appel de fonction quelle que soit la mise en forme.

  2. Correspondance de motifs : chaque règle décrit une construction dangereuse, par exemple « un appel à subprocess avec shell=True ».

  3. Suivi de flux : pour les règles de type taint, l'outil relie la source au sink à travers les variables intermédiaires.

  4. Rapport : chaque constat indique la règle, le fichier, la ligne, une sévérité et souvent une piste de correction.

Le SAST excelle sur les failles qui ont une forme reconnaissable dans le code : une donnée externe qui finit dans une requête, une fonction de hachage obsolète, une désérialisation non contrôlée. Il est en revanche structurellement aveugle à tout ce qui ne s'écrit pas en une construction locale : un contrôle d'accès oublié dans la logique métier, une faille dans une dépendance, une mauvaise configuration du serveur en production.

Le SAST voit bienLe SAST ne voit pas
Injections SQL, commande, chemin, XSS côté serveurUn contrôle d'accès absent dans la logique métier
Cryptographie faible (MD5, SHA-1, mode ECB)Les failles des dépendances tierces
Désérialisation de données non fiablesLa configuration réelle du serveur déployé
Appels système dangereux (shell=True, eval)Ce qui dépend de l'exécution (session, timing)

Ces trois familles de contrôles se complètent parce qu'elles regardent trois objets différents. Le SAST lit votre code, la SCA (Software Composition Analysis) inventorie les bibliothèques que vous importez et les confronte aux vulnérabilités publiées, le DAST attaque l'application en marche. La fuite d'Equifax en 2017, 147 millions de personnes exposées, l'illustre bien : elle passait par une faille connue d'Apache Struts, une dépendance non mise à jour. C'est un cas d'école pour la SCA, pas pour le SAST, qui ne lit pas le code de Struts.

ContrôleCe qu'il litQuandExemple de faille trouvée
SASTvotre code sourcedès le commitinjection SQL dans votre route
SCAvos dépendancesdès le commitStruts vulnérable (Equifax)
DASTl'application en marcheaprès déploiement de testen-tête de sécurité absent, faille exploitable

Rien ne remplace un vrai scan pour comprendre le métier. L'application de test tient en deux routes Flask : /user construit une requête SQL par concaténation, /ping passe un paramètre à une commande système avec shell=True. Le scan utilise le jeu de règles p/python du registre Semgrep, téléchargé au lancement, et l'image officielle épinglée par digest. L'option --metrics=off coupe l'envoi de statistiques d'usage à l'éditeur.

Fenêtre de terminal
IMG="semgrep/semgrep:1.178.0@sha256:32e459968daabe7ab86968184a29109b9564aa00392401156f9788452b42786b"
docker run --rm -v "$PWD:/src" -w /src "$IMG" \
semgrep scan --config p/python --metrics=off .

Le scan remonte cinq constats pour deux failles réelles :

LigneRègleSévérité
9sql-injection-db-cursor-execute (règle Django)WARNING
11tainted-sql-string (règle Flask, taint)ERROR
19subprocess-injectionERROR
19dangerous-subprocess-useERROR
19subprocess-shell-trueERROR

Ce tableau contient déjà les deux leçons du métier. D'abord, plusieurs règles signalent la même faille : trois constats pour une seule ligne 19. Ensuite, une règle écrite pour Django se déclenche sur du code Flask : elle a raison sur le fond, mais son intitulé égare. Le travail de tri commence donc avant même de parler de faux positifs, et il justifie de choisir un jeu de règles adapté à votre framework plutôt que d'activer tout le registre.

Le piège le plus coûteux est ailleurs : ce scan sort avec le code 0, malgré cinq constats. Sans l'option --error, semgrep scan ne fait échouer aucun pipeline, et un job « vert » peut cacher des injections. Avec --error, le même scan sort en code 1, et c'est seulement là que le contrôle devient un contrôle bloquant.

Le choix se fait sur trois critères : les langages de vos dépôts, la licence au regard de votre usage, et la facilité à écrire vos propres règles. Dans cette formation, Semgrep sert d'implémentation de référence, pour ses règles lisibles et sa sortie SARIF, le format standard que GitHub et GitLab savent afficher. La leçon suivante lui est entièrement consacrée.

OutilLangagesLicence et usagePoint fort
Semgrep (CE)30+moteur libre, LGPLrègles lisibles, rapide, SARIF
BanditPythonlibre, Apache 2.0simple, spécialisé Python
BearerJS, TS, Ruby, Python, Java, Go, PHPsource disponible, Elastic License 2.0flux de données sensibles
CodeQLC/C++, C#, Go, Java, JS, Python, Ruby...gratuit seulement pour le code open sourcerequêtes puissantes, intégré à GitHub
SonarQube25+édition communautaire LGPL, éditions avancées payantesqualité et sécurité dans un tableau de bord

La ligne CodeQL mérite une lecture attentive. Ses conditions d'utilisation autorisent l'outil sur du code publié sous licence open source ou pour la recherche académique, mais excluent explicitement l'analyse automatisée en intégration continue d'un code propriétaire sans contrat commercial. Sur un dépôt privé d'entreprise, il passe donc par une licence GitHub Advanced Security.

Le même principe statique s'applique à d'autres cibles que le code applicatif : les scanners IaC (Checkov, Trivy, KICS) lisent Terraform, Kubernetes ou un Dockerfile, les scanners de secrets (Gitleaks) lisent le code et l'historique Git. Ils ont leurs propres leçons dans la formation. Notez au passage que tfsec a été fondu dans Trivy par son éditeur, qui recommande désormais la migration : un guide qui le présente encore comme outil d'avenir est périmé.

Le SAST se place à deux endroits qui ne jouent pas le même rôle. Sur le poste, un hook pre-commit donne un retour immédiat au développeur, mais il reste contournable d'un --no-verify. En CI, le même scan devient incontournable et peut bloquer la fusion. Le poste sert le confort, la CI sert la garantie : l'exigence SOCLE-APP-AST-1 demande précisément un SAST exécuté à chaque build, avec des constats critiques bloquants.

Semgrep publie ses hooks dans le dépôt semgrep/pre-commit, versionné comme l'outil. Le hook ne scanne que les fichiers modifiés du commit, ce qui le garde rapide. L'option --skip-unknown-extensions évite qu'il échoue sur un type de fichier qu'aucune règle ne couvre.

.pre-commit-config.yaml
repos:
- repo: https://github.com/semgrep/pre-commit
rev: v1.178.0
hooks:
- id: semgrep
args: ["--config", "p/python", "--error", "--skip-unknown-extensions"]

L'ancienne action semgrep-action est dépréciée par son éditeur, qui recommande de lancer Semgrep directement dans son image officielle. Les deux exemples ci-dessous suivent cette recommandation, avec l'image épinglée par digest et l'option --error qui transforme les constats en échec du job.

Le job tourne dans le conteneur Semgrep. permissions: {} retire tous les droits par défaut, contents: read suffit pour cloner, et persist-credentials: false empêche le jeton de rester dans .git/config.

.github/workflows/sast.yml
name: SAST
on:
pull_request:
permissions: {}
jobs:
semgrep:
runs-on: ubuntu-24.04
permissions:
contents: read
container:
image: semgrep/semgrep:1.178.0@sha256:32e459968daabe7ab86968184a29109b9564aa00392401156f9788452b42786b
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Semgrep, échec sur constat
run: semgrep scan --config p/python --error --metrics=off .

Activer un SAST bloquant sur un dépôt ancien fait échouer chaque merge request à cause de la dette existante, et l'équipe finit par désactiver le contrôle. L'option --baseline-commit règle ce problème : Semgrep compare au commit indiqué et ne rapporte que les constats introduits depuis. Mesuré sur le dépôt de test, le scan complet remonte 5 constats ; avec la branche principale comme référence, il n'en remonte que 3, ceux de la nouvelle route /ping, et sort bien en code 1 avec --error.

Fenêtre de terminal
# Ne rapporter que ce qui a été introduit depuis la branche principale
semgrep scan --config p/python --error --baseline-commit "$(git merge-base origin/main HEAD)" .

La dette ne disparaît pas pour autant : elle se traite dans un backlog daté, avec un scan complet planifié à côté, sinon la baseline devient un angle mort permanent.

Bloquer sur tout paralyse les équipes, ne bloquer sur rien produit des rapports que personne ne lit. Une politique graduée fixe, par sévérité, ce qui bloque la fusion et ce qui ouvre seulement un ticket. Le point de départ raisonnable : bloquer sur les sévérités ERROR des règles de type taint, qui suivent une donnée externe réelle, et traiter le reste en revue.

Sévérité SemgrepActionExemple
ERRORbloquer la fusiondonnée HTTP qui atteint une requête SQL
WARNINGticket avec échéanceconstruction suspecte sans flux prouvé
INFOinformation en revuebonne pratique non suivie

Un faux positif est une alerte sur du code qui n'est pas réellement exploitable : la donnée est validée ailleurs, ou le code est un exemple de test. Mal traités, ils détruisent la confiance dans l'outil : une équipe qui ignore cent alertes finit par ignorer la cent unième, la vraie. Trois mécanismes existent, du plus ciblé au plus large, et le plus ciblé est presque toujours le bon.

Le commentaire nosemgrep se pose sur la ligne où le constat est rapporté, pas sur la ligne qui vous semble fautive. Mesuré sur une variante de l'application de test, où la requête est d'abord construite dans une variable query puis passée à cursor.execute(query) : un # nosemgrep posé sur la ligne execute n'a rien retiré, parce que les constats étaient rapportés sur d'autres lignes. Posé sur la ligne qui construit query, il a retiré ce seul constat, et l'autre règle qui signalait la même faille est restée active.

# La forme qui nomme la règle n'éteint que celle-ci, sur cette ligne
query = "SELECT * FROM users WHERE id = " + user_id # nosemgrep: python.flask.security.injection.tainted-sql-string.tainted-sql-string

Nommez toujours la règle et justifiez l'exception en revue de code. Mesuré : avec l'identifiant exact, le constat disparaît ; avec l'identifiant d'une autre règle, il reste, ce qui protège d'une exception trop large. Un nosemgrep nu, sans identifiant, éteint en revanche toutes les règles de la ligne. Pour Bandit, l'équivalent est # nosec B105, avec l'identifiant du test.

Le fichier .semgrepignore, à la racine du dépôt, exclut des chemins entiers avec la syntaxe d'un .gitignore. Un piège mesuré change tout : sans ce fichier, Semgrep applique une liste d'exclusion par défaut qui écarte déjà tests/, et dès qu'un .semgrepignore existe, il remplace cette liste au lieu de la compléter. Un fichier qui ne contient que qa/ fait donc soudain scanner tests/, et un fichier vide fait tout scanner. Reprenez toujours les exclusions par défaut que vous voulez garder. C'est le bon outil pour du code généré ou des jeux de données, et un mauvais pour des tests qui contiennent de la vraie logique.

.semgrepignore
tests/
vendor/
*.min.js

Changer de règles plutôt que de multiplier les exceptions

Section intitulée « Changer de règles plutôt que de multiplier les exceptions »

Quand une même règle produit des faux positifs partout, le problème n'est plus la ligne mais le jeu de règles. Remplacez un registre large par celui de votre framework, ou écrivez une règle maison plus précise : c'est l'objet de la leçon Semgrep. Une exclusion globale qui grossit à chaque sprint est un contrôle désactivé qui ne dit pas son nom.

La moitié des incidents SAST ne sont pas des failles mais des pipelines qui mentent. Un job vert ne prouve rien tant que vous n'avez pas vérifié qu'il peut échouer : poussez une fois une faille volontaire sur une branche de test et regardez le job tomber.

SymptômeCause probableSolution
Job vert malgré des constatssemgrep scan sans --error sort en code 0Ajouter --error
Erreur au téléchargement des règlesPas d'accès réseau au registre SemgrepOuvrir l'accès ou versionner les règles dans le dépôt
nosemgrep sans effetCommentaire sur la mauvaise ligneLe poser sur la ligne rapportée par le constat
Chaque MR échoue sur l'ancien codeDette existante--baseline-commit et backlog daté
Une règle Django sur du code FlaskRegistre trop largeChoisir le jeu de règles du framework
  1. Le SAST lit votre code sans l'exécuter et suit une donnée de sa source à son sink.
  2. Il ne voit ni les dépendances (SCA) ni l'application en marche (DAST) : Equifax était un cas de SCA.
  3. Un vrai scan produit des doublons : plusieurs règles pour une seule faille.
  4. Sans --error, semgrep scan sort en code 0 et ne bloque rien.
  5. --baseline-commit ne bloque que les failles nouvelles, la dette se traite à part.
  6. nosemgrep se pose sur la ligne rapportée, en nommant la règle.
  7. CodeQL n'est gratuit que pour le code open source.
  8. Preuve attendue par SOCLE-APP-AST-1 : un job SAST bloquant et des tickets sur les constats critiques.

Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn