📍 Guide principal du sujet : Pipeline SAST DAST SCA 2026 : architecture, outils et intégration CI/CD
Ce tutoriel orchestre dans GitLab CI 18 les briques SAST, SCA et DAST décrites dans le guide principal.
Pourquoi orchestrer la sécurité applicative dans GitLab CI
GitLab a unifié sous une seule plateforme le dépôt Git, le runner CI/CD, le registre de conteneurs et la gestion des vulnérabilités, ce qui en fait un environnement idéal pour matérialiser un pipeline DevSecOps de bout en bout. La version GitLab 18.8, disponible sur GitLab.com depuis janvier 2026, et la 18.9 pour Self-Managed depuis février 2026, complètent les améliorations introduites par la 18.0 — notamment AST_ENABLE_MR_PIPELINES qui active par défaut les pipelines de merge request pour les scans de sécurité.
Les templates officiels couvrent SAST (motorisé par Semgrep depuis 2024, remplaçant les anciens analyzers par langage), Secret Detection (basé sur Gitleaks), Dependency Scanning, Container Scanning (basé sur Trivy en 2026), Coverage-guided Fuzz Testing, Infrastructure-as-Code Scanning et DAST (basé sur OWASP ZAP, analyzer v4). Le tableau Vulnerability Report agrège les findings tous outils confondus, déduplique automatiquement et offre un workflow d’approbation. Ce tutoriel construit pas-à-pas un pipeline qui combine SAST + Dependency Scanning + Container Scanning + DAST avec un seuil bloquant sur les nouvelles vulnérabilités.
Prérequis
- Une instance GitLab 18.8+ (gitlab.com ou Self-Managed) avec runners CI fonctionnels.
- Un projet contenant du code applicatif (Python, Node, Java, Go, etc.) et un
Dockerfilepour le container scanning. - Une licence GitLab Premium ou Ultimate pour activer le Vulnerability Report et les seuils de Merge Request Approval — la SAST/Secret Detection est disponible en Free, mais le tri centralisé requiert Premium minimum.
- Niveau attendu : à l’aise avec un fichier
.gitlab-ci.yml, comprendre la notion de stage, job et artefact. - Temps estimé : 60 minutes pour le pipeline minimal, 120 pour la version avec DAST authentifié.
Étape 1 — Activer les templates Security d’un seul include
L’approche la plus rapide consiste à inclure le template Auto DevOps Security maintenu par GitLab. Cette inclusion ajoute une douzaine de jobs sécurité en arrière-plan, exécutés automatiquement quand les conditions sont remplies (présence d’un Dockerfile pour Container Scanning, présence d’un manifest de dépendances pour Dependency Scanning, etc.). Créer ou éditer le fichier .gitlab-ci.yml à la racine du dépôt.
stages:
- test
- build
- scan
- deploy
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
variables:
AST_ENABLE_MR_PIPELINES: "true"
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp, vendor"
La variable AST_ENABLE_MR_PIPELINES: "true" garantit que les scans tournent dans les pipelines de merge request, ce qui est désormais le déclencheur recommandé. La variable SAST_EXCLUDED_PATHS évite de scanner les répertoires de tests qui contiennent souvent des credentials de fixture et polluent le rapport. Pousser le commit, observer la pipeline qui s’exécute, et constater dans Security & Compliance → Vulnerability Report l’apparition des premiers findings.
Étape 2 — Builder l’image Docker dans le pipeline
Le Container Scanning analyse une image OCI ; il faut donc construire l’image avant l’étape de scan. La pratique recommandée est d’utiliser BuildKit ou Buildah dans un job dédié, puis de pousser l’image dans le registre intégré GitLab Container Registry. Ajouter ces deux jobs dans le stage build.
build_image:
stage: build
image: docker:27-cli
services:
- docker:27-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$IMAGE_TAG" .
- docker push "$IMAGE_TAG"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Le service docker:27-dind fournit le démon Docker-in-Docker requis par docker build. Les variables $CI_REGISTRY_* sont injectées automatiquement par GitLab et donnent accès au registre projet sans secret manuel. Le tag de l’image inclut le SHA court du commit, ce qui permet au job suivant de scanner exactement la même image que celle qui sera potentiellement déployée. La règle rules: limite le job aux MR et au push sur la branche par défaut, évitant les builds inutiles sur les branches de feature.
Étape 3 — Brancher Container Scanning sur l’image construite
Le template Container Scanning de GitLab utilise Trivy en 2026. Par défaut il scanne $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG, mais on peut le pointer explicitement sur le tag immutable produit à l’étape précédente avec CS_IMAGE.
container_scanning:
stage: scan
variables:
CS_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
CS_SEVERITY_THRESHOLD: "HIGH"
needs:
- build_image
La déclaration needs: build_image permet de paralléliser les autres scans sans attendre que build_image soit terminé pour le SAST ou le Dependency Scanning ; seul le Container Scanning attend l’image. Le seuil CS_SEVERITY_THRESHOLD: "HIGH" indique que le job retourne en échec uniquement si une CVE de sévérité ≥ HIGH est trouvée, mais reste informatif pour les MEDIUM/LOW. Cette stratégie évite la rupture de pipeline sur du bruit tout en bloquant les vraies urgences.
Étape 4 — Ajouter le DAST sur l’environnement de Review App
Le DAST nécessite une application déployée et accessible. La pratique GitLab consiste à coupler DAST avec un environnement de Review App créé dynamiquement par MR : chaque MR déploie une preview, le DAST l’attaque, puis l’environnement est détruit à la fermeture de la MR. Ajouter le template DAST et le job de Review App.
include:
- template: Security/DAST.gitlab-ci.yml
review:
stage: deploy
image: alpine:3.20
script:
- apk add --no-cache curl bash openssh-client
- ./scripts/deploy-review.sh "$CI_COMMIT_REF_SLUG"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://review-$CI_COMMIT_REF_SLUG.example.com
on_stop: stop_review
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
dast:
stage: scan
variables:
DAST_WEBSITE: "https://review-$CI_COMMIT_REF_SLUG.example.com"
DAST_FULL_SCAN_ENABLED: "false"
needs:
- review
La variable DAST_FULL_SCAN_ENABLED: "false" bascule sur le mode passif (baseline scan) qui n’envoie pas de payload offensif. Pour une vraie analyse, basculer à true sur les pipelines nocturnes via une règle rules: - if: $CI_PIPELINE_SOURCE == "schedule". Le mode actif est destructeur : l’attaquer en MR risque d’écrire des données dans la base, donc le séparer en pipeline scheduled hors MR est une bonne pratique.
Étape 5 — Configurer l’authentification du DAST
La majorité des vulnérabilités intéressantes se trouvent derrière l’authentification. ZAP exécuté via DAST template peut s’authentifier via formulaire ou via OAuth/OIDC selon la configuration. La méthode formulaire est la plus courante.
dast:
variables:
DAST_WEBSITE: "https://review-$CI_COMMIT_REF_SLUG.example.com"
DAST_AUTH_URL: "https://review-$CI_COMMIT_REF_SLUG.example.com/login"
DAST_USERNAME: "qa@example.com"
DAST_PASSWORD: "$DAST_PASSWORD_SECRET"
DAST_AUTH_USERNAME_FIELD: "id:email"
DAST_AUTH_PASSWORD_FIELD: "id:password"
DAST_AUTH_SUBMIT_FIELD: "css:button[type='submit']"
DAST_AUTH_VERIFICATION_URL: "https://review-$CI_COMMIT_REF_SLUG.example.com/account"
Les sélecteurs (id:email, css:button[type='submit']) suivent la syntaxe ZAP-Selector. Le mot de passe est stocké comme variable CI/CD masked + protected dans Settings → CI/CD → Variables. Le DAST tente de se connecter, vérifie qu’il atteint DAST_AUTH_VERIFICATION_URL en mode authentifié, puis attaque les surfaces protégées. Si l’authentification échoue, le rapport l’indique clairement et tous les findings deviennent suspect (couverture incomplète) ; vérifier les sélecteurs et la présence éventuelle d’un CAPTCHA bloquant.
Étape 6 — Définir les seuils dans la Merge Request Approval
L’objectif final n’est pas de générer du rapport mais de bloquer ce qui doit l’être. GitLab Premium expose les Merge Request Approval Policies dans Security & Compliance → Policies. Créer une politique YAML qui exige une approbation supplémentaire dès qu’une nouvelle vulnérabilité critique apparaît dans une MR.
type: scan_result_policy
name: Block critical SAST findings
description: Require security team approval if a CRITICAL SAST finding appears in a MR
enabled: true
rules:
- type: scan_finding
branches: [main]
scanners: [sast, dependency_scanning, container_scanning]
vulnerabilities_allowed: 0
severity_levels: [critical]
vulnerability_states: [newly_detected]
actions:
- type: require_approval
approvals_required: 1
role_approvers: [security_engineer]
La règle se déclenche uniquement sur les findings newly_detected, ce qui évite de bloquer les MR à cause de la dette historique présente sur main. Le déclencheur basé sur la sévérité (critical) garde la friction modérée. Le rôle security_engineer doit exister dans le projet (créé via Settings → Members avec custom role). Quand la politique se déclenche, le bouton Merge devient grisé et un commentaire automatique liste les findings concernés.
Étape 7 — Importer les rapports dans DefectDojo
GitLab Vulnerability Report convient pour le quotidien interne, mais une équipe sécurité multi-équipe préfère souvent un agrégateur central. Les artefacts de chaque scan sont disponibles au format JSON GitLab natif et peuvent être convertis en SARIF puis importés dans DefectDojo. Ajouter un job final qui pousse les rapports.
push_to_defectdojo:
stage: scan
image: python:3.12-slim
needs:
- sast
- dependency_scanning
- container_scanning
script:
- pip install --quiet defectdojo_api_v2
- python ./scripts/push-to-defectdojo.py
variables:
DOJO_URL: "https://defectdojo.example.com"
DOJO_API_KEY: "$DOJO_API_KEY"
DOJO_PRODUCT_ID: "42"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Le script push-to-defectdojo.py appelle l’endpoint /api/v2/import-scan/ avec chaque artefact JSON GitLab en sélectionnant le scanner type approprié (GitLab SAST Report, GitLab Dependency Scanning Report, etc.). DefectDojo connaît nativement plus de 160 formats, dont les natifs GitLab. La règle $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH évite de polluer DefectDojo avec les findings éphémères des branches de feature, qui restent visibles uniquement dans le Vulnerability Report GitLab.
Étape 8 — Activer les schedules pour les scans approfondis
Certains scans (DAST full, Coverage-guided Fuzzing, IaC scanning sur tout le repo) prennent 30 à 60 minutes et n’ont pas leur place dans une MR. Les pipelines schedulés s’exécutent à intervalle régulier sur la branche par défaut, en dehors du flux des développeurs. Configurer dans CI/CD → Schedules un cron quotidien à 02:00 et passer le mode full en variable.
dast_full:
extends: dast
variables:
DAST_FULL_SCAN_ENABLED: "true"
DAST_TARGET_AVAILABILITY_TIMEOUT: "600"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULED_JOB == "dast_full"
La variable SCHEDULED_JOB: dast_full est définie au niveau du schedule lui-même, ce qui permet de différencier le schedule DAST du schedule de fuzz. Cette segmentation garde la matrice des jobs lisible et évite que tous les schedules tournent en parallèle et saturent les runners. Le résultat alimente la même Vulnerability Report que les scans MR, avec un tag scheduled pour distinguer les sources.
Erreurs fréquentes
| Symptôme | Cause | Solution |
|---|---|---|
| SAST ne détecte aucun langage | Le repo est essentiellement composé de fichiers de configuration | Forcer la liste avec SAST_EXCLUDED_ANALYZERS: "" et SAST_DEFAULT_ANALYZERS: "semgrep" |
| Container Scanning échoue avec image not found | CS_IMAGE ne correspond pas au tag réellement poussé |
Aligner explicitement avec $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA dans build et scan |
| DAST authentification échoue | Sélecteur CSS/ID changé suite à un refactor frontend | Tester les sélecteurs dans la console DevTools, utiliser data-testid stables côté frontend |
| Pipeline trop long > 30 minutes | Tous les scans en série sur une seule MR | Utiliser needs: pour paralléliser, déplacer DAST full en schedule nocturne |
| Vulnerability Report montre la même CVE deux fois | Pas de déduplication entre Container Scanning et Dependency Scanning | Activer SECURE_LOG_LEVEL: "info" et configurer la dedup dans Settings → Security |
| MR Approval Policy ne se déclenche jamais | Politique appliquée à main alors que la MR cible develop |
Étendre branches: [main, develop] dans la politique |
FAQ
Quels templates Security sont gratuits sur GitLab Free ? SAST et Secret Detection sont disponibles en Free depuis 2022. Dependency Scanning, Container Scanning, License Compliance, DAST et Vulnerability Report nécessitent Premium ou Ultimate selon la fonctionnalité précise. La matrice exacte est documentée dans Security & Compliance → Configuration de chaque projet.
Comment intégrer Trivy en remplacement du Container Scanning natif ? Désactiver le template Container Scanning et ajouter un job trivy qui utilise aquasec/trivy:latest avec sortie SARIF + GitLab JSON. Les artefacts GitLab JSON sont reconnus par le Vulnerability Report. Attention au pinning de version après l’incident de mars 2026 : utiliser aquasec/trivy:0.70.0 minimum, idéalement par digest sha256.
Que devient AutoDevOps en 2026 ? AutoDevOps continue d’exister comme accélérateur (un seul include et tout marche), mais l’approche recommandée est désormais d’inclure les templates Security individuellement pour garder la maîtrise des variables. AutoDevOps reste un bon point de départ pour démontrer la valeur, à remplacer par une configuration explicite dès la maturité acquise.
Le DAST consomme-t-il beaucoup de minutes CI ? En mode passif, 5 à 10 minutes par MR. En mode full sur une application authentifiée non triviale, 30 à 90 minutes. Sur GitLab.com avec runners partagés, anticiper l’impact sur le quota minutes-CI Premium ; pour Self-Managed, dimensionner les runners ou les dédier au scheduled.
Comment exporter les findings en SBOM CycloneDX ? Le job Dependency Scanning produit des fichiers gl-sbom-*.cdx.json au format CycloneDX (versions 1.4 à 1.6 supportées). La feature dependency_scanning_using_sbom_reports est officielle depuis GitLab 17.3 et activée par défaut depuis 17.5. L’artefact est attaché à la pipeline et peut être consommé par toute plateforme compatible.
Tutoriels associés
- Auto-héberger SonarQube Community Build 26.4 sur un VPS Linux pas-à-pas — pour brancher SonarQube en complément du SAST natif GitLab via décoration de PR.
- Bloquer les PR sur les nouvelles vulnérabilités sans freiner l’équipe — méthodologie applicable aux Merge Request Approval Policies.
- 🔝 Retour au guide principal : Pipeline SAST DAST SCA 2026 : architecture, outils et intégration CI/CD