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

Plumber : sécuriser vos pipelines GitLab CI et GitHub Actions

Par mis à jour le

Améliorer cette page

Si votre pipeline GitLab CI/CD utilise des images Docker non épinglées, des includes sur main ou un build en Docker-in-Docker, ou si vos workflows GitHub Actions référencent des actions tierces par tag mutable, vous exposez votre chaîne à une attaque de supply chain. Plumber est un scanner de sécurité de pipelines : il lit votre configuration CI, la confronte à une série de contrôles, puis rend un score de 0 à 100 (lettre A à E) et des chemins d'attaque à corriger. Ce guide, pour débutants et intermédiaires, part de la CLI, passe au composant GitLab et à l'action GitHub, puis traite le PBOM, les rapports, le score et Plumber Radar.

  • Installer Plumber en vérifiant son empreinte, puis lancer un premier scan avec un jeton GitLab ou GitHub
  • Lire un chemin d'attaque et le bloc Plumber Score d'un scan réel, puis comprendre comment le score se calcule et détailler un code avec plumber explain
  • Corriger les failles fréquentes : image non épinglée, action non épinglée par SHA, source non autorisée, branche non protégée
  • Configurer les contrôles avec une surcouche (extends: plumber:default) et la valider
  • Intégrer Plumber à la CI avec le composant GitLab ou l'action GitHub, tous deux épinglés
  • Régler le seuil du job (--min-points, --min-score) et interpréter les codes de sortie 0, 1, 2 et 3
  • Exporter un PBOM, un CycloneDX, un rapport SARIF ou GitLab SAST
  • Vérifier l'hygiène CI/CD d'un fournisseur avec Plumber Radar

Plumber est un scanner de sécurité pour GitLab CI/CD et GitHub Actions. Côté GitLab, il interroge l'API du projet et lit la configuration mergée ; côté GitHub, il analyse les fichiers de .github/workflows avec le moteur de règles Rego, puis complète ses constats par l'API GitHub : protection de branche, avis de sécurité des actions, dépôts archivés, états des workflows. Il signale les écarts les plus dangereux : images non fiables, tags mutables, actions non épinglées par SHA, sources non autorisées, branches non protégées, scripts téléchargés sans vérification, Docker-in-Docker.

Quelques termes reviennent tout au long du guide. Une attaque de supply chain passe par une dépendance que vous ne maîtrisez pas (une action, une image, un template) plutôt que par votre propre code. Épingler une dépendance, c'est la figer sur un identifiant qui ne change jamais : le SHA du commit pour une action, le digest (sha256:…) pour une image de conteneur. Un tag comme v4 ou 3.12 reste mutable, car son propriétaire peut le déplacer vers un autre contenu. Le provider désigne la plateforme analysée, GitLab ou GitHub. Le Docker-in-Docker (DinD) est un job qui démarre son propre démon Docker dans un conteneur privilégié.

Chaque problème porte un code d'issue (ISSUE-XXX) que vous détaillez avec plumber explain. Le détail de ce qui est regroupé en chemin d'attaque et de ce qui reste un finding individuel se lit plus bas, dans la section sur le score.

Plumber est un logiciel open source, distribué sous licence MPL 2.0 (fichier LICENSE du dépôt getplumber/plumber). La documentation officielle (getplumber.io/docs/cli) complète ce guide pour les options rares.

Plumber n'est pas seul : zizmor et poutine analysent en profondeur les workflows GitHub, alors que Plumber couvre deux plateformes sous une même configuration et rend un score unique. Le lab Pipeline CI durci les fait passer ensemble à zéro finding.

Plumber s'utilise de quatre façons, et le choix change surtout l'ordre d'apprentissage. La CLI montre la sortie brute et permet d'itérer en quelques secondes. Le composant GitLab et l'action GitHub évitent d'écrire un job complet, mais masquent une partie du fonctionnement. La Platform, le service de pilotage multi-projets de Plumber (présenté dans la section « Quand passer à la Platform ? »), répond à un besoin de gouvernance, pas de découverte. Repérez votre situation dans la colonne de gauche.

