Semgrep est devenu en 2025-2026 le SAST de choix pour les équipes DevSecOps qui veulent du vrai signal et peu de bruit. Sa force : la possibilité d'écrire en quelques minutes des règles spécifiques à votre stack, qui détectent vos patterns dangereux à vous, pas seulement les généralités OWASP. Voici comment construire votre catalogue de règles maison.
Pourquoi les règles génériques ne suffisent pas
Un SAST générique (SonarQube, Snyk, Checkmarx) détecte :
- Injection SQL via concaténation de strings.
- XSS via output non-échappé.
- Secrets hardcodés.
- Crypto faible.
C'est utile, mais ne détecte pas :
- Vous avez une fonction
unsafe_db_query()qu'aucun nouveau code ne devrait appeler. - Vous avez une API interne qui ne doit jamais être exposée publiquement.
- Vous avez un SDK fournisseur dont une méthode est dangereuse.
- Vous avez un pattern de rate-limiting interne qu'il faut systématiquement appliquer.
Ces détections nécessitent de la connaissance métier. Semgrep permet de la coder.
Anatomie d'une règle Semgrep
Une règle est un YAML qui décrit un pattern AST à matcher. Exemple basique :
``yaml rules: - id: no-print-in-production pattern: print(...) message: "print() ne doit pas exister en production. Utiliser le logger structuré." languages: [python] severity: WARNING ``
Cette règle détecte tout print() dans du code Python.
Patterns avancés
Match avec contexte
``yaml rules: - id: dangerous-eval-with-user-input patterns: - pattern: eval($X) - pattern-inside: | def $F(request, ...): ... message: "eval() avec input depuis la request HTTP — vulnérabilité de code injection." languages: [python] severity: ERROR ``
Détecte eval() uniquement quand il est dans une fonction qui prend request en paramètre.
Match avec exclusion
``yaml rules: - id: hardcoded-api-key patterns: - pattern-regex: '["\']sk-[a-zA-Z0-9]{32,}["\']' - pattern-not-inside: | # test setup ... message: "Clé API hardcodée détectée." severity: ERROR ``
Détecte les patterns ressemblant à des clés API, mais ignore les blocs marqués comme tests.
Taint mode (data flow)
``yaml rules: - id: user-input-to-shell mode: taint pattern-sources: - pattern: request.GET.get(...) - pattern: request.POST.get(...) pattern-sinks: - pattern: subprocess.run(...) - pattern: os.system(...) message: "User input flows to shell command — command injection possible." languages: [python] severity: ERROR ``
Détecte le data flow d'un input utilisateur vers une commande shell.
Exemples pratiques de règles entreprise
Règle 1 — Forbidden API
Vous avez décrécié legacy_db_query(), plus aucun nouveau code ne doit l'appeler.
``yaml rules: - id: no-legacy-db-query pattern: legacy_db_query(...) message: "legacy_db_query() est dépréciée. Utiliser db.query() avec ORM." languages: [python] severity: ERROR ``
Règle 2 — Required wrapper
Toutes les routes admin doivent passer par @require_admin.
``yaml rules: - id: admin-route-without-auth patterns: - pattern: | @app.route("/admin/...", ...) def $F(...): ... - pattern-not: | @app.route("/admin/...", ...) @require_admin def $F(...): ... message: "Route admin sans @require_admin." languages: [python] severity: ERROR ``
Règle 3 — Token scope check
Tout appel à votre API privée doit passer un check de scope.
``yaml rules: - id: private-api-without-scope-check patterns: - pattern: api.private.$M(...) - pattern-not-inside: | if ensure_scope($SCOPE): ... message: "Appel API privée sans check de scope." languages: [python] severity: WARNING ``
Règle 4 — Crypto choice
Forcer l'usage d'AES-GCM, interdire AES-CBC sans HMAC.
``yaml rules: - id: aes-cbc-without-hmac patterns: - pattern-either: - pattern: AES.new($KEY, AES.MODE_CBC, ...) - pattern-not-inside: | # validate with HMAC ... message: "AES-CBC nécessite un HMAC séparé. Préférer AES-GCM." languages: [python] severity: ERROR ``
Règle 5 — Tenant isolation
Vos requêtes DB doivent toujours inclure tenant_id.
``yaml rules: - id: db-query-without-tenant-filter pattern: | db.query($MODEL).filter(...) pattern-not: | db.query($MODEL).filter(tenant_id=..., ...) message: "Requête DB sans filtre tenant_id — risque de cross-tenant leak." languages: [python] severity: ERROR ``
C'est la règle la plus précieuse pour un SaaS multi-tenant. Empêche les fuites cross-tenant à la racine.
Workflow d'écriture
- Identifier le pattern dangereux récurrent (post-mortem incident, code review répétitive).
- Écrire 2-3 cas d'exemple de code à détecter et 2-3 cas à ne pas détecter.
- Tester la règle sur le repo :
semgrep --config myrule.yaml. - Ajuster pour minimiser faux positifs.
- Intégrer dans la CI.
Temps typique : 30-90 minutes par règle.
Intégration CI/CD
```yaml
- name: Semgrep scan
uses: returntocorp/semgrep-action@v1 with: config: | .semgrep/ # vos règles p/security-audit # pack OWASP p/secrets # pack secrets ```
Bloquant sur ERROR, warning sur WARNING.
Versioning et review
Vos règles Semgrep sont du code :
- Stockées dans
.semgrep/dans votre repo principal (ou un repo dédié). - Reviewées comme du code (PR, CODEOWNERS).
- Documentées : pourquoi cette règle, quel risque, qui l'a écrite.
- Testées : exemples de code attendu détecté ou ignoré.
Stratégie d'adoption
Semaine 1 — Inventaire
Lister les patterns dangereux récurrents à interdire. Output : une liste de 10-20 règles candidates.
Semaine 2 — Écriture des 5 règles les plus impactantes
Tester sur le code existant, ajuster.
Semaine 3 — Intégration CI bloquante
D'abord en mode "warning" pour évaluer le bruit, puis bloquant après ajustement.
Mois 2-3 — Croissance organique
Chaque post-mortem incident ajoute potentiellement une règle qui aurait évité le problème.
Mois 6+ — Catalogue stable
50-100 règles entreprise, maintenues, comprises par l'équipe. C'est un actif technique réel.
ROI réel
Une règle Semgrep bien écrite :
- Détecte des bugs sécurité au commit, plus en runtime ou en pen-test.
- Évite la dette technique de re-explication ("encore une fois, ne fais pas X").
- Documente exécutablement les patterns dangereux propres à votre stack.
Coût : 30 min d'écriture × 50-100 règles = 25-50 heures d'investissement initial.
Bénéfice : prévention quasi-systématique de nouvelles instances des patterns détectés.