Aller au contenu
Développement medium

Sécuriser la supply chain d'un projet Go

10 min de lecture

Go

Une seule dépendance vulnérable dans votre go.mod peut exposer tout le binaire ; encore faut-il la détecter, la corriger et prouver ce que vous livrez. Ce guide vous apprend à scanner votre code avec govulncheck, qui ne signale que les failles réellement atteignables, à maîtriser l'intégrité des dépendances via go.mod, go.sum et go mod verify, à produire un SBOM (l'inventaire de ce que contient votre binaire), et à câbler ces contrôles en intégration continue pour bloquer une livraison vulnérable. Public visé : vous savez compiler un binaire Go et gérer un module. Exemples testés sur Go 1.26.

  • Détecter les vulnérabilités atteignables avec govulncheck.
  • Vérifier l'intégrité des dépendances avec go.sum et go mod verify.
  • Générer un SBOM du binaire, natif ou au format CycloneDX.
  • Bloquer une livraison vulnérable en intégration continue.

govulncheck est l'outil officiel de l'équipe Go. Sa force par rapport à un scanner classique : il ne se contente pas de lire les versions du go.mod, il fait une analyse d'atteignabilité (call graph). Il ne remonte donc que les vulnérabilités dont le code vulnérable est effectivement appelé par le vôtre, ce qui réduit drastiquement le bruit. Installez-le puis lancez-le sur le projet :

Fenêtre de terminal
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

Sur un projet qui utilise une vieille version de golang.org/x/text et appelle language.ParseAcceptLanguage, le rapport est précis : il nomme la faille, la version qui corrige, et la trace d'appel exacte qui prouve l'atteignabilité :

Vulnerability #1: GO-2022-1059
Denial of service via crafted Accept-Language header in
golang.org/x/text/language
More info: https://pkg.go.dev/vuln/GO-2022-1059
Found in: golang.org/x/text@v0.3.5
Fixed in: golang.org/x/text@v0.3.8
Example traces found:
#1: main.go:11:44: main.main calls language.ParseAcceptLanguage
Your code is affected by 2 vulnerabilities from 1 module.

La correction consiste presque toujours à monter la dépendance vers la version corrigée, puis à relancer le scan :

Fenêtre de terminal
go get golang.org/x/text@latest
go mod tidy
govulncheck ./...
No vulnerabilities found.

C'est la nuance qui distingue govulncheck d'un scanner classique. Reprenez le même projet avec sa dépendance vulnérable en version 0.3.5, mais sans appeler la fonction fautive : on se contente de language.Make, qui n'est concernée par aucune des deux failles.

tag := language.Make("fr") // fonction NON vulnérable

Le verdict s'inverse complètement :

No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
... but your code doesn't appear to call these vulnerabilities.

La dépendance vulnérable est toujours là, et govulncheck le sait. Mais comme aucun chemin d'appel n'atteint le code fautif, il ne déclenche pas d'alerte. Un scanner qui lirait seulement le go.mod vous aurait noyé sous deux alertes sans objet. C'est tout l'intérêt de l'atteignabilité : moins de bruit, des alertes qui comptent.

On n'a pas toujours la source sous la main : un artefact issu d'une chaîne de build, un binaire tiers. govulncheck analyse aussi un binaire compilé avec -mode binary :

Fenêtre de terminal
go build -o outil .
govulncheck -mode binary outil

Sur un binaire qui appelle réellement la fonction vulnérable, les failles ressortent :

Vulnerability #1: GO-2022-1059
Found in: golang.org/x/text@v0.3.5
Fixed in: golang.org/x/text@v0.3.8

Un binaire ne contient toutefois pas le graphe d'appels détaillé : le mode binaire ne montre pas la trace menant à la faille et se révèle un peu moins précis. Réservez-le à la vérification d'un artefact livré ; préférez le scan de la source quand vous l'avez.

Le fichier go.mod déclare vos dépendances et leurs versions ; le fichier go.sum enregistre les empreintes cryptographiques (hachages) de chaque module téléchargé. Ce second fichier est votre garantie d'intégrité : il détecte toute altération d'une dépendance entre deux récupérations. Commitez toujours go.sum, et vérifiez à tout moment que le cache local correspond aux empreintes attendues :

Fenêtre de terminal
go mod verify
all modules verified

