Aller au contenu

0017 — Releases npm signées par OIDC sur deux registres

Le monorepo publie une vingtaine de paquets sur npm. Deux risques classiques pèsent sur la chaîne de publication :

  1. Vol de jeton npm — un NPM_TOKEN compromis permet à un attaquant de publier des versions vérolées au nom du projet. La rotation est manuelle et la fenêtre d’exposition est longue.
  2. Indisponibilité de registre — si le registre principal est en panne ou refuse une publication (politique, modération, panne réseau), les consommateurs sont coincés.

GitHub Actions permet depuis 2023 la publication OIDC : le runner demande à GitHub un jeton court (15 min), npm le vérifie auprès de GitHub, et la publication s’effectue sans NPM_TOKEN persistant. La provenance (URL du workflow, commit, repo) est attachée au paquet et vérifiable par le consommateur avec npm audit signatures.

GitHub Packages, en parallèle de npm public, offre un second registre qui sert de mirror et de fallback.

Toutes les releases (paquets packages/* et cli/* non private) sont publiées sur deux registres :

  • npm public (registry.npmjs.org), registre primaire, avec provenance OIDC ;
  • GitHub Packages (npm.pkg.github.com), registre secondaire, en mirror de disponibilité, sans provenance.

La provenance npm n’est attachée qu’au registre primaire. L’attestation in-toto signée OIDC (npm audit signatures) n’est honorée que par registry.npmjs.org ; sur GitHub Packages, --provenance n’apporte rien et peut faire échouer la publication côté serveur. La boucle GitHub Packages de publish-packages.sh force donc NPM_CONFIG_PROVENANCE=false. Aucun NPM_TOKEN long-terme n’est stocké côté GitHub Actions : la publication utilise id-token: write (OIDC) et un trust npm préalablement configuré.

Le consommateur vérifie la chaîne avec :

Fenêtre de terminal
npm audit signatures

Accepted.

Bénéfices. Pas de jeton npm long-terme à rotater ni à exfiltrer. La provenance lie chaque paquet publié à un commit Git et un workflow auditables. Le double registre offre un fallback en cas d’indisponibilité du registre primaire.

Prix à payer. Le workflow de release est plus complexe (deux publications successives, vérification de provenance). Les bumps de versioning doivent être synchronisés entre registres (un seul registre qui rate la publication crée une divergence). Quelques outils tiers ne vérifient pas encore la provenance.

Garde-fous.

  • La pipeline de release est documentée dans docs/collaboration/releases.md.
  • Un incident de publication (un registre seul vert, l’autre rouge) ouvre un ticket d’investigation systématique.
  • La rotation des trusts OIDC est revue lors de l’audit semestriel.
  • Tant que la publication npm passe par un NPM_TOKEN (et non encore par le trust OIDC pur), ce token doit être un automation token : un token classique sur un compte à 2FA active échoue en CI avec EOTP This operation requires a one-time password (constaté en Phase 15). L’automation token contourne la 2FA pour la publication programmatique. À retirer le jour où le trust OIDC npm est pleinement configuré (publication sans token).
  • Le script scripts/release/publish-packages.sh rend l’échec npm non bloquant pour la publication GitHub Packages (registre de repli) et scanne toutes les catégories publiables (packages/, cli/, services/, config/, assets/) — un bug antérieur n’itérait que sur packages/, laissant les CLIs hors de GitHub Packages.
  • La boucle GitHub Packages ne masque plus tout échec : elle tolère le seul cas bénin (version déjà publiée → « cannot publish over ») et propage les vraies erreurs (token invalide, scope manquant, réseau) via un code de sortie agrégé — au lieu de l’ancien || true global qui sortait un faux « ✅ » même quand un paquet n’avait jamais atteint le mirror. La boucle tourne dans le shell courant (done < <(find …), pas find … | while) pour que ce code de sortie survive au sous-shell.