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

Gouvernance du code source : rulesets, tags et CODEOWNERS

Par mis à jour le

Améliorer cette page

Vous signez vos images, vous scannez vos dépendances, vous générez des attestations. Mais qui peut modifier votre code source ? Si n'importe quel compte peut pousser sur main ou déplacer un tag de version, toute la sécurité en aval se contourne par l'amont. Cette leçon s'adresse aux développeurs et aux responsables de dépôt qui veulent qu'aucune modification n'atteigne la branche principale sans validation explicite. Elle couvre les rulesets GitHub et les branches protégées GitLab, la protection des tags, le fichier CODEOWNERS et la documentation des règles de contribution.

  • Comprendre pourquoi un dépôt sans règles est le premier risque d'une chaîne CI/CD, à partir de l'attaque tj-actions
  • Protéger la branche principale avec un ruleset GitHub ou une branche protégée GitLab, puis le vérifier
  • Verrouiller les tags de version, que la protection de branche ne couvre pas
  • Désigner les relecteurs obligatoires des zones sensibles avec CODEOWNERS
  • Situer votre configuration dans SLSA Source et dans le score Scorecard
  • Ancrage socle : cette leçon produit la preuve de SOCLE-SRC-GEN-1 et des exigences de protection des branches et de la revue

Gouverner le code source, c'est décider qui peut changer quoi, et sous quelle validation. Sans cette décision, le reste de la chaîne protège un code dont personne ne garantit l'origine. L'OWASP Top 10 CI/CD place ce risque en tête, sous le nom CICD-SEC-1, « mécanismes de contrôle de flux insuffisants » : un attaquant qui obtient un droit d'écriture pousse son code jusqu'en production sans croiser aucun relecteur.

Sur GitHub comme sur GitLab, un dépôt fraîchement créé n'impose presque rien. Tout collaborateur disposant du droit d'écriture peut pousser directement sur main, fusionner une pull request sans approbation, modifier un workflow pour lire les secrets de la CI, ou déplacer un tag de version vers un autre commit. Et en cas d'incident, rien ne permet de reconstituer qui a validé quoi.

Un compte compromis ou un contributeur malveillant peut alors injecter une porte dérobée dans le code, modifier un workflow pour voler des jetons, altérer un Dockerfile pour piéger l'image, ou remplacer une dépendance par un paquet malveillant. Chacune de ces modifications part en production par le chemin normal, puisque rien ne l'arrête.

En mars 2025, l'action GitHub tj-actions/changed-files, utilisée par plus de 23 000 dépôts, a été compromise (CVE-2025-30066). L'attaquant disposait d'un jeton d'accès personnel volé à un bot qui avait les droits d'écriture sur le dépôt. Il n'a touché à aucune branche : il a réécrit les tags de version pour les faire pointer vers un commit malveillant, qui imprimait les secrets des pipelines dans leurs journaux de build.

La leçon est double. Côté éditeur, une protection de branche ne protège que les branches : les tags demandent leur propre règle, et un jeton de bot à large portée est une clé de toute la maison. Côté consommateur, seul l'épinglage par SHA de commit protégeait les projets, ce que traite la leçon Épingler les actions par SHA. Le risque de jetons trop larges est développé dans OWASP Top 10 CI/CD.

La gouvernance du code source repose sur quatre mécanismes complémentaires. Le tableau dit ce que chacun apporte et ce qui se passe quand il manque. Le premier est le fondement : sans lui, les autres restent des suggestions que n'importe qui peut ignorer.

MécanismeRôleSans lui
Protection de branche (ruleset)Interdit le push direct, exige une pull request validée et une CI verteN'importe quel compte modifie main
Protection des tagsInterdit de déplacer ou de supprimer un tag de versionUne version publiée change de contenu sans bruit
CODEOWNERSDésigne qui doit approuver chaque zone du codeN'importe quel collaborateur approuve
CONTRIBUTING.mdDocumente les règles pour les contributeursRègles floues, erreurs de bonne foi