Deux réflexes complètent cette base. Épinglez des versions précises plutôt que de dépendre de @latest en production, pour des builds reproductibles. Et faites régulièrement le ménage avec go mod tidy, qui synchronise go.mod et go.sum avec les imports réels du code, en retirant les dépendances devenues inutiles.

Un SBOM (Software Bill of Materials, la nomenclature logicielle) est l'inventaire de tout ce que contient votre binaire : c'est ce qui permet, quand une faille est publiée, de savoir en minutes si vous êtes concerné. Ne confondez pas avec go.sum : ce dernier prouve l'intégrité, le SBOM décrit le contenu.

Go fournit déjà un inventaire minimal, sans aucun outil externe : la commande go version -m liste les modules embarqués dans un binaire compilé, avec leur version et leur empreinte :

Fenêtre de terminal
go build -o outil .
go version -m outil
mod example.com/scalab (devel)
dep golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEeg...

Pour un SBOM au format standard, échangeable avec d'autres outils de sécurité, utilisez cyclonedx-gomod, qui produit du CycloneDX avec les identifiants purl de chaque composant :

Fenêtre de terminal
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest
cyclonedx-gomod mod -json -output sbom.json .

Le fichier obtenu déclare son format et liste chaque dépendance de façon interopérable :

bomFormat: CycloneDX specVersion: 1.6
composant: golang.org/x/text v0.40.0
purl: pkg:golang/golang.org/x/text@v0.40.0

L'intérêt de ces contrôles se révèle en intégration continue : un scan qui échoue doit stopper la livraison avant qu'un binaire vulnérable ne parte en production. govulncheck est fait pour cela, car il renvoie un code de sortie non nul quand il trouve une faille atteignable. Sur notre projet, le code passe de 0 à 3 selon l'état :

Fenêtre de terminal
govulncheck ./... ; echo "exit=$?"
# projet corrigé -> exit=0
# projet vulnérable -> exit=3

Un job qui lance simplement govulncheck ./... échoue donc de lui-même dès qu'une faille apparaît. L'équipe Go publie aussi une action officielle, golang/govulncheck-action (à épingler par SHA comme toute action), pour l'intégrer sans script. Combinez ce garde-fou avec go mod verify et la génération du SBOM à chaque build, et vous couvrez les trois piliers : détecter les failles, garantir l'intégrité, inventorier le livré.

La sécurité de la supply chain donne un faux sentiment de maîtrise quand on comprend mal les outils. Voici les confusions les plus courantes.

Idée reçueRéalitéBon réflexe
« govulncheck lit mon go.sum »Il analyse le graphe d'appels, pas juste les versionsÉcrire le code test réel ; il ne signale que l'atteignable
« Zéro alerte = zéro risque »reflect et unsafe échappent à l'analyseDéfense en profondeur, ne pas s'arrêter à un seul outil
« go.sum est mon SBOM »go.sum prouve l'intégrité, pas le contenuGénérer un SBOM distinct (go version -m, CycloneDX)
« Un scanner de go.mod suffit »Il sur-rapporte des failles dans des dépendances non appeléesPréférer l'analyse d'atteignabilité de govulncheck
« @latest me garde à jour »Il casse la reproductibilité des buildsÉpingler les versions, mettre à jour de façon contrôlée

Le contresens le plus répandu oppose govulncheck aux scanners qui se contentent de lire le go.mod. Ces derniers signalent toute vulnérabilité présente dans l'arbre des dépendances, y compris dans une bibliothèque transitive que votre code n'appelle jamais. Vous vous retrouvez alors à trier des alertes sans objet. govulncheck élimine ce bruit en prouvant, trace d'appel à l'appui, qu'une faille est réellement joignable depuis votre programme. C'est précisément ce qui en fait un bon premier filtre, à condition de garder en tête ses angles morts (reflect, unsafe).

  • govulncheck ne signale que les vulnérabilités atteignables, avec la trace d'appel qui le prouve ; il ignore reflect et unsafe.
  • On corrige en montant la dépendance vers la version marquée Fixed in, puis on relance le scan.
  • go.sum garantit l'intégrité des dépendances ; go mod verify la vérifie, go mod tidy la nettoie.
  • Un SBOM décrit le contenu livré : go version -m en natif, CycloneDX (cyclonedx-gomod) pour un format standard.
  • En CI, govulncheck renvoie exit 3 sur une faille atteignable : le job échoue et bloque la livraison.
  • go.sum n'est pas un SBOM : l'un prouve l'intégrité, l'autre inventorie les composants.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn