Aller au contenu

Chaîne d'approvisionnement : ce qui se vérifie

La nomenclature CycloneDX, le dossier VEX et sa règle de justification, les scans et leur cadence, le contrôle « zéro symbole de sortie réseau » sur l'artefact construit, les signatures et la livraison.

Cette page décrit les artefacts et les contrôles de la chaîne d’approvisionnement logicielle, avec le chemin du fichier ou du workflow qui les produit. Chaque affirmation renvoie à ce qu’un acheteur peut vérifier lui-même. Chaque version est livrée avec sa nomenclature (SBOM), sa déclaration d’exploitabilité (VEX) et ses signatures (livre blanc sécurité V3, section 9.1). Les axes que le livre blanc ne couvre pas sont décrits dans Ce qui reste à construire.

Le workflow .github/workflows/sbom-release.yml produit une nomenclature CycloneDX de toutes les dépendances npm du dépôt, générée par Trivy sur une version figée :

Fenêtre de terminal
trivy fs --format cyclonedx --output source.cdx.json ./

Le document est attaché à la version sous un nom adressable et versionné, lemniscate-source-<tag>.cdx.json, et livré avec elle. Il couvre les services de la passerelle, les extensions et leurs dépendances, et vous pouvez le charger dans vos outils de gestion des vulnérabilités (livre blanc sécurité V3, section 9.1). Il n’est pas committé : c’est un dérivé reproductible d’une version figée, que vous pouvez rejouer avec un autre outil et comparer.

Un garde-fou précède l’attachement. Le workflow échoue si le document n’annonce pas bomFormat: CycloneDX, s’il n’a pas de version de spécification, ou s’il ne contient aucun composant. Le décompte des composants est écrit dans le résumé du job.

Le mécanisme s’auto-vérifie : toute modification de ce workflow ou de l’installation de Trivy déclenche une génération et une validation de la nomenclature. L’étape d’attachement se déclenche sur l’événement release : une nomenclature accompagne chaque version.

Un scanner qui rend une liste de vulnérabilités sur une nomenclature ne dit pas lesquelles atteignent le produit. La réponse à « vous avez N CVE, justifiez » vit dans security/vex/, sous forme de documents OpenVEX versionnés.

Ces documents sont committés. Ce sont des décisions humaines tracées : l’historique git dit qui a justifié quoi, et quand. La déclaration VEX est générée à chaque version et livrée avec elle : pour chaque vulnérabilité publiée sur une dépendance, elle dit si le produit est affecté et ce qui est fait (livre blanc sécurité V3, section 9.1).

Le validateur security/lib/vex.ts rejette tout statement not_affected dépourvu de l’une des cinq justifications du référentiel CISA :

  • component_not_present
  • vulnerable_code_not_present
  • vulnerable_code_not_in_execute_path
  • vulnerable_code_cannot_be_controlled_by_adversary
  • inline_mitigations_already_exist

Le rejet intervient avant que le scanner ne consomme le dossier. Aucune CVE n’est retirée d’un rapport sans justification recevable, et cette garantie ne dépend pas du comportement d’un outil tiers. Elle est couverte par security/vex/vex-validation.test.ts.

Le contrôle porte aussi sur le dossier réel, pas seulement sur des fixtures : security/vex/vex-dir-compliance.test.ts valide security/vex/ tel qu’il est committé. Un statement non conforme fait échouer l’intégration continue.

Le jugement d’exploitabilité est humain. Aucune analyse automatisée d’atteignabilité n’est employée (décision D3 de specs/2026-07-29-sbom-vex/spec.md). Deux raisons sont consignées : les moteurs crédibles pour JavaScript et TypeScript sont des services hébergés aux États-Unis, ce qui les exclut pour un produit destiné à des environnements isolés ; et leur mode d’échec est le faux négatif, c’est-à-dire une affirmation de conformité fausse.

ContrôleFichierDéclenchementEffet
OSV différentiel sur une proposition de modification.github/workflows/osv-scanner-pr.ymlà chaque propositionbloquant si elle en introduit une
OSV, dépôt completscripts/cron-local/jobs/osv-scan.shlundi 11h00, sur le poste de l’éditeurrapport et décompte, signalés à l’éditeur
Secrets (Trivy)scripts/cron-local/jobs/trivy-secret.shlundi 11h30, sur le poste de l’éditeurrapport et décompte, signalés à l’éditeur
Images de conteneurs (Trivy)scripts/cron-local/jobs/trivy-image.shlundi 12h00, sur le poste de l’éditeurconstruction locale puis scan
Dépendances après suppression VEX (Trivy)scripts/cron-local/jobs/trivy-vex.shlundi 12h30, sur le poste de l’éditeurliste résiduelle, chaque retrait justifié

Les quatre scans planifiés tournent sur le poste de l’éditeur, sous des minuteries systemd déclarées dans scripts/cron-local/schedule.yaml, sur une copie propre de main. Chacun se termine par un message envoyé à une personne : un scan qui n’a rien trouvé le dit, et un scan qui n’a pas pu mesurer échoue. Les workflows .github/workflows/trivy-secret-scan.yml, trivy-image-scan.yml et trivy-vex-scan.yml gardent un déclencheur manuel ; les deux derniers s’auto-testent aussi sur une proposition qui les modifie.

La porte différentielle exécute deux scans, l’un sur la branche cible, l’autre sur la branche courante, et n’échoue que si la modification introduit une vulnérabilité. Une dette héritée ne bloque pas le travail courant ; une régression ne passe pas. Le contrôle ne dépend d’aucune fonctionnalité payante de la forge.

Le scan « après suppression VEX » consomme les documents du dépôt : sa sortie est la liste de ce qui reste à traiter, chaque retrait étant adossé à un document justifié et signé par un auteur identifiable. Son comportement est vérifié sur une vulnérabilité figée dans security/vex/vex-suppression.integration.test.ts.

Le scan de secrets est éprouvé par un canari : security/trivy/secret-scan.integration.test.ts place un secret factice et vérifie qu’il est détecté.

Un job dédié, .github/workflows/security-checks.yml, exécute le typage et les tests du paquet security/ sur toute proposition qui y touche.

Un scanner téléchargé sans vérification annule l’intérêt du scan. Trois règles s’appliquent :

  • Trivy est épinglé par version et vérifié par empreinte. L’action .github/actions/install-trivy/action.yml centralise la version pour tous les workflows de sécurité et compare l’archive au fichier de sommes publié dans la même release avant de l’exécuter. Aucun tag mouvant, aucun latest (identifiant SEC-9.3-04).
  • Les actions tierces des workflows de sécurité sont épinglées par empreinte de commit, avec la version en commentaire.
  • Les dépendances sont installées par npm ci contre les fichiers de verrouillage, ce qui valide l’empreinte d’intégrité de chaque paquet installé.

Le contrôle « zéro symbole de sortie réseau » sur l’artefact construit

Section intitulée « Le contrôle « zéro symbole de sortie réseau » sur l’artefact construit »

Ce contrôle ne porte pas sur les sources mais sur le binaire livré. Il se vérifie sans accès au code.

Lemniscate se construit selon un profil de déploiement choisi à la construction, et non par un réglage à l’exécution : dans l’artefact on-premise, la couche de sortie réseau n’est pas désactivée, elle est absente du graphe de modules. Une garantie qui dépend d’un réglage se falsifie par un changement de configuration ; celle-ci se falsifie par inspection de l’artefact.

Fenêtre de terminal
npm run build --prefix extensions/cli
npm run esbuild-base --prefix extensions/vscode
(cd gui && NODE_OPTIONS=--max-old-space-size=6144 npx vite build)
npm run build --prefix llm-gateway
npm run build --prefix gateway-admin

Le script connaît six unités déployables : cli, vscode, gui, llm-gateway, gateway-admin et intellij. Sans option, il les inspecte toutes ; l’option répétable --composant restreint l’inspection aux composants nommés. Un composant demandé mais non construit n’est pas jugé conforme : il est signalé comme absent, et le contrôle échoue. L’invocation ci-dessous nomme les cinq unités JavaScript ; le greffon JetBrains, qui exige une construction Gradle, est inspecté hors de l’intégration continue.

Fenêtre de terminal
node scripts/verify-onprem-artifact.mjs \
--composant cli --composant vscode --composant gui \
--composant llm-gateway --composant gateway-admin

Le script applique quatre contrôles indépendants :

  1. Les symboles présents dans le texte des artefacts construits : domaines, clés de projet, noms de bibliothèques. C’est le seul angle vérifiable par un tiers sans lire le code source.
  2. Les entrées des manifestes de construction esbuild. Un bundle minifié peut ne plus porter le nom du paquet qu’il embarque ; le manifeste liste chaque module entré dans le graphe. Ce second angle survit à la minification.
  3. L’inventaire des unités déployables : l’apparition d’une unité non déclarée fait échouer le contrôle.
  4. L’absence d’appel, au chargement d’un module, d’une frontière de profil de déploiement, c’est-à-dire d’un point où la sortie réseau est substituée.

Ce contrôle n’a aucune option de désactivation et aucune liste d’exceptions. Le critère d’acceptation de l’arbitrage AR-03 (specs/securite/arbitrages.md) est zéro occurrence. Un symbole qui réapparaît se retire du graphe de modules ; il ne se met pas sur une liste dérogatoire.

Le contrôle s’exécute dans .github/workflows/pr.yaml sur chaque proposition de modification : les deux contrôles qui lisent les sources tournent sur chaque proposition, avec l’option --sans-artefacts, et l’inspection des artefacts construits tourne dans le job qui les construit. Le script est lui-même couvert par scripts/verify-onprem-artifact.test.mjs.

Le détail des flux, côté produit, est décrit dans Où passent les données.

Aucun artefact n’est livré sans signature. Les images de conteneurs, image de session comprise, et les composants du poste sont signés. La clé de signature vous est remise avec la première livraison, et son empreinte vous est communiquée par un canal distinct, ce qui permet de détecter une substitution. Les mises à jour se vérifient ensuite hors ligne, avant leur installation (livre blanc sécurité V3, sections 9.1 et 9.3).

À la publication d’une version, la signature est posée par cosign (.github/actions/sign-release-blobs). Elle couvre aussi la nomenclature CycloneDX des sources et le manifeste de livraison, qui liste chaque fichier installable avec son empreinte SHA-256. Les signatures sont attachées à la version, à côté des artefacts.

Les artefacts vous parviennent par l’un de deux chemins (livre blanc sécurité V3, section 9.3) :

  • en mode connecté, ils sont poussés dans votre registre privé ;
  • en mode isolé, ils sont livrés sur média, sous forme d’archive signée, avec la nomenclature et la déclaration VEX.

Dans les deux cas, l’installation commence par la vérification des signatures avec la clé que vous détenez. Votre équipe applique ensuite l’installation ou la mise à jour : ni les services ni les extensions ne se mettent à jour seuls, et le produit ne dépend d’aucun service externe. La version précédente est conservée pour un retour arrière.

Une batterie de scénarios d’évasion est exécutée avant chaque livraison, en six familles : sortie réseau depuis le bac à sable, lecture ou écriture hors de la copie du dépôt, modification d’un fichier exécuté par un tiers, injection d’instructions par un fichier du dépôt, épuisement de ressources, contournement de la politique de commandes. Le rapport d’exécution est joint à la version, au même titre que la nomenclature et la déclaration VEX, et une régression bloque la publication. Vous pouvez rejouer ces scénarios sur votre déploiement (livre blanc sécurité V3, section 9.2).

Le canal est SECURITY.md, à la racine du dépôt : signalement par courriel à security@lemniscate-tech.com, sans ouverture d’issue publique, avec description, étapes de reproduction, appréciation de l’impact et mitigations éventuelles.

C’est un canal de signalement, pas un programme de prime aux vulnérabilités. Ce qu’il ne porte pas est décrit dans Ce qui reste à construire.

  • Des artefacts signés, vérifiables hors ligne avec la clé qui vous est remise.
  • Une nomenclature CycloneDX des sources, générée et validée par version.
  • Le rapport des scénarios d’évasion joué sur la version livrée.
  • Un dossier OpenVEX versionné, où chaque exclusion porte un auteur, une date et une justification du référentiel CISA ; le retrait est refusé par le code du dépôt si la justification manque.
  • Des rapports de scan reproductibles : porte différentielle bloquante, scan complet hebdomadaire, secrets, images, dépendances après suppression VEX.
  • Une politique de divulgation publiée, avec un canal dédié.
  • Un contrôle « zéro symbole de sortie réseau » exécuté sur l’artefact construit, vérifiable par un tiers sans accès au code source.
  • Une méthode de preuve traçable : 286 identifiants SEC-*, statuts adossés à des preuves fichier:ligne, arbitrages datés avec leurs options écartées (posture de conformité).