Une protection de branche est le gardien automatique du dépôt : elle refuse toute modification directe de main et impose le passage par une pull request validée. Sur GitHub, la forme actuelle est le ruleset, qui remplace les règles classiques et se lit sans droit d'administration, ce qui compte pour les outils d'audit comme Scorecard.

Un ruleset n'est pas une case unique mais un ensemble de verrous activés séparément. Le tableau associe chaque règle à ce qu'elle empêche. Trois méritent votre attention avant les autres : la pull request obligatoire, l'approbation du dernier push, qui empêche d'ajouter un commit après la revue, et une liste de contournement vide, sans laquelle un administrateur passe outre toutes les règles.

Règle (interface GitHub)Ce qu'elle empêche
Require a pull request before mergingPush direct sur la branche protégée
Required approvalsFusion sans validation humaine
Dismiss stale pull request approvals when new commits are pushedApprobation conservée après un changement du code
Require approval of the most recent reviewable pushCommit ajouté après la revue, sans nouvel accord
Require review from Code OwnersFusion sans l'accord des responsables désignés
Require status checks to passFusion avec une CI en échec
Block force pushesRéécriture de l'historique de la branche
Restrict deletionsSuppression de la branche protégée

La configuration se fait dans les paramètres du dépôt, ou de l'organisation pour l'appliquer à plusieurs dépôts d'un coup. Les intitulés ci-dessous sont ceux de l'interface anglaise de GitHub, la seule disponible.

  1. Créer le ruleset

    Dans le dépôt, ouvrez Settings, puis Rules, Rulesets, et choisissez New ruleset, puis New branch ruleset.

  2. Nommer et cibler la règle

    • Ruleset name : Protéger main
    • Enforcement status : Active
    • Target branches : Add target, puis Include default branch

    Ajoutez si besoin d'autres cibles par motif, comme release/*.

  3. Activer les règles essentielles

    • Restrict deletions et Block force pushes
    • Require a pull request before merging, avec au moins 1 approbation (2 pour un dépôt sensible), Dismiss stale pull request approvals, Require review from Code Owners et Require approval of the most recent reviewable push
    • Require status checks to pass, avec vos jobs de CI (tests, lint, build)
  4. Vider la liste de contournement

    En haut de la page, la Bypass list doit rester vide. Un rôle qui y figure, administrateurs compris, fusionne sans respecter les règles, ce qui annule l'intérêt du ruleset.

  5. Enregistrer

    Cliquez Create. Le ruleset s'applique immédiatement.

GitLab n'a pas de ruleset : la protection se règle par branche, et les approbations se règlent à part. Les règles d'approbation et l'approbation par les code owners demandent l'édition Premium ou Ultimate ; sur l'édition gratuite, seule la restriction de push et de fusion est disponible.

  1. Protéger main

    Dans Settings, Repository, Protected branches : choisissez main, réglez Allowed to merge sur Maintainers, Allowed to push and merge sur No one, et laissez Allowed to force push désactivé.

  2. Exiger l'approbation des code owners

    Sur la même ligne, activez Code owner approval, si vous avez un fichier CODEOWNERS.

  3. Bloquer les fusions risquées

    Dans Settings, Merge requests, cochez Pipelines must succeed et All threads must be resolved.

  4. Fixer le nombre d'approbations

    Dans la section Merge request approvals, créez une règle avec au moins 1 approbation requise, et retirez à l'auteur le droit d'approuver sa propre merge request.

Une protection non testée est une hypothèse. Le test le plus simple consiste à tenter un push direct sur main depuis un clone à jour : il doit être refusé par le serveur, et non accepté puis signalé.

Fenêtre de terminal
git switch main
echo "# Test de protection" >> README.md
git commit -am "test: un push direct doit échouer"
git push origin main

Avec un ruleset, GitHub répond par une erreur GH013 qui liste la règle violée ; la protection classique répond par l'erreur GH006.

remote: error: GH013: Repository rule violations found for refs/heads/main.
remote: - Changes must be made through a pull request.

Si le push passe, relisez la cible du ruleset et sa liste de contournement. Annulez ensuite le commit local de test avec git reset --hard origin/main.

La protection de branche ne couvre pas les tags, et c'est exactement par là qu'est passée l'attaque tj-actions. Un tag de version comme v1.4.0 est un nom que les consommateurs croient figé : s'il peut être déplacé, une version publiée peut changer de contenu sans que personne ne le voie.

Sur GitHub, un ruleset de tags se crée au même endroit que celui des branches, avec New tag ruleset. Ciblez tous les tags de version, par exemple le motif v*, et activez Restrict updates et Restrict deletions : un tag publié ne peut alors plus être ni déplacé ni supprimé, sauf par un rôle inscrit dans la liste de contournement, qui doit rester vide.

GitHub propose en complément des releases immuables : une fois l'option activée dans les paramètres du dépôt, une release publiée, son tag et ses fichiers ne peuvent plus être modifiés. C'est ce réglage qui a protégé la version v0.35.0 de trivy-action lors de la compromission de mars 2026, détaillée dans la leçon Tests de sécurité. Sur GitLab, l'équivalent est le menu Settings, Repository, Protected tags, où Allowed to create se règle sur Maintainers.

Étape 3 : désigner les responsables avec CODEOWNERS

Section intitulée « Étape 3 : désigner les responsables avec CODEOWNERS »

Une protection qui exige « 1 approbation » ne dit pas par qui. Par défaut, n'importe quel collaborateur doté du droit d'écriture approuve, et un attaquant qui contrôle deux comptes, ou un complice, s'auto-valide. Le fichier CODEOWNERS règle ce point : il désigne les relecteurs obligatoires de chaque zone du code, et la forge les ajoute automatiquement à chaque pull request qui la touche.

À l'ouverture d'une pull request, la forge regarde les fichiers modifiés, cherche dans CODEOWNERS la dernière règle qui correspond à chacun, et ajoute les responsables trouvés comme relecteurs. Leur approbation ne devient obligatoire que si la règle « Require review from Code Owners » est active dans la protection de branche : sans elle, le fichier n'est qu'une suggestion.

Sur GitHub, le fichier se place dans .github/CODEOWNERS, à la racine ou dans docs/ ; sur GitLab, à la racine, dans docs/ ou dans .gitlab/. L'exemple ci-dessous commence par un propriétaire par défaut, puis protège les zones qui donnent accès aux secrets, à l'image ou aux dépendances.

.github/CODEOWNERS
# La DERNIÈRE règle qui correspond à un fichier l'emporte :
# règle générale d'abord, règles spécifiques ensuite.
# Propriétaire par défaut de tout ce qui suit
* @acme/maintainers
# Workflows CI/CD : ils lisent les secrets du dépôt
.github/workflows/ @acme/securite
.github/actions/ @acme/securite
.gitlab-ci.yml @acme/securite
# Construction et déploiement : un Dockerfile piégé piège l'image
Dockerfile @acme/ops
docker-compose*.yml @acme/ops
helm/ @acme/ops
terraform/ @acme/ops
# Dépendances : premier vecteur d'attaque de la chaîne
package.json @acme/securite
package-lock.json @acme/securite
requirements*.txt @acme/securite
go.mod @acme/securite
go.sum @acme/securite
# Le fichier CODEOWNERS lui-même
.github/CODEOWNERS @acme/securite

La dernière ligne compte plus qu'elle n'en a l'air : sans elle, un contributeur pourrait retirer les responsables d'une zone en modifiant CODEOWNERS dans la même pull request que son changement, et ne demander l'avis de personne.

Les motifs suivent la syntaxe de .gitignore, avec une différence de taille : l'ordre compte, puisque la dernière règle qui correspond l'emporte. Le tableau résume les formes utiles et leur portée.

MotifSignificationExemple
*Tous les fichiers (règle par défaut)* @acme/maintainers
*.pyTous les fichiers de cette extension*.py @acme/python
/src/Le dossier src/ à la racine uniquement/src/ @acme/core
tests/Tout dossier tests/ où qu'il soittests/ @acme/qa
@aliceUn compte utilisateurdocs/ @alice
@acme/securiteUne équipe de l'organisationgo.mod @acme/securite

GitHub signale les erreurs de syntaxe directement dans l'affichage du fichier CODEOWNERS, mais le seul vrai test est une pull request qui touche une zone protégée. Les relecteurs désignés doivent y apparaître automatiquement, et la fusion doit rester bloquée tant qu'ils n'ont pas approuvé.

Fenêtre de terminal
git switch -c test/codeowners
echo "# test" >> .github/workflows/ci.yml
git commit -am "test: vérifier CODEOWNERS"
git push origin test/codeowners

Ouvrez la pull request vers main : l'équipe @acme/securite doit figurer parmi les relecteurs requis, et le bouton de fusion afficher « Review required from code owners » tant que l'approbation manque.

Étape 4 : documenter les règles avec CONTRIBUTING.md

Section intitulée « Étape 4 : documenter les règles avec CONTRIBUTING.md »

CODEOWNERS dit qui approuve, CONTRIBUTING.md explique comment contribuer. Ce document évite les erreurs de bonne foi (branche mal nommée, pull request sans description), épargne aux relecteurs de répéter les mêmes règles, et sert de preuve d'audit que le processus était écrit avant l'incident. GitHub l'affiche automatiquement s'il se trouve à la racine, dans .github/ ou dans docs/.

Le modèle ci-dessous tient en une page, ce qui est voulu : un document de contribution trop long n'est pas lu. Il nomme les zones sensibles et leurs responsables, liste ce qui est interdit, et dit où signaler une faille.

CONTRIBUTING.md
# Contribuer
Toute modification passe par une pull request vers `main`.
## Processus
1. Créez une branche depuis `main` : `feature/…`, `fix/…`, `security/…`.
2. Ouvrez une pull request qui explique le pourquoi, pas seulement le quoi,
et lie l'issue concernée (`Fixes #123`).
3. Attendez une CI verte et au moins une approbation. Les zones sensibles
demandent l'approbation de leurs responsables (fichier CODEOWNERS).
## Zones sensibles
| Zone | Risque | Responsables |
|------|--------|--------------|
| `.github/workflows/` | Accès aux secrets CI/CD | @acme/securite |
| `Dockerfile`, `helm/` | Image ou déploiement piégé | @acme/ops |
| fichiers de dépendances | Attaque de la chaîne d'approvisionnement | @acme/securite |
## Interdit
- Pousser directement sur `main` ou forcer un push sur une branche protégée.
- Fusionner sans l'approbation des responsables requis ou avec une CI rouge.
- Désactiver un contrôle de CI pour faire passer une fusion.
## Signaler une vulnérabilité
N'ouvrez pas d'issue publique : utilisez l'onglet Security du dépôt
(« Report a vulnerability ») ou écrivez à securite@acme.example.
Réception accusée sous 48 heures.

Trois paliers se distinguent, du minimum que tout dépôt devrait appliquer à la configuration des dépôts sensibles. Chacun ferme une attaque de plus, et chacun correspond à des exigences du référentiel SOCLE, citées entre parenthèses pour servir de preuve.

Ce premier palier ferme le trou le plus large, le push direct sur main, et impose une revue et une CI verte avant toute fusion. Il ne demande aucune organisation d'équipe : quelques réglages du dépôt suffisent, et un projet personnel peut l'appliquer dès le premier jour.

  • Pull request obligatoire et au moins une approbation (SOCLE-SRC-GEN-1).
  • Checks de statut obligatoires (SOCLE-SRC-BRA-1).
  • Force push et suppression interdits sur main (SOCLE-SRC-SIG-1).

C'est le palier visé par la plupart des équipes. Il ajoute la brique qui empêche l'auto-approbation et verrouille le contournement par les administrateurs : à ce niveau, un compte compromis seul ne suffit plus à faire passer du code malveillant.

  • CODEOWNERS actif avec « Require review from Code Owners » (SOCLE-SRC-BRA-2).
  • Approbations rejetées à chaque nouveau push (SOCLE-SRC-BRA-3), approbation du dernier push exigée, liste de contournement vide.
  • Tags de version protégés contre la mise à jour et la suppression.

Ce dernier palier vise les projets dont la compromission coûterait cher. Il exige deux approbations de personnes différentes pour contrer la collusion, et des commits signés pour authentifier chaque auteur, avec Gitsign par exemple.

  • Revue à deux personnes (SOCLE-SRC-BRA-4).
  • Commits signés exigés par la protection de branche.
  • Journal d'audit des changements de protection, consulté régulièrement.

Ces paliers se mesurent. Le check Branch-Protection de OpenSSF Scorecard note la protection par paliers séquentiels : l'approbation du dernier push manquante bloque à elle seule le palier 6/10, quels que soient les réglages plus stricts déjà actifs.

La plupart de ces erreurs ont un point commun : une protection activée à moitié, qui donne un faux sentiment de sécurité. Le cas le plus fréquent est un fichier CODEOWNERS sans règle qui le rende obligatoire, donc ignorable. Lisez la colonne Conséquence avant la solution : elle dit ce qu'un attaquant peut faire tant que le réglage n'est pas corrigé.

ErreurConséquenceSolution
CODEOWNERS sans « Require review from Code Owners »Les responsables sont ajoutés mais pas obligatoiresActiver la règle dans le ruleset
Administrateurs dans la liste de contournementToutes les protections se contournentVider la Bypass list
Tags non protégésUne version publiée change de contenuRuleset de tags avec Restrict updates et Restrict deletions
Règle * placée en dernierElle écrase toutes les règles spécifiquesRègle par défaut en premier
CODEOWNERS sans propriétaire pour lui-mêmeUn contributeur retire les responsables dans sa propre PRAjouter .github/CODEOWNERS à une équipe de sécurité
Équipe citée inexistante ou sans droit d'écritureLigne ignorée, revue non requiseVérifier l'équipe et ses droits
Jeton de bot à droits d'écriture largesUn vol de jeton suffit à tout modifierJetons à portée minimale, expirants, audités

Où se situe cette configuration dans SLSA Source ?

Section intitulée « Où se situe cette configuration dans SLSA Source ? »

SLSA définit, depuis sa version 1.2, une piste Source en quatre niveaux. Le tableau les résume avec leurs exigences ; la colonne de droite dit ce que la configuration de cette leçon apporte à chacun.

NiveauExigence SLSACe que cette leçon apporte
L1 Version contrôléeUn système de gestion de versionsAcquis avec Git
L2 Historique et provenanceHistorique continu, immuable et conservé ; attestations de provenance émises par la forgeHistorique protégé (force push et suppression interdits), mais pas d'attestation de provenance
L3 Contrôles techniques continusLa forge applique les contrôles de l'organisation sur les références nomméesRulesets de branches et de tags, checks obligatoires
L4 Revue à deux personnesDeux personnes de confiance relisent tout changement des branches protégéesPalier renforcé, avec deux approbations

La nuance compte : la configuration décrite ici est alignée sur les contrôles des niveaux 3 et 4, mais elle ne produit pas les attestations de provenance de source qu'exige déjà le niveau 2. Elle ne suffit donc pas, à elle seule, à revendiquer un niveau SLSA Source.

  • La protection de branche est le fondement : sans elle, CODEOWNERS et CONTRIBUTING.md ne sont que des suggestions.
  • Sur GitHub, préférez le ruleset à la protection classique, et gardez sa liste de contournement vide.
  • L'approbation du dernier push empêche d'ajouter un commit après la revue ; sans elle, une revue ne vaut que pour ce qui a été relu.
  • Les tags de version demandent leur propre ruleset : l'attaque tj-actions les a réécrits sans toucher à une seule branche.
  • CODEOWNERS désigne les relecteurs obligatoires des zones sensibles, et doit se protéger lui-même.
  • Cette configuration s'aligne sur les contrôles de SLSA Source L3 et L4, sans produire les attestations de provenance exigées dès L2.
  • Preuve attendue par SOCLE-SRC-GEN-1 : la configuration de protection de branche, revue obligatoire et push direct interdit. Une capture ou un export du ruleset la constitue.

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

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

8 questions
8 min.
70% requis

Informations

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

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

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

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

Suivez aussi le projet sur LinkedIn