Votre situationPorte d'entrée recommandéePourquoi
Vous découvrez PlumberCLI localeVous comprenez ce que l'outil détecte et comment lire la sortie
Vous avez un pipeline GitLabComposant GitLab CI/CDUn include suffit, les exports sont déjà branchés
Vous avez des workflows GitHubAction GitHubUn step, avec un rapport SARIF (format d'échange standard) dans l'onglet Security
Vous devez centraliser plusieurs projetsPlatformVous passez de l'analyse projet par projet à la gouvernance

Avant de lancer votre premier scan, vous avez besoin de :

  • un dépôt Git lié à un projet GitLab ou GitHub ;
  • côté GitLab : un jeton avec les scopes read_api et read_repository, avec le rôle Maintainer sur le projet (sinon la protection de branche reste inaccessible) ;
  • côté GitHub : un jeton exporté dans GH_TOKEN ou GITHUB_TOKEN (Plumber lit GH_TOKEN en premier), ou une session gh auth login, avec en plus Administration:read pour lire la protection de branche et Actions:read pour lire l'état des workflows (le paragraphe « Quel jeton GitHub créer ? » plus bas détaille le type de jeton) ;
  • un terminal interactif si vous voulez utiliser plumber config init.

Plumber est distribué comme binaire unique, sans dépendance. Les trois premières méthodes posent l'exécutable sur votre poste ; la variante Docker évite toute installation et convient aux runners de CI. Quel que soit le canal, épinglez la version : un résultat d'analyse ne se compare que d'une exécution à l'autre sur la même version.

Fenêtre de terminal
mise use -g github:getplumber/plumber@0.7.2

Vérification (mise, Homebrew ou binaire ; avec Docker, lancez docker run --rm <image@digest> version) :

Fenêtre de terminal
plumber version

La sortie doit afficher trois lignes : plumber version 0.7.2, le hash de commit (620b4de274459229ad4f35041ca8bb48d8241d13) et la date de build (2026-10-17T04:18:53Z). L'option --version n'existe pas : c'est la sous-commande version.

Plumber vérifie aussi, à la fin d'une analyse, si une version plus récente existe, et affiche la commande de mise à jour de votre canal d'installation. Désactivez cet avis avec PLUMBER_NO_UPDATE_CHECK=1.

Le chemin est toujours le même : s'authentifier, scanner, lire, corriger. Plumber lit votre remote origin pour détecter le provider : une URL GitLab bascule sur l'API GitLab, une URL GitHub sur l'analyse des workflows locaux complétée par l'API GitHub. Aucun fichier .plumber.yaml n'est nécessaire pour démarrer : sans fichier, Plumber affiche No .plumber.yaml found; using Plumber's built-in default configuration. et applique sa configuration par défaut embarquée.

  1. Authentifiez-vous auprès de votre provider

    Côté GitLab, créez un jeton via User Settings -> Access Tokens avec read_api et read_repository, puis exportez-le :

    Fenêtre de terminal
    export GITLAB_TOKEN=<votre-jeton>

    Côté GitHub, exportez un jeton (voir le paragraphe suivant pour son type et ses droits) ou ouvrez une session avec la CLI gh :

    Fenêtre de terminal
    export GH_TOKEN=<votre-jeton>
    # ou : gh auth login

    Quel jeton GitHub créer ? Un jeton fine-grained (à granularité fine), parce qu'il porte le droit Administration:read, nécessaire à la lecture de la protection de branche, sans rien ouvrir d'autre. Un jeton classique de scope repo la lit aussi, mais il donne l'écriture sur tous vos dépôts : à éviter. Créez le jeton fine-grained dans Settings > Developer settings > Personal access tokens > Fine-grained tokens, en lui donnant Administration:read et Actions:read. Ce droit ne s'obtient que sur un dépôt que vous administrez : sur le dépôt d'un tiers, le contrôle de protection de branche reste partiel. Dans l'action GitHub, le GITHUB_TOKEN du workflow ne porte pas ce droit : branchMustBeProtected y reste alors partiel (voir l'entrée github-token de l'action). Plumber lit GH_TOKEN, puis GITHUB_TOKEN, puis la session gh.

  2. Lancez l'analyse

    Fenêtre de terminal
    cd mon-projet
    plumber analyze --score-point

    Plumber détecte le provider, l'URL et le chemin du projet depuis origin. L'option --score-point ajoute le décompte des points sous le score, que la CLI affiche de toute façon.

  3. Lisez d'abord le bloc Score, puis remontez vers le détail

    Cherchez en priorité la lettre et les points sur 100 avec la ligne de statut ; les chemins d'attaque (Attack paths), qui coûtent le plus cher ; les findings individuels (Individual findings) avec leurs codes ISSUE-XXX ; le job ou le workflow concerné.

Oui : analyze accepte une cible positionnelle, qui évite d'être dans le dépôt et de dépendre de son remote. Les formes reconnues sont owner/repo (GitHub par défaut), github.com/owner/repo, https://gitlab.com/groupe/projet, git@hote:chemin.git, avec un suffixe /tree/<branche> pour viser une branche. Un hôte autre que github.com et gitlab.com est traité comme GitLab, sauf si vous ajoutez --provider github.

Fenêtre de terminal
plumber analyze nektos/act --score-point
plumber analyze https://gitlab.com/Bob74/pipeline-craft --branch solution/lab-12 --score-point

Si le dépôt local n'a pas de remote origin, ou s'il pointe ailleurs que vers GitLab ou GitHub, passez les paramètres à la main : --gitlab-url https://gitlab.example.com --project mon-groupe/mon-projet pour un GitLab auto-hébergé, --github-url https://github.example.com --project mon-org/mon-projet pour GitHub Enterprise Server (laissez --github-url vide pour api.github.com).

Le code de sortie est ce que votre CI lit pour faire échouer le job. Plumber en utilise quatre, et le dernier est celui qu'on rencontre sans l'attendre.

CodeSensExemple
0Analyse réussie, seuil atteintScore supérieur ou égal à --min-points
1Seuil non atteint (gate)Status: FAILED (gate blocks at 100 pts)
2Erreur d'exécutionJeton manquant, configuration invalide, réseau
3Analyse incomplèteCollecte de données partielle : score retenu

Le code 3 prime sur le seuil : une collecte dégradée sort toujours en 3, quel que soit le score. Avec --fail-warnings, une clé de configuration inconnue sort en 2 et un contrôle « could not verify » en 3.

Le dépôt de lab Bob74/pipeline-craft, branche solution/lab-12, a été scanné le 17 octobre 2026 avec la 0.7.2 et un jeton GitLab. Les images non épinglées par digest sont regroupées en chemins d'attaque, un par image : chacun dit ce qu'un attaquant atteindrait si cette image était compromise. Voici le chemin le plus grave et le bloc Score, sans le logo ASCII ni les lignes de séparation. Les chemins 2 et 3 sont omis.

MED Attack path 1
Entry docker.io/docker:27 (mutable image tag)
│
└──▶ runs in job `docker-build`
└─▶ reaches secrets Plumber could not list and a step that publishes
So if this image is compromised, an attacker can read the secrets Plumber could not list
and ship a malicious release
Note Plumber could not list the secrets
Note Capped at Medium: it needs a dependency compromise first. Plumber could not read:
secrets Plumber could not list
Fix pin the image by digest
↳ details: run `plumber explain -a 1`
Score
Plumber Score 88 / 100
Status: FAILED (gate blocks at 100 pts)
100 - 12 (attack paths) - 0 (individual findings)
Worst case: if this image is compromised, an attacker can read the secrets Plumber could not
list and ship a malicious release (attack path 1: docker.io/docker:27)
Best fix: pin the image by digest (image docker.io/alpine:3.20), +3 pts, 91 / 100 (A)
medium paths x1 -6
low paths x2 -6

Le chemin 1 est Medium : il atteint « a step that publishes », une étape qui publie, ce qui ferait un High, mais une image sur un tag mutable ne devient dangereuse qu'après la compromission de son éditeur, et Plumber le plafonne à Medium (ligne Note). Les deux autres chemins, celui de python:3.12-slim et celui de alpine:3.20, sont Low, parce que Plumber n'a pas pu lister les secrets des jobs concernés : un fait non prouvé ne monte pas un chemin. Total : 6 + 3 + 3 = 12 points perdus, 88 points, B. Le job échoue tout de même, puisque le seuil par défaut est de 100 points.

Le dépôt nektos/act (commit 4f41128) a été scanné le 17 octobre 2026 avec la 0.7.2, un jeton GitHub et la configuration par défaut. Il illustre un cas où les findings individuels pèsent lourd et où un seul chemin High apparaît. Les sorties ci-dessous sont abrégées : les lignes ↳ details, Worst case et (action …) de la sortie réelle sont omises. Le chemin est affiché avec son entrée, le job concerné et ce qu'il atteint :

HIGH Attack path 1
Entry cargo-bins/cargo-binstall@main via `vedantmgoyal2009/winget-releaser@4ffc7888bffd`
(untrusted and mutable external action)
│
└──▶ runs in job `release` from workflow `release.yml`
└─▶ reaches 3 secrets, a token with push access and write access to actions
and a step that publishes
So if this action is compromised or malicious,
an attacker can read 3 secrets of the repository, push in your repository
and ship a malicious release
Note the job runs in environment `release`
Note Capped at High: the source is not authorized, so a compromise is more likely
Fix pin the outermost action to a commit SHA whose definition pins this reference, or
vendor the chain into `./.github/actions/` and pin it there

L'action winget-releaser, épinglée par SHA, embarque elle-même une action composite qui référence cargo-binstall@main : Plumber résout les actions composites et les workflows réutilisables, y compris ceux d'autres dépôts, et affiche la chaîne avec le mot via. L'entrée est mutable, le propriétaire est hors des sources autorisées, donc le chemin est plafonné à High et non à Medium. Le bloc Score qui en résulte :

Score
Plumber Score 55 / 100
Status: FAILED (gate blocks at 100 pts)
100 - 15 (attack paths) - 30 (individual findings)
individual findings count for 30 at most
Best fix: pin the outermost action to a commit SHA whose definition pins this reference,
or vendor the chain into `./.github/actions/` and pin it there, +15 pts, 70 / 100 (C)
ISSUE-307 low x6 -0
ISSUE-713 medium x6 -15
ISSUE-801 medium x6 -15
high paths x1 -15

La lecture tient en trois points. Le chemin High coûte 15 points. Les six ISSUE-713 (action venue d'une source non autorisée, mais épinglée par SHA, donc Medium) et les six ISSUE-801 (pas de permissions: explicite) sont Medium (5 points chacun, soit 60 en théorie) : le plafond des findings individuels les ramène à 30 points, que le tableau répartit à 15 par code. Les six ISSUE-307 (credentials persistés par actions/checkout) sont Low et ne retirent rien. Résultat : 100 - 15 - 30 = 55 points, C, et le meilleur correctif, épingler l'action la plus externe, rapporterait 15 points.

Plumber documente 76 codes d'issues organisés en neuf familles (voir l'annexe en bas de guide). Sur un projet jamais audité, commencez par quatre erreurs simples à corriger et présentes quel que soit le provider. Les extraits YAML qui suivent sont des fragments de .plumber.yaml : la section sur la surcouche montre comment les assembler sans perdre les autres contrôles.

Dans le lab pipeline-craft, ces findings sont les entrées des chemins d'attaque vus plus haut. Le problème n'est pas seulement l'usage de latest : un tag de version comme python:3.12-slim reste mutable. Si le registre republie l'image, votre pipeline peut exécuter autre chose demain. Le code est ISSUE-103 (« Container image is not pinned by digest »).

Pour activer le mode strict, la clé se trouve dans le contrôle containerImageMustNotUseForbiddenTags, sous la section du provider :

.plumber.yaml
version: "2.0"
gitlab:
controls:
containerImageMustNotUseForbiddenTags:
enabled: true
containerImagesMustBePinnedByDigest: true

Le contrôle accepte aussi une liste exemptImages : une image n'est exemptée que si son registre et son nom correspondent et si son tag figure dans tags (le digest est ignoré, un autre tag reste contrôlé). Les images exemptées sont listées sous « Exempted by configuration » dans la sortie.

2. Actions tierces non épinglées par SHA (provider GitHub)

Section intitulée « 2. Actions tierces non épinglées par SHA (provider GitHub) »

C'est la première correction à faire côté provider GitHub, sous le code ISSUE-701 (« Third-party action reference is not pinned by commit SHA »). Un step dont le uses: pointe sur owner/action@v4 ou owner/action@main reste mutable côté mainteneur. Si l'action est compromise, comme tj-actions/changed-files en mars 2025 (CVE-2025-30066), votre workflow exécute silencieusement le code modifié avec ses secrets. Corrigez en épinglant sur le SHA du commit, tag en commentaire :

.github/workflows/ci.yml
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Activez le contrôle dans la section GitHub. L'option qui exempte les actions de premier rang est trustedOwners : elle limite le signal initial à la surface tierce, celle que GitHub ne maintient pas. Le guide Épingler les actions par SHA détaille la méthode et sa maintenance, et la fiche de l'incident tj-actions/changed-files raconte le scénario réel.

.plumber.yaml
github:
controls:
actionsMustBePinnedByCommitSha:
enabled: true
trustedOwners:
- actions
- github
- dependabot

3. Includes sur main, master ou HEAD (provider GitLab)

Section intitulée « 3. Includes sur main, master ou HEAD (provider GitLab) »

Un include pointant sur une branche mutable rend votre pipeline difficile à reproduire et peut casser sans changement dans votre dépôt.

Avant
include:
- project: security/templates
file: /pipelines/sast.yml
ref: main
Après
include:
- project: security/templates
file: /pipelines/sast.yml
ref: v2.4.1

Le contrôle associé est includesMustNotUseForbiddenVersions avec ISSUE-404. Un tag vaut mieux qu'une branche, mais il peut encore être redéplacé : pour une garantie complète, épinglez le SHA du commit en gardant le tag en commentaire, comme le montre Durcir vos pipelines.

Si la branche par défaut n'est pas protégée, un push direct ou une modification non revue peut contourner vos pratiques de revue. Le contrôle existe sous les deux providers et renvoie ISSUE-501 (branche non protégée, Critical sur la branche par défaut, High ailleurs) ou ISSUE-505 (protection non conforme). Côté GitHub, il lit la protection de branche et les rulesets ; sans le droit Administration:read, il s'abstient et le dit dans le champ partialControls du JSON.

Exemple minimal
gitlab:
controls:
branchMustBeProtected:
enabled: true
defaultMustBeProtected: true
namePatterns:
- main
allowForcePush: false

Une cinquième erreur très courante : Docker-in-Docker

Section intitulée « Une cinquième erreur très courante : Docker-in-Docker »

Plumber détecte aussi les jobs qui utilisent le service docker:dind, sous ISSUE-412 (« Docker-in-Docker service detected »). Sur des runners partagés, DinD exige le mode privileged, qui augmente le risque d'évasion de conteneur et de mouvement latéral. Pour construire des images sans lui, deux outils sans démon existent, avec des exigences différentes sur un runner à executor Docker. kaniko, tiré de son fork maintenu osscontainertools/kaniko (le dépôt de Google est archivé depuis juin 2025), construit avec les profils de sécurité par défaut. Buildah échoue avec ces profils et demande des filtres seccomp et AppArmor assouplis. Le guide Registries GitLab donne les deux jobs et leurs réglages. Le contrôle existe aussi côté GitHub avec ISSUE-413 (daemon non sécurisé).

Quelles protections supply chain activer ensuite ?

Section intitulée « Quelles protections supply chain activer ensuite ? »

Une fois les quatre erreurs de base corrigées, Plumber offre une famille de contrôles 7XX dédiée aux actions tierces, un vecteur d'attaque de supply chain que des incidents réels ont démontré (tj-actions/changed-files en mars 2025). Ces contrôles vont plus loin que le simple pinning et méritent d'être activés dès que votre pipeline est stable.

Restreindre les sources d'actions autorisées (ISSUE-713)

Section intitulée « Restreindre les sources d'actions autorisées (ISSUE-713) »

Épingler une action par SHA garantit qu'elle ne change pas, mais pas qu'elle vienne d'un auteur de confiance. Le contrôle githubActionMustComeFromAuthorizedSources signale toute action dont le propriétaire n'est ni GitHub, ni votre organisation, ni sur votre liste d'autorisation, ni (si vous fixez minimumStars) au-dessus du seuil d'étoiles. C'est la parade directe contre le squat de dépôt ou une action inconnue qui hériterait de vos secrets. C'est ici que s'emploie trustGithubOfficialActions, et pas dans le contrôle d'épinglage.

.plumber.yaml
github:
controls:
githubActionMustComeFromAuthorizedSources:
enabled: true
trustGithubOfficialActions: true
trustSameOrgActions: true
# Seuil d'étoiles minimal (0 = désactivé, nécessite l'API GitHub)
minimumStars: 0
trustedGithubActions:
- docker/setup-buildx-action
- docker/login-action
- ossf/scorecard-action

Bloquer les actions portant un CVE connu (ISSUE-703)

Section intitulée « Bloquer les actions portant un CVE connu (ISSUE-703) »

Le contrôle actionsMustNotCarryKnownCVEs croise chaque action épinglée avec les avis de sécurité publiés. C'est l'un des rares cas où une dépendance peut ouvrir un chemin Critical : un avis publié sur la version épinglée suffit à lever le plafond. Le contrôle complémentaire actionsMustNotBeArchived (ISSUE-702) signale les actions dont le dépôt amont est archivé. Les deux interrogent l'API GitHub : sans jeton, ils sont ignorés et l'analyse est incomplète.

.plumber.yaml
github:
controls:
actionsMustNotCarryKnownCVEs:
enabled: true
actionsMustNotBeArchived:
enabled: true

Le contrôle pullRequestTargetMustNotCheckoutHead détecte un workflow déclenché par pull_request_target qui fait un checkout de la HEAD de la pull request. Le code non fusionné d'un contributeur externe s'exécute alors avec les secrets du dépôt. Le guide Sécuriser pull_request_target détaille les patterns sûrs.

.plumber.yaml
github:
controls:
pullRequestTargetMustNotCheckoutHead:
enabled: true

Ne jamais laisser un déclencheur peu fiable écrire le cache (ISSUE-705)

Section intitulée « Ne jamais laisser un déclencheur peu fiable écrire le cache (ISSUE-705) »

Le contrôle untrustedTriggersMustNotWriteCache (« Untrusted trigger granted cache write ») signale un job dont le cache-mode vaut write ou write-only sous un déclencheur peu fiable : pull_request_target, issue_comment ou workflow_run. Un cache écrit par un tel job peut être restauré ensuite par un job privilégié. Le remède est cache-mode: read, que l'on pose au niveau du workflow ou sous un job (fragment ci-dessous). Le contrôle accepte deux listes d'exemptions : exemptWorkflows (motif sur le nom de fichier) et exemptJobs (motif <workflow>/<job id>).

.github/workflows/ci.yml
on: pull_request_target
cache-mode: read # niveau workflow ; la clé peut aussi se poser sous un job
jobs:
test:
runs-on: ubuntu-24.04

Le fragment ne montre que l'emplacement de la clé : un workflow complet porte aussi ses permissions: et ses étapes.

Le schéma v2.0 organise les contrôles par provider : tout ce qui concerne GitLab CI/CD vit sous gitlab.controls:, tout ce qui concerne GitHub Actions sous github.controls:. La clé version: "2.0" est obligatoire. Le moteur Rego est le seul moteur d'évaluation, il n'y a pas de bloc engine:. La référence des contrôles liste toutes les clés.

Il existe deux façons d'écrire .plumber.yaml, et elles ne se comportent pas pareil. Une surcouche (overlay) commence par extends: plumber:default et ne liste que ce que vous changez ; tout bloc absent hérite de la configuration de base et les contrôles ajoutés à la base s'activent d'eux-mêmes. Une configuration complète, sans extends, décrit tout : un bloc absent n'est pas exécuté. La surcouche est l'usage normal.

Surcouche (extends: plumber:default)Configuration complète
Bloc absentHérite de la baseContrôle non exécuté
Maps (clés)Fusionnées avec la baseTelles qu'écrites
ListesRemplacent celles de la baseTelles qu'écrites
includePlumberDefaultsActif (garde les listes de confiance de la base)Sans effet

Le piège tient à la ligne des listes : écrire trustedOwners: [actions] dans une surcouche remplace la liste de base au lieu de l'allonger. Générez le point de départ avec --overlay :

Fenêtre de terminal
plumber config generate --overlay --output .plumber.yaml

Le fichier produit contient la clé extends: plumber:default, la version "2.0" et un exemple d'override commenté. Voici une surcouche complète qui active le mode strict sur les digests et exempte une image d'infrastructure :

.plumber.yaml
extends: plumber:default
version: "2.0"
gitlab:
controls:
containerImageMustNotUseForbiddenTags:
containerImagesMustBePinnedByDigest: true
github:
controls:
githubActionMustComeFromAuthorizedSources:
includePlumberDefaults: true
trustedGithubActions:
- docker/setup-buildx-action

Ces commandes se lisent en trois groupes : validate et view inspectent ce que Plumber comprend de votre fichier ; resolve, slim et diff le confrontent à la base ; generate et init en créent un. Notez le --force de generate : sans lui, la commande refuse d'écraser un .plumber.yaml existant.

Fenêtre de terminal
plumber config validate # fautes de frappe, clés inconnues, schéma
plumber config view # configuration lue, sans commentaires
plumber config resolve # configuration EFFECTIVE, après fusion avec la base
plumber config slim # réduit une configuration complète en surcouche minimale
plumber config diff # écarts avec le défaut embarqué
plumber config generate --output .plumber.yaml --force # template complet commenté
plumber config init # assistant interactif (terminal requis)

config validate signale les fautes de frappe avec une suggestion (Unknown control in gitlab.controls: "branchMustBeProtectd". Did you mean "branchMustBeProtected"?) ; avec --fail-warnings, un avertissement sort en code 2. config resolve est la commande à lancer en cas de doute : elle imprime ce que Plumber exécute vraiment, sans fichier ou après héritage. config slim fait le chemin inverse d'une configuration complète vers une surcouche (les commentaires sont perdus).

Si votre .plumber.yaml est écrit au schéma v1, avec un controls: au top-level, config validate répond legacy config schema detected et vous invite à migrer. plumber config migrate réécrit le fichier au schéma v2.0 :

Fenêtre de terminal
# Crée .plumber.yaml.v2 à côté du fichier original
plumber config migrate
# Ou remplace en place (le fichier original part en .plumber.yaml.bak)
plumber config migrate --in-place

La migration encapsule le bloc controls: sous gitlab.controls:, passe version: "1.0" à "2.0" et supprime le bloc engine:, absent du schéma v2.0. Elle est idempotente : sur un fichier déjà en v2.0, elle répond Nothing to do. Elle renomme aussi la clé de contrôle releaseWorkflowsMustNotRestoreUntrustedCache en untrustedTriggersMustNotWriteCache.

Comment ajouter Plumber à GitLab CI avec le composant officiel ?

Section intitulée « Comment ajouter Plumber à GitLab CI avec le composant officiel ? »

Le composant CI/CD officiel est le bon point d'entrée côté GitLab (voir sa documentation). Il est publié dans le catalogue sous gitlab.com/getplumber/plumber/plumber et se référence par le tag Git du dépôt, @v0.7.2 : la forme @0.7.2 est refusée à la résolution (« content not found »). Pour l'option la plus stricte, remplacez le tag par le SHA du commit du tag, avec la version en commentaire :

.gitlab-ci.yml
include:
- component: gitlab.com/getplumber/plumber/plumber@d364d54c7c045571d129252e4fb0cf2926c029fa # v0.7.2

Ajoutez d'abord le jeton GITLAB_TOKEN dans Settings -> CI/CD -> Variables, en le masquant et en limitant sa portée à l'environnement plumber : le job du composant se rattache à cet environnement, et le jeton reste invisible des autres jobs du projet. Utilisez ensuite ce squelette :

.gitlab-ci.yml
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
- if: $CI_COMMIT_TAG
include:
- component: gitlab.com/getplumber/plumber/plumber@v0.7.2

Ce bloc workflow:rules évite de créer à la fois un pipeline de branche et un pipeline de merge request sur le même push. Le job s'exécute sur les merge requests, la branche par défaut et les tags, et utilise par défaut l'image du composant, épinglée par digest (getplumber/plumber@sha256:14698bde…).

Le bloc inputs: surcharge les valeurs par défaut. Deux réglages comptent d'abord : stage, qui sort le job du .pre, et min_points (ou min_score), le seuil du Plumber Score en dessous duquel le job échoue (le calcul du score est détaillé plus bas). Trois groupes d'entrées complètent l'ensemble : la cible (project_path, branch, server_url, ci_config_path, remplies depuis les variables CI), la configuration (config_file, controls, skip_controls, fail_warnings), et les fichiers de sortie.

.gitlab-ci.yml
include:
- component: gitlab.com/getplumber/plumber/plumber@v0.7.2
inputs:
stage: test
min_points: 80 # échoue sous 80 points sur 100
config_file: .plumber.yaml
score_point: true # décompte des points dans le journal
environment_name: plumber # environnement auquel le jeton est restreint

Les exports sont actifs par défaut : output_file (plumber-report.json), pbom_file (plumber-pbom.json), pbom_cyclonedx_file (plumber-cyclonedx-sbom.json) et glsast_file (gl-sast-report.json), ce dernier étant déjà téléversé comme rapport sast du Security Dashboard. Pour désactiver un export, passez-lui une chaîne vide. D'autres entrées existent : allow_failure, verbose, mr_comment, badge, score_push, score_endpoint, platform et platform_allow_http. L'entrée score est un no-op déprécié : utilisez score_point. Le composant n'a aucune entrée pour le profil de score (voir plus bas).

Dans quel ordre le composant cherche-t-il la configuration ?

Section intitulée « Dans quel ordre le composant cherche-t-il la configuration ? »

Le composant recherche la configuration dans cet ordre : l'input config_file s'il est défini, puis le fichier .plumber.yaml à la racine du dépôt, puis la configuration par défaut embarquée dans l'image.

Gardez cette variante pour les besoins spécifiques. L'image a pour ENTRYPOINT /plumber : avec un script:, il faut donc neutraliser l'entrypoint (entrypoint: [""]), sans quoi GitLab lancerait /plumber avec votre script comme argument.

.gitlab-ci.yml
plumber-scan:
stage: test
image:
name: getplumber/plumber:0.7.2@sha256:14698bde5dbc40fe2b5b9d339b039084b18f3b195e2fba0ceef9ff05ec69654c
entrypoint: [""]
script:
- /plumber analyze --gitlab-url "$CI_SERVER_URL" --project "$CI_PROJECT_PATH" --branch "$CI_COMMIT_REF_NAME" --glsast gl-sast-report.json --score-point
artifacts:
when: always
reports:
sast: gl-sast-report.json

Si vous voulez ajouter --mr-comment et --badge, séparez-les en deux jobs : le commentaire de merge request n'a de sens qu'en pipeline de MR, et le badge ne se met à jour que sur la branche par défaut.

Comment utiliser le composant sur un GitLab auto-hébergé ?

Section intitulée « Comment utiliser le composant sur un GitLab auto-hébergé ? »

Un GitLab auto-hébergé ne peut pas consommer directement le composant de gitlab.com. La documentation de l'éditeur propose deux méthodes : importer le dépôt https://gitlab.com/getplumber/plumber.git, ou le forker et le synchroniser par miroir (méthode recommandée, car les mises à jour suivent). Dans les deux cas, activez le CI/CD Catalog dans le projet, publiez une release sur un tag existant, puis consommez le composant local :

.gitlab-ci.yml
include:
- component: gitlab.example.com/infrastructure/plumber/plumber@v0.7.2

Pour une garantie complète, épinglez le SHA du commit du miroir (celui du tag v0.7.2 sur gitlab.com est d364d54c7c045571d129252e4fb0cf2926c029fa) avec la version en commentaire. Les SHA du miroir GitLab et du dépôt GitHub diffèrent.

Comment ajouter Plumber à GitHub Actions avec l'action officielle ?

Section intitulée « Comment ajouter Plumber à GitHub Actions avec l'action officielle ? »

L'action GitHub getplumber/plumber (voir sa documentation) est une action composite : elle télécharge le binaire de la version demandée, vérifie sa somme de contrôle et son attestation de provenance (SLSA), lance l'analyse, téléverse le rapport SARIF dans l'onglet Security et publie les rapports en artefact. Comme tout uses:, elle s'épingle sur le SHA du commit de la release, tag en commentaire.

.github/workflows/plumber.yml
name: plumber
on:
push:
branches: [main]
pull_request:
permissions: {}
jobs:
plumber:
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: getplumber/plumber@ccd274c2abc9f66d7e44051d612d49c8ed1747ac # v0.7.2
with:
min-score: B

Les permissions minimales sont contents: read (le checkout), actions: read (lire les états de workflow et un journal de run pour prouver la portée réelle du GITHUB_TOKEN) et security-events: write (téléversement SARIF). Ajoutez id-token: write seulement si vous publiez le score avec score-push. Sur un dépôt privé, le téléversement SARIF exige GitHub Advanced Security ; désactivez-le avec upload-sarif: false sinon.

Les entrées principales portent les mêmes noms que les options de la CLI, avec des tirets : min-points, min-score, config-file, controls, skip-controls, fail-warnings. D'autres règlent les sorties : upload-sarif, upload-artifacts (30 jours de rétention), output, pbom, pbom-cyclonedx, sarif. L'action expose en sortie passed, report et sarif. Trois entrées méritent une explication :

  • github-token : par défaut le GITHUB_TOKEN du workflow. Une lecture refusée n'est jamais une erreur : le fait concerné reste unknown dans le score ;
  • soft-fail: true : le code 1 (seuil non atteint) ne fait plus échouer le job, mais les codes 2 et 3 échouent toujours ;
  • score : à l'inverse du composant GitLab, l'entrée score de l'action active bien le décompte détaillé (--score-point).

L'entrée version vaut v0.7.2 par défaut et verify-attestation vaut true : laissez la vérification active.

Le PBOM (Pipeline Bill of Materials) est l'inventaire des dépendances de votre pipeline. Côté GitLab, il liste les images (image: et services:), les composants, les templates et les includes. Côté GitHub, il liste les conteneurs (container: et services:), les actions tierces (uses: owner/repo@ref) et les workflows réutilisables. Pour les actions composites et les workflows d'autres dépôts, le PBOM garde la provenance : le champ nested marque une dépendance trouvée à l'intérieur d'une autre, et via donne la chaîne.

Fenêtre de terminal
plumber analyze \
--pbom plumber-pbom.json \
--pbom-cyclonedx plumber-cyclonedx-sbom.json

Les deux fichiers décrivent le même pipeline, mais pas pour le même lecteur. Le PBOM natif conserve des métadonnées propres à Plumber ; le CycloneDX (version 1.5, JSON) perd ce détail au profit d'un format que Trivy, Grype ou Dependency-Track consomment directement. Rien n'empêche de générer les deux. L'éditeur présente le PBOM comme une fonctionnalité en phase initiale, sans standard reconnu pour les inventaires de pipelines.

FormatQuand l'utiliserCe qu'il contient
PBOM natifVous voulez comprendre le pipeline en détailMétadonnées riches propres à Plumber, dont nested et via
CycloneDXVous voulez brancher un autre outilFormat standard pour Trivy, Grype ou Dependency-Track

Pour obtenir l'inventaire seul, sans évaluer les contrôles, ajoutez --no-controls : Plumber collecte et écrit les fichiers d'inventaire (PBOM, JSON, CSV, OCSF) mais ne rend ni verdict ni score, et sort en code 0 si la collecte est complète. L'option est incompatible avec --controls et --skip-controls.

La sous-commande sbom prend le fichier en entrée sans retélécharger les images ; le filtre --severity limite la sortie aux niveaux qui justifient une action immédiate.

Fenêtre de terminal
trivy sbom plumber-cyclonedx-sbom.json --severity HIGH,CRITICAL

Le préfixe sbom: indique à Grype que l'argument est un inventaire et non une image, et --fail-on high fait sortir la commande en code non nul, ce qui casse le job de CI dès qu'une vulnérabilité de ce niveau apparaît.

Fenêtre de terminal
grype sbom:plumber-cyclonedx-sbom.json --fail-on high

Comment remonter les findings dans les tableaux de bord ?

Section intitulée « Comment remonter les findings dans les tableaux de bord ? »

Le PBOM inventorie le pipeline ; pour faire remonter les écarts dans vos plateformes, Plumber produit des rapports de findings standardisés. Vous les générez en ajoutant un drapeau à plumber analyze, chacun écrivant dans un fichier :

OptionFormatUsage
--sarifSARIF 2.1.0GitHub Code Scanning (onglet Security)
--glsastGitLab SAST (schéma 15.0.4)Security Dashboard et widget de merge request GitLab
--csvCSVUne ligne par contrôle, plus une par finding en échec : tableur
--ocsfOCSF 1.8.0 (Compliance Finding)Outils d'observabilité sécurité compatibles OCSF
--outputJSONRapport complet, relisible par plumber explain --run
Fenêtre de terminal
plumber analyze --sarif plumber.sarif --glsast gl-sast-report.json --csv plumber.csv

Ces formats ne remplacent pas la lecture du résumé : ils servent à historiser les findings et à les rapprocher des autres scanners déjà branchés sur vos tableaux de bord. Dans un SARIF, la règle est le code d'issue, le niveau suit la sévérité contextuelle, et la propriété plumber/baseSeverity garde la sévérité propre du code.

Comment comprendre un code ou un chemin avec plumber explain ?

Section intitulée « Comment comprendre un code ou un chemin avec plumber explain ? »

plumber explain est la commande réflexe devant un ISSUE-XXX inconnu : elle fonctionne hors ligne, sans configuration ni dépôt, et le préfixe ISSUE- peut être omis.

Fenêtre de terminal
plumber explain ISSUE-412 # raccourci : plumber explain 412
plumber explain --list # liste compacte des codes
plumber explain --all # référence exhaustive
plumber explain 701 --json # sortie JSON exploitable

Elle renvoie la description, la remédiation et le lien vers la documentation du code. Le JSON ajoute le nom du contrôle (controlName), le rôle du code (role), le type d'entrée (entryKind) et, pour un finding imbriqué, la remédiation à appliquer à la chaîne (nestedRemediation).

explain relit aussi un chemin d'attaque du dernier scan, avec ses claims (voir la section sur le score) :

Fenêtre de terminal
plumber explain -a 1 # chemin numéro 1 du dernier scan
plumber explain -a all # tous les chemins
plumber explain -a 1 --run plumber-report.json # relit un rapport JSON de CI
plumber explain -a 1 --project nektos/act --provider github

L'option -a N n'est valable que si le scan a au moins N chemins : sur un run à un seul chemin, explain -a 3 répond par une erreur (code 2) qui indique le nombre de chemins disponibles. Chaque scan garde son JSON dans le cache utilisateur (~/.cache/plumber/runs/<provider>/<propriétaire>/<dépôt>.json, lisible par vous seul), ce qui permet de relire un chemin sans relancer l'analyse. Pour un scan fait en CI, l'option --run lit le rapport JSON récupéré en artefact.

Autre commande utile pour un outil tiers : plumber catalog imprime en JSON le catalogue des contrôles et des issues de votre version.

Le Plumber Score (profil scoring-v4, le défaut) part de 100 points et retire un prix fixe pour chaque chemin d'attaque, puis un prix plus faible pour chaque finding individuel, c'est-à-dire un problème qu'aucun chemin n'utilise. Un chemin se lit en trois temps : une entrée (une action, une image, une entrée utilisateur injectée dans un script, une branche non protégée), le job où ce code s'exécute, et ce qu'il atteint (secrets, jeton d'écriture, publication, déploiement). La lettre se lit sur les points finaux, avec des seuils fixes.

LettrePointsCe que la lettre signifie
A90 à 100Aucune exposition notable
B71 à 89Exposition mineure
C51 à 70Exposition à corriger
D31 à 50Exposition importante accumulée
E0 à 30Un chemin d'attaque Critical subsiste, ou des pertes très lourdes

Le niveau d'un chemin dépend de ce qu'il atteint, et seuls les faits prouvés comptent. Il est Critical quand un secret ou un jeton d'écriture est prouvé, et que s'y ajoute soit un changement prouvé (publication, déploiement, écriture dans le dépôt), soit une entrée contrôlée par l'ouvreur d'une pull request. Un jeton déclaré en écriture sur contents, packages ou deployments compte à la fois comme privilège et comme changement : un job contents: write atteint par une telle entrée, sous un déclencheur privilégié comme pull_request_target, est donc Critical (sur pull_request, une PR de fork ne reçoit qu'un jeton en lecture). Le chemin est High quand un changement prouvé, ou un secret ou jeton d'écriture prouvé, est atteint seul. Une entrée de pull request sans secret, jeton d'écriture ni changement prouvé reste Low, comme tout chemin où du code s'exécute sans que rien de prouvé soit atteint. Le tableau donne les prix.

NiveauPrix d'un chemin d'attaquePrix d'un finding individuel
Critical3020
High1510
Medium65
Low32

Une dépendance seulement mal épinglée (tag mutable, action sans SHA) est plafonnée à Medium, puisqu'il faut d'abord compromettre son éditeur ; le plafond monte à High quand la source est hors de vos sources autorisées. Une dépendance connue mauvaise (avis publié, commit hors du dépôt de l'action) n'a pas de plafond. Un finding utilisé par un chemin ne coûte rien de plus : la sévérité affichée est celle du chemin, et le JSON garde la sévérité propre du code dans baseSeverity.

Trois limites évitent qu'une accumulation de petits constats fasse s'effondrer le score.

  • Les findings individuels coûtent 30 points au total, au maximum, les plus graves d'abord : à eux seuls, ils ne peuvent jamais faire descendre un dépôt en E.
  • Une limite emboîtée s'applique à tout ce qui n'est pas Critical : les éléments Low comptent pour 29 points au plus, Medium et Low ensemble pour 49, High, Medium et Low ensemble pour 69. Sans aucun Critical, un dépôt garde donc au moins 31 points (D), au moins 51 (C) sans High, au moins 71 (B) sans rien au-dessus de Low.
  • Seul un chemin Critical force le E : il bloque le score à 30 points au plus. Un finding Critical isolé coûte ses 20 points et ne force rien.

Chaque fait sur lequel repose un chemin est une affirmation (claim) qui porte un état. proven : tous les faits sont établis. assumed : elle repose sur une valeur prise par défaut faute de signal, par exemple le GITHUB_TOKEN d'un dépôt dont Plumber n'a pas pu lire l'écriture réelle. unknown : un fait n'a pas pu être décidé (lecture refusée, référence non résolue). Seul le prouvé déplace un palier : le supposé et l'inconnu restent affichés dans la portée du chemin, sans rien changer à son niveau. Dans l'exemple pipeline-craft, c'est ce qui rend les chemins Low : les secrets n'ont pas pu être listés.

Les lignes Note expliquent l'écart : Plumber assumed the token can write: … pour un état supposé, Plumber could not read: … pour un état inconnu, et plumber explain -a N liste toutes les affirmations sous Claims. Plumber pose aussi trois questions par job : qui peut le déclencher (un inconnu, après approbation d'un mainteneur, uniquement ce dépôt), ce qu'il détient (secrets par nom, jeton) et s'il s'exécute (un workflow désactivé, un if: toujours faux ou un workflow_call jamais appelé ne forment aucun chemin). Un jeton sans bloc permissions compte comme détenu, jamais comme un changement. Le détail complet figure dans la documentation officielle du score.

Le profil scoring-v3 attribue des points par sévérité de code, sans chemins d'attaque. Il se choisit avec --score-profile v3 ou la variable PLUMBER_ANALYZE_SCORE_PROFILE=v3, et le champ profileId du JSON indique le profil utilisé. Sur le commit de nektos/act scanné plus haut, ce profil donne 28,0 points, E contre 55 points, C pour le profil par défaut : un profil classe plus durement un dépôt qui accumule des constats Medium. Seule la CLI expose cette option : le composant GitLab et l'action GitHub n'ont aucune entrée de profil.

Comment choisir le seuil qui fait échouer le job ?

Section intitulée « Comment choisir le seuil qui fait échouer le job ? »

Le score est la seule porte de sortie : il pilote le code de retour via --min-points (0-100) ou --min-score (lettre A à E). Le défaut est --min-points 100, donc le moindre finding fait échouer le job (FAILED (gate blocks at 100 pts)). Fixez un seuil réaliste si vous acceptez un résidu : --min-points 80 échoue sous 80, --min-score B échoue sur C, D et E. Posé seul, --min-score décide seul (le défaut de 100 points ne s'applique pas) ; posés ensemble, les deux doivent passer.

Fenêtre de terminal
plumber analyze --min-points 80 # échoue sous 80 points sur 100
plumber analyze --min-score B # échoue en dessous d'un B

Trois cas particuliers surprennent. Un dépôt GitHub sans aucun workflow passe le seuil si les contrôles activés ne remontent rien ; il échoue si aucun contrôle n'a été évalué (configuration sans section pour le provider, filtre qui écarte tout), avec Status: NOTHING CHECKED (no control was evaluated). L'option --threshold est dépréciée. Enfin, en mode Platform, les options --min-*, --controls et --skip-controls sont ignorées avec un avertissement.

Ces deux drapeaux attendent des noms de contrôles séparés par des virgules, pas des codes d'issues, et s'excluent mutuellement. Ils servent à isoler une correction en cours ; ne les laissez pas dans le job de CI, sinon vous cessez de mesurer les contrôles écartés.

Fenêtre de terminal
# Uniquement les images et la protection de branche
plumber analyze --controls containerImageMustNotUseForbiddenTags,branchMustBeProtected
# Tout sauf la protection de branche
plumber analyze --skip-controls branchMustBeProtected

Chaque option de la CLI a son pendant en variable d'environnement, de la forme PLUMBER_ANALYZE_<OPTION> (par exemple PLUMBER_ANALYZE_MIN_POINTS=80) ; les options de la ligne de commande priment.

Avec le composant GitLab, activez-les ainsi :

.gitlab-ci.yml
include:
- component: gitlab.com/getplumber/plumber/plumber@v0.7.2
inputs:
mr_comment: true
badge: true

Plumber Radar : comment vérifier l'hygiène CI/CD d'un fournisseur ?

Section intitulée « Plumber Radar : comment vérifier l'hygiène CI/CD d'un fournisseur ? »

Plumber Radar est une page publique de getplumber.io qui publie le Plumber Score de projets open source scannés en continu, avec la CLI et une configuration d'étude dont le YAML est consultable sur la page. Vous la lisez sans compte à l'adresse getplumber.io/radar. Avant d'ajouter l'action ou l'image d'un fournisseur à votre liste d'autorisation, vous voyez comment ce fournisseur protège ses propres pipelines. La liste se filtre par provider, par configuration, par activité, et selon qu'elle inclut ou non les dépôts archivés et les forks.

La vue la plus utile pour juger un fournisseur est le rapport d'organisation, qui agrège tous ses projets publics. Il s'ouvre en cliquant sur le nom d'une organisation, ou par une URL de la forme https://getplumber.io/radar?q=<organisation>/&provider=github&report=1.

Rapport Plumber Radar de l'organisation docker le 14 octobre 2026 : 53 projets publics, 21 chemins d'attaque dont 1 Critical, 8 High et 12 Medium, répartition des notes 15 A, 19 B, 16 C, 2 D et 1 E, ce qu'un attaquant pourrait faire (lire des secrets, pousser du code, publier des paquets) et par où il entrerait

Ce rapport sur l'organisation docker (capture du 14 octobre 2026) se lit de haut en bas. Le bandeau donne le nombre de chemins d'attaque par niveau, le graphique la répartition des notes des 53 projets. Le bloc « What an attacker could do » dit ce qu'un attaquant atteindrait, et « How they could get in » les entrées qu'il emprunterait, chacune avec son niveau. Pour un chemin Critical, la page résume le risque mais retient les détails. Les chiffres bougent à chaque scan : seule la page en ligne fait foi.

Trois précautions évitent de surinterpréter ces chiffres.

  • C'est une photographie datée : chaque ligne indique la date de son dernier scan, les projets n'ont pas tous été scannés par la même version de Plumber, et la note bouge à chaque commit du fournisseur.
  • Le Radar juge le pipeline du fournisseur, pas son code : pour la maintenance, la revue et la gouvernance du projet, croisez avec OpenSSF Scorecard, et pour une démarche d'évaluation complète des tiers, avec le guide TPRM pour équipes techniques.
  • Une bonne note ne remplace pas l'épinglage : même une action notée A s'épingle par SHA de commit dans vos workflows.

La Platform n'est pas le bon point de départ pour un débutant. Elle devient pertinente quand vous devez suivre plusieurs projets dans le temps, disposer d'un tableau de bord centralisé, gérer des politiques communes à plusieurs équipes et être alerté d'une dérive. Avec --platform <url> (entrée platform du composant et de l'action), la CLI pousse tout le résultat à la Platform par OIDC ; ce mode prime sur --score-push et rend les seuils locaux inertes.

Si votre besoin tient dans « je veux comprendre et sécuriser mon pipeline », restez sur la CLI, le composant GitLab ou l'action GitHub. Si votre besoin devient « je veux gouverner un portefeuille de projets », regardez la Platform.

Plumber 0.7.2 documente 76 codes d'issues en neuf familles, chacun avec une description et une remédiation accessibles via plumber explain ISSUE-XXX. La famille la plus fournie est la 4XX (20 codes : jobs, includes, composants, Docker-in-Docker, nommage), suivie de la 2XX (14 codes) ; la famille 7XX dédiée aux actions tierces en compte 15. Pour la liste exacte de votre installation, utilisez plumber explain --list, qui rend une ligne par code, ou plumber catalog pour le JSON. L'index des issues en ligne les décrit aussi.

Le tableau donne la grille de lecture pour identifier la famille d'une issue remontée :

FamillePlageCouvreExemples
Images de conteneursISSUE-1XX (3 codes)Sources, tags, digest101 source non autorisée, 102 tag interdit, 103 image non épinglée par digest
Exécution et expressionsISSUE-2XX (14 codes)Variables, injection, conditions203 debug trace, 207 injection d'expression, 209 écriture dans $GITHUB_ENV, 213 toJson(github)
Secrets et jetonsISSUE-3XX (8 codes)Fuites, secrets: inherit, jetons302 secrets: inherit, 307 credentials persistés, 309 toJson(secrets)
Jobs, templates et structureISSUE-4XX (20 codes)Jobs, includes, composants, DinD, OIDC, nommage404 include mutable, 410 job de sécurité affaibli, 412 DinD, 414 composant de source non autorisée, 421 publication par jeton statique au lieu d'OIDC, 422 workflow sans name:
Protection de branche et revueISSUE-5XX (6 codes)Protection de branche, règles d'approbation501 branche non protégée, 504 aucune règle d'approbation sur les branches protégées, 505 protection non conforme
Politique de sécurité GitLabISSUE-6XX (1 code)Projet de politique de sécurité lié601 projet de politique absent ou mauvais
Actions tierces (supply chain)ISSUE-7XX (15 codes)Pinning, sources, CVE, archivage, code distant, cache701 action non épinglée par SHA, 703 CVE connu, 705 déclencheur peu fiable avec écriture de cache, 706 FROM non épinglé par digest, 713 source non autorisée, 714 code distant mutable
Permissions et déclencheursISSUE-8XX (4 codes)permissions:, déclencheurs801 pas de permissions: explicite, 804 pull_request_target piégé
Hygiène du dépôtISSUE-9XX (5 codes)Mises à jour, scanners, SECURITY.md903 pas d'outil de mise à jour, 904 pas de scanner statique, 905 pas de SECURITY.md

La famille 7XX concentre les protections supply chain contre les actions tierces compromises. Les familles 8XX et 9XX sont quasi exclusivement GitHub : elles couvrent les risques propres à l'écosystème Actions (permissions du GITHUB_TOKEN, outil de mise à jour, scanners, SECURITY.md).

Avant de parcourir le tableau, retenez deux points : sans jeton, l'analyse est incomplète (code 3), et le défaut min-points 100 fait échouer le job au premier finding.

SymptômeCause probableSolution
Status: INCOMPLETE (data collection failed), code de sortie 3, score absentJeton absent ou refusé : l'API n'a pas pu lire la protection de branche, les environnements ou les états des workflowsExportez GH_TOKEN ou GITLAB_TOKEN (ou gh auth login), avec les droits listés en prérequis
Auth none, running in degraded modeAucun jeton détectéIdem : seuls les contrôles sur le contenu des workflows s'exécutent
Could not verify sur des actionsEnrichissement par l'API GitHub sauté (CVE, dépôt archivé, étoiles)Fournissez un jeton ; PLUMBER_METADATA_TOKEN résout les versions d'actions tierces
GITLAB_TOKEN environment variable is required for GitLab analysisVariable manquante côté provider GitLabExportez GITLAB_TOKEN avant plumber analyze
401 Unauthorized (GitLab)Jeton incomplet ou invalideVérifiez read_api et read_repository
403 Forbidden on MR comment ou sur le badgeScope ou rôle insuffisantUtilisez le scope api (et un rôle Maintainer pour le badge)
Status: FAILED (gate blocks at 100 pts) malgré une bonne lettreSeuil par défaut min-points 100 : le moindre finding fait échouerNormal ; fixez --min-points ou --min-score si vous acceptez un résidu
Status: NOTHING CHECKED (no control was evaluated)Aucun contrôle évalué (configuration sans section pour ce provider, filtre qui les écarte tous)Vérifiez .plumber.yaml, --controls et --skip-controls
unknown key 'controls' at root ou legacy config schema detectedConfiguration au schéma v1Lancez plumber config migrate
Unknown control in gitlab.controls: …Faute de frappe dans un nom de contrôleReprenez la suggestion Did you mean … ; config validate la répète
Error: the run in …/runs/… has 1 attack path avec explain -a 3Le scan n'a pas autant de cheminsDemandez -a 1, ou -a all
Avertissement --threshold is deprecatedSeuil exprimé en pourcentagePassez à --min-points ou --min-score
Le composant ne s'exécute pasJob en .pre sans autre stage normalForcez stage: test
Deux pipelines sur le même pushworkflow:rules absentAjoutez le bloc de démarrage rapide
Le job manuel échoue au démarrageEntrypoint /plumber actif avec un script:Ajoutez entrypoint: [""] à l'image
Le projet n'est pas auto-détectéRemote Git absent ou mal nomméVérifiez origin, passez une cible owner/repo ou --gitlab-url / --github-url et --project
Avertissement contextual score unavailable, scoring-v3 usedPlumber n'a pas pu établir les faits du pipelineLe score suit le profil par points de sévérité ; le champ profileId du JSON le confirme
  • Plumber scanne la sécurité de vos pipelines GitLab CI/CD et GitHub Actions depuis la même CLI ; le composant GitLab et l'action GitHub suivent le même numéro de version (0.7.2)
  • Un jeton est requis, même sur un dépôt public : sans lui, l'analyse est incomplète, le score est retenu et le code de sortie vaut 3
  • Le schéma v2.0 sépare les contrôles par provider ; une surcouche (extends: plumber:default) ne liste que vos changements, et ses listes remplacent celles de la base
  • Le job échoue via le Plumber Score : --min-points (défaut 100) ou --min-score ; codes de sortie 0 réussi, 1 seuil non atteint, 2 erreur, 3 analyse incomplète
  • Commencez par corriger les images non épinglées (ISSUE-103), les actions non épinglées par SHA (ISSUE-701), les includes mutables (ISSUE-404) et les branches non protégées (ISSUE-501)
  • Activez ensuite les protections supply chain : sources autorisées (ISSUE-713), CVE connues (ISSUE-703), pull_request_target (ISSUE-804) et cache écrit par un déclencheur peu fiable (ISSUE-705)
  • Épinglez Plumber lui-même : image getplumber/plumber:0.7.2@sha256:… avec entrypoint: [""], composant @v0.7.2, action par SHA du commit avec # v0.7.2
  • Le score se calcule par chemins d'attaque ; seul le prouvé déplace un palier, les findings individuels coûtent 30 points au plus et seul un chemin Critical force le E
  • Le PBOM inventorie le pipeline ; le CycloneDX se branche sur Trivy et Grype ; --sarif, --glsast, --csv et --ocsf alimentent vos tableaux de bord
  • plumber explain détaille un code hors ligne et, avec -a N, un chemin du dernier scan (valable si le scan en a au moins N)
  • Plumber Radar montre, sans compte, la posture CI/CD des organisations open source dont vous adoptez les actions ou les images
  • 76 codes d'issues en neuf familles ; la liste exacte vient toujours de plumber explain --list sur votre version

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

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

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

Lance le quiz et démarre le chronomètre

Ces questions reviennent systématiquement avant l'adoption : ce que fait l'outil, ce qu'il coûte, ce qu'il apporte face aux scanners déjà en place, s'il touche à vos fichiers et comment il fait échouer un pipeline.

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