Le Cyber Resilience Act vous demande des preuves : SBOM, signature, traçabilité, canal advisory, politique de support. Ce guide vous montre comment produire toutes ces preuves automatiquement pour une CLI Python distribuée, via un pipeline GitHub Actions complet, validé par quatre outils de sécurité, et publiant chaque release avec l'ensemble des artefacts vérifiables.
Le code complet est inspiré d'un lab fonctionnel qu'on construit ensemble : une CLI minimaliste cracli (qui calcule un SHA256), avec une chaîne de distribution conforme à l'esprit du règlement. La valeur n'est pas dans la commande, elle est dans la rigueur du pipeline autour.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Pinner vos dépendances Python et vos actions GitHub par version exacte / SHA
- Documenter un canal de remontée de vulnérabilités conforme dans
SECURITY.md - Valider en continu la conformité de votre pipeline (actionlint, zizmor, poutine, plumber)
- Générer un SBOM CycloneDX à chaque release avec Syft
- Signer les artefacts en mode keyless avec cosign et l'OIDC GitHub Actions
- Publier la release avec SBOM + signatures + certificats + checksums
- Vérifier côté consommateur l'authenticité d'une release en deux commandes
Prérequis
Section intitulée « Prérequis »- Une CLI à distribuer, exemple ici en Python 3.10+ (la démarche se transpose à Go, Node, Rust)
- Un dépôt GitHub (les workflows utilisent l'OIDC GitHub pour cosign keyless)
- Le CLI
ghconfiguré localement pour les validations en pré-publication - Lecture conseillée : guide CRA complet, Syft pour le SBOM, SLSA
1. Le squelette de la CLI avec dépendances épinglées
Section intitulée « 1. Le squelette de la CLI avec dépendances épinglées »La règle absolue : toute version est explicite. Pas de >=, pas de ~=, pas de plage. Le CRA vous demande de pouvoir reproduire bit-à-bit ce que vous avez livré.
pyproject.toml — extrait significatif :
[build-system]requires = ["hatchling==1.27.0"]build-backend = "hatchling.build"
[project]name = "cracli-demo"version = "0.1.0"requires-python = ">=3.10"dependencies = [ "typer==0.15.1",]
[project.scripts]cracli = "cracli_demo.cli:app"Chaque dépendance porte un ==X.Y.Z. Pour une CLI réelle, vous compléterez avec pip-compile ou uv pip compile pour figer aussi les sous-dépendances dans un requirements.lock. Sans verrouillage transitif, vous ne pouvez pas démontrer ce qui a réellement été livré.
La CLI elle-même est triviale (trois sous-commandes Typer : hash, verify, version). Le détail importe peu pour ce guide — l'objet est la chaîne autour.
2. SECURITY.md : la pièce maîtresse côté consommateur
Section intitulée « 2. SECURITY.md : la pièce maîtresse côté consommateur »Le CRA exige un canal de divulgation responsable documenté et publiquement accessible. Sans SECURITY.md, vous ne pouvez pas démontrer ce point d'audit. Le fichier doit couvrir au moins quatre sections : versions supportées, canal de remontée, délais d'engagement, vérification d'authenticité.
Versions supportées — un tableau lisible, et une durée minimale documentée :
| Version | Statut | Fin de support || -------- | ------------------ | -------------- || `0.1.x` | Maintenue (active) | À définir |
La durée minimale de support est de **5 ans** à compter de la mise surle marché de la version majeure, conformément à l'esprit du CRA. Toutemodification est annoncée dans le CHANGELOG.md avec un préavis de 6 mois.Canal de remontée — privilégier les Private Security Advisories GitHub (traçabilité native + workflow CVE intégré) avec un fallback email :
**Canal privé recommandé** : utiliser la fonctionnalité GitHub[Private Security Advisories](https://github.com/<org>/<repo>/security/advisories/new).
**Canal alternatif** : envoyer un courriel à `security@votreentreprise.com`.
Ne pas ouvrir d'issue publique ni publier un PoC tant que le correctifn'est pas disponible et qu'un advisory n'a pas été émis.Délais d'engagement — alignés avec l'article 14 du CRA pour les vulnérabilités activement exploitées :
| Étape | Délai cible |
|---|---|
| Accusé de réception | sous 48 heures ouvrées |
| Triage initial | sous 5 jours ouvrés |
| Notification autorités CRA | sous 24 h (early warning) en cas de vulnérabilité activement exploitée |
| Notification complète | sous 72 h |
Vérification d'authenticité — la procédure que vos utilisateurs vont copier (donc à tester avant publication). On y revient en section 7.
3. Validation continue de la conformité du pipeline
Section intitulée « 3. Validation continue de la conformité du pipeline »Avant même de parler de produit, le CRA suppose que votre pipeline lui-même est auditable. Quatre outils complémentaires se relaient pour le valider, chacun couvrant un angle :
| Outil | Couvre | Mode dans le workflow |
|---|---|---|
| actionlint | Syntaxe YAML, erreurs courantes, scripts shell | Binaire téléchargé avec checksum |
| zizmor | Audit sécurité spécifique GitHub Actions | Binaire téléchargé avec checksum |
| poutine | Scanner CI/CD multi-plateformes orienté supply chain | Action officielle pinnée par SHA |
| plumber | Conformité de pipelines, PBOM CycloneDX | Binaire téléchargé avec checksum |
L'idée : chaque push et chaque pull request déclenche un job validate-pipeline qui exécute les quatre outils. Si l'un fail, la CI bloque. Vous obtenez ainsi un gardien continu : impossible d'introduire silencieusement une régression de sécurité dans le workflow.
Job de validation — squelette pertinent :
jobs: validate-pipeline: runs-on: ubuntu-24.04 permissions: contents: read env: ACTIONLINT_VERSION: "1.7.12" ZIZMOR_VERSION: "1.25.2" PLUMBER_VERSION: "0.3.50" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false
- name: actionlint run: | curl -sSL -o /tmp/al.tar.gz \ "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" # Vérification du checksum, install, exécution...
- name: zizmor run: zizmor .github/workflows/
- name: poutine uses: boostsecurityio/poutine-action@e240ebd3eff8b2db5a8e5f6b28f58739d7db2247 # v1.1.4
- name: plumber run: plumber analyze --pbom-cyclonedx plumber-pbom.cdx.jsonTrois détails non négociables dans ce job :
permissions: contents: readau niveau job (jamais hériter de permissions implicites)persist-credentials: falsesuractions/checkout(sinon le token GitHub reste dans la config Git du workspace — vulnérabilité classique exploitée par plusieurs incidents supply chain récents)- Toutes les actions tierces épinglées par SHA 40 caractères, avec le tag en commentaire pour la lisibilité
Plumber génère un PBOM (Pipeline Bill of Materials) au format CycloneDX qui complète votre SBOM applicatif. Vous avez ainsi deux niveaux d'inventaire : ce qui constitue votre produit (SBOM) et ce qui constitue le pipeline qui le produit (PBOM). C'est cette double visibilité que demande implicitement le CRA.
4. Génération du SBOM CycloneDX avec Syft
Section intitulée « 4. Génération du SBOM CycloneDX avec Syft »Le SBOM est l'inventaire des composants de votre produit, dans un format machine-lisible. Sans lui, vous ne pouvez pas répondre à la question CRA fondamentale : « si la CVE X tombe sur la dépendance Y, mes produits sont-ils impactés ? ».
Voir le guide Syft pour la théorie. Côté pipeline, l'action anchore/sbom-action génère le SBOM en une étape :
- name: Générer le SBOM CycloneDX (Syft) uses: anchore/sbom-action@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8 with: path: . format: cyclonedx-json output-file: sbom.cdx.json upload-artifact: false upload-release-assets: falseLe SBOM est généré avant la signature (logique : on signe le SBOM aussi). Format CycloneDX JSON parce qu'il est le standard supply chain le plus largement adopté en 2026 (SPDX reste valide mais a moins de traction dans l'écosystème supply chain security).
5. Signature des artefacts en mode cosign keyless
Section intitulée « 5. Signature des artefacts en mode cosign keyless »Cosign keyless utilise l'OIDC GitHub Actions pour signer sans gérer de clé privée. À la place, chaque signature est liée à un certificat éphémère émis par Sigstore pour la durée du job, prouvant que la signature a bien été produite par votre workflow GitHub à un commit donné.
- name: Installer cosign uses: sigstore/cosign-installer@1aa8e0f2454b781fbf0fbf306a4c9533a0c57409 # v3.7.0 with: cosign-release: 'v2.4.1'
- name: Signer les artefacts (cosign keyless, OIDC) run: | set -euo pipefail for f in dist/* sbom.cdx.json; do cosign sign-blob --yes "$f" \ --output-signature "${f}.sig" \ --output-certificate "${f}.crt" donePermissions requises au niveau du job : id-token: write (pour récupérer le token OIDC GitHub) et contents: write (pour publier la release). Sans id-token: write, cosign keyless échoue avec un message obscur — pensez à le rajouter explicitement.
Pour aller plus loin avec la provenance SLSA niveau 3 (attestation in-toto signée), vous pouvez ajouter le reusable workflow slsa-framework/slsa-github-generator. Hors scope de ce guide pour rester focus, mais c'est l'étape suivante naturelle si votre produit doit être audité par un tiers.
6. Checksums et publication de la release
Section intitulée « 6. Checksums et publication de la release »Les checksums SHA256 servent les utilisateurs qui ne veulent pas (ou ne peuvent pas) installer cosign. C'est la couche minimale, sans laquelle vous laissez l'utilisateur sans aucun moyen de vérifier l'intégrité.
- name: Calculer les checksums SHA256 run: | set -euo pipefail (cd dist && sha256sum -- *) > checksums.txt sha256sum sbom.cdx.json >> checksums.txt
- name: Créer la GitHub Release avec toutes les preuves env: GH_TOKEN: ${{ github.token }} TAG_NAME: ${{ github.ref_name }} run: | gh release create "$TAG_NAME" \ --title "$TAG_NAME" \ --generate-notes \ dist/* \ sbom.cdx.json \ sbom.cdx.json.sig \ sbom.cdx.json.crt \ checksums.txtPourquoi gh release create plutôt qu'une action tierce ? Le CLI gh est pré-installé sur les runners GitHub-hosted : zéro dépendance externe, zéro surface d'attaque supply chain ajoutée, et ça passe sans warning les scanners de pipeline qui flaggent les « unverified creators ». Sur ce point précis, choisir la solution native gagne sur tous les axes.
7. Vérification côté consommateur
Section intitulée « 7. Vérification côté consommateur »C'est ce que vos utilisateurs vont copier-coller depuis votre SECURITY.md. Donc à tester avant chaque release pour ne pas leur donner une procédure cassée.
Vérification minimale (checksums) :
sha256sum --check checksums.txtVérification cosign keyless (sans clé) :
cosign verify-blob \ --signature cracli_demo-0.1.0-py3-none-any.whl.sig \ --certificate cracli_demo-0.1.0-py3-none-any.whl.crt \ --certificate-identity-regexp 'https://github\.com/<org>/<repo>/' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ cracli_demo-0.1.0-py3-none-any.whlLe --certificate-identity-regexp est crucial : il fixe quel workflow est autorisé à signer. Sans cette ancre d'identité, n'importe qui ayant cosign peut produire une signature valide pour Sigstore — vous valideriez la cryptographie sans valider l'origine.
Ce que cette chaîne démontre vis-à-vis du CRA
Section intitulée « Ce que cette chaîne démontre vis-à-vis du CRA »Sept exigences principales du CRA, sept éléments produits par ce pipeline :
| Exigence CRA | Élément correspondant |
|---|---|
| Inventaire des composants | SBOM CycloneDX par release (Syft) |
| Intégrité des artefacts livrés | Signatures cosign keyless + checksums SHA256 |
| Traçabilité de la chaîne d'approvisionnement | Pipeline auditable + PBOM Plumber + certificats OIDC |
| Canal de divulgation des vulnérabilités | SECURITY.md + Private Security Advisories GitHub |
| Politique de support documentée | Tableau « Versions supportées » dans SECURITY.md |
| Sécurité par défaut du pipeline lui-même | Validation continue actionlint / zizmor / poutine / plumber |
| Notification 24 h / 72 h des vulnérabilités exploitées | Délais documentés + canal GHSA en place |
Ce que ce pipeline ne couvre pas et qui reste à votre charge :
- Le triage humain des vulnérabilités remontées (qui décide ? avec quels critères ?)
- La communication aux utilisateurs (mail list, RSS, GHSA — à choisir)
- La classification CRA du produit (important, critique, défaut — déclaration manuelle)
- L'attestation SLSA niveau 3 (étape avancée, demande un reusable workflow supplémentaire)
- La vérification continue des signatures côté consommateur (admission controller Kubernetes, policy as code)
À retenir
Section intitulée « À retenir »- Pinner tout : versions Python
==X.Y.Z, actions GitHub@<SHA>, binaires téléchargés vérifiés par checksum. Sans pinning, pas de reproductibilité, donc pas de conformité démontrable. - Valider le pipeline avant le produit : actionlint + zizmor + poutine + plumber sur chaque push. La conformité du produit suppose un pipeline auditable.
- SBOM, signature, checksums sont indissociables : chacun couvre un angle (inventaire / intégrité / vérification simple). Ils s'utilisent ensemble, pas l'un ou l'autre.
- Cosign keyless avec OIDC GitHub est le standard 2026 pour une CLI distribuée. Aucune clé à gérer, identité du signataire ancrée sur l'URL du workflow.
SECURITY.mdest la pièce maîtresse côté consommateur. Sans politique de support et canal de remontée documentés, vous ne pouvez pas démontrer l'engagement de durée de vie qu'exige le CRA.- Préférer
gh release createaux actions tierces quand c'est possible. Une dépendance externe en moins = une surface d'attaque en moins, et un scan de pipeline qui passe plus facilement.