Aller au contenu
Sécurité medium

Kusari : transformer les métadonnées supply chain en décisions

27 min de lecture

Vous avez investi dans votre supply chain : SBOM générés avec Syft, images signées avec Cosign, attestations SLSA produites par vos pipelines. Mais quand un développeur ouvre une pull request qui ajoute une dépendance risquée, qui décide si le merge est autorisé ? Souvent, personne, la PR est mergée avant même que la sécurité n'ait pu réagir.

C'est le problème que Kusari adresse : transformer vos métadonnées supply chain en décisions exploitables au moment où elles comptent, dans les pull requests, avant le merge.

Kusari est l'éditeur d'une plateforme de sécurité de la supply chain logicielle, connu de la communauté pour avoir créé GUAC. Son produit destiné aux équipes de développement s'appelle Kusari Inspector : il analyse le diff de chaque pull request et rend un avis go/no-go avant le merge.

Concrètement, Inspector :

  • Analyse le diff de la PR : secrets, code, dépendances, licences, configuration
  • Compare les résultats à un fichier kusari.yaml versionné avec le code
  • Commente la pull request, à la fois en synthèse et ligne par ligne
  • Conserve les dérogations approuvées dans un registre d'espace de travail

Inspector ne génère pas de SBOM et ne signe pas d'artefact : il intervient avant que l'artefact existe, sur le code proposé. Les briques du tableau ci-dessous ne sont donc pas des concurrentes mais des étapes différentes de la même chaîne. La colonne de droite est celle qui compte : c'est le livrable de chaque outil qui détermine où il se branche.

Architecture Kusari : couche de décision consommant SBOM, SLSA, scans et licences

OutilRôleCe qu'il produit
SyftGénère un SBOMListe des composants
CosignSigne et attestePreuve cryptographique
Trivy/GrypeScanne les vulnérabilitésListe de CVE
GUACCorrèle et visualiseGraphe interrogeable
Kusari InspectorAnalyse le diff et se prononceAvis go/no-go dans la PR

Le fossé entre production de métadonnées et action

Section intitulée « Le fossé entre production de métadonnées et action »

La plupart des équipes qui investissent dans la supply chain security rencontrent le même blocage :

  1. Elles génèrent des SBOM à chaque build
  2. Elles signent leurs images avec Cosign
  3. Elles produisent des attestations SLSA
  4. Elles scannent les vulnérabilités en CI
  5. Mais rien n'empêche une pull request dangereuse d'être mergée

Toutes ces preuves portent sur un artefact déjà construit. Or le moment où une dépendance douteuse entre dans le projet, c'est la revue de code, avant qu'aucun build n'ait eu lieu. Les rapports s'accumulent en aval pendant que la décision se prend en amont, sans eux.

Les protections de branche natives de GitHub savent exiger que les tests passent, qu'une revue soit approuvée et qu'il n'y ait pas de conflit. Ce sont des conditions sur le processus, pas sur le contenu du changement.

Elles ne savent pas répondre à :

  • « Cette PR ajoute une dépendance dont le nom ressemble à un paquet populaire. »
  • « Ce changement introduit une licence copyleft forte. »
  • « Ce workflow référence une action par tag mobile. »
  • « Cette chaîne ajoutée dans le code est un jeton d'API. »

Kusari Inspector comble ce fossé en analysant le contenu du diff et en publiant son verdict là où la discussion a lieu, sur la pull request.

Kusari Inspector : contrôles dans les pull requests

Section intitulée « Kusari Inspector : contrôles dans les pull requests »

Kusari Inspector est le produit principal de Kusari. Il existe sous quatre formes qui partagent le même moteur et le même fichier de configuration : application GitHub, workflow GitHub Actions, template GitLab et client en ligne de commande. Le choix ne change pas les résultats, seulement la manière de déclencher l'analyse.

Le tableau ci-dessous liste les familles de contrôles documentées par Kusari. Chacune correspond à un interrupteur du fichier kusari.yaml que vous committez à la racine du dépôt : vous pouvez désactiver une famille entière si elle fait doublon avec un outil déjà en place. Retenez surtout la colonne de droite : Inspector regarde le diff de la pull request, pas l'intégralité du dépôt, ce qui explique pourquoi les premières exécutions sont rapides même sur un gros monorepo.

CatégorieContrôles effectués
Secrets (secret_scan_enabled)Identifiants, jetons et clés privées exposés dans le diff
DépendancesVulnérabilités connues avec sévérité et probabilité d'exploitation, typosquatting de noms de paquets, dépendances directes et transitives
Licences (license_scan_enabled)Licences des dépendances, classées par type de copyleft
Code et configuration (code_scan_enabled, misconfig_scan_enabled, workflow_scan_enabled)Analyse statique du code, workflows GitHub Actions, Dockerfile, Terraform, Helm
Épinglage de version (container_version_pinning_check_enabled)Images de conteneur et actions GitHub référencées par tag mobile plutôt que par version figée

Quand un développeur ouvre une pull request, Kusari Inspector :

  1. Calcule le diff

    Compare la branche de la PR à sa branche de base et isole les fichiers modifiés, dont les manifestes de dépendances (package.json, go.mod, requirements.txt, Dockerfile).

  2. Analyse le diff

    Passe les changements dans ses moteurs de détection : secrets, analyse statique, dépendances (vulnérabilités et typosquatting), licences, configuration.

  3. Applique votre kusari.yaml

    Retire du verdict les paquets listés dans allowed_packages, ainsi que les familles de scanners que vous avez désactivées, puis les exceptions déjà approuvées sur l'espace de travail.

  4. Publie le verdict

    Ajoute un commentaire de synthèse sur la PR, plus des commentaires posés directement sur les lignes de code concernées. Une nouvelle exécution remplace le commentaire précédent au lieu d'en empiler un de plus.

Le commentaire de la PR n'est pas une liste brute de findings : il est trié par gravité et chaque entrée porte la remédiation attendue. Le réflexe de lecture est donc de partir du bas de la ligne, la recommandation, plutôt que de l'identifiant de vulnérabilité. L'extrait ci-dessous montre la forme du rapport, avec un exemple réel de dépendance JavaScript vulnérable.

### Kusari Inspector
Status: changes requested (3 findings)
- Dependency: lodash 4.17.20 -> CVE-2021-23337 (CVSS 7.2, High)
Command injection via the template function. Fixed in 4.17.21.
Recommendation: bump to 4.17.21 before merge.
- License: dependency added under AGPL-3.0 (strong copyleft)
Recommendation: confirm compatibility with the distribution model.
- Workflow: .github/workflows/release.yml uses actions/checkout@v4
Recommendation: pin the action to a full commit SHA.
  • Un dépôt GitHub ou GitLab sur lequel vous pouvez installer une application, ou un accès administrateur au projet.
  • Un compte Kusari : l'analyse tourne sur la plateforme Kusari, pas sur votre machine. Les projets CNCF et OpenSSF y ont accès gratuitement.
  • Pour l'usage en ligne de commande : un couple KUSARI_CLIENT_ID et KUSARI_CLIENT_SECRET stocké dans les secrets du dépôt.

L'application GitHub est la voie la plus courte : elle commente les pull requests sans qu'aucun workflow ne soit ajouté au dépôt. Le comportement se règle ensuite par un fichier kusari.yaml committé à la racine, ce qui rend la configuration revue en pull request comme le reste du code.

  1. Installer l'application GitHub

    Depuis la console Kusari, autorisez l'application sur l'organisation puis sélectionnez les dépôts concernés. L'accès gratuit se demande via le programme OpenSSF / CNCF pour les projets éligibles.

  2. Générer le fichier de configuration

    La commande ci-dessous écrit un kusari.yaml aux valeurs par défaut dans le répertoire courant. Partir de ce fichier évite d'inventer des clés que le produit ne lit pas.

    Fenêtre de terminal
    kusari config generate
    cat kusari.yaml

    Sortie obtenue avec la version 2.9.0 du client :

    github_action_version_pinning_check_enabled: true
    container_version_pinning_check_enabled: true
    post_comment_on_failure: true
    post_comment_on_success: false
    sbom_generation_enabled: false
  3. Ajuster les scanners et les exemptions

    Les interrupteurs de scanners valent true par défaut : vous ne les écrivez que pour en désactiver un. allowed_packages attend des PURL (identifiants de paquet normalisés), avec ou sans version.

    # kusari.yaml, à la racine du dépôt
    secret_scan_enabled: true
    code_scan_enabled: true
    misconfig_scan_enabled: false # déjà couvert par Checkov en CI
    workflow_scan_enabled: true
    license_scan_enabled: true
    # Paquets exclus de l'analyse de dépendances
    allowed_packages:
    - pkg:npm/lodash # toutes les versions de lodash
    - pkg:npm/express@4.18.0 # cette version précise uniquement
    # Épinglage
    github_action_version_pinning_check_enabled: true
    container_version_pinning_check_enabled: true
    # Exceptions : false = un administrateur valide chaque proposition
    auto_approve_exceptions: false
    # Commentaires GitHub
    post_comment_on_failure: true
    post_comment_on_success: false
  4. Rendre le résultat bloquant

    L'application publie un statut sur la pull request. Pour qu'il empêche réellement le merge, exigez-le dans les règles de protection de branche : Settings, Branches, Branch protection rules, Require status checks.

Le client kusari sert à deux choses : rejouer une analyse en local avant d'ouvrir la PR, et intégrer Inspector dans une CI qui n'est ni GitHub ni GitLab. Aucun script d'installation n'est à exécuter à l'aveugle : le projet publie des archives et un fichier de sommes qu'on vérifie avant d'extraire.

Fenêtre de terminal
VERSION=2.9.0
BASE="https://github.com/kusaridev/kusari-cli/releases/download/v${VERSION}"
# Archive + fichier de sommes publié par le projet
curl -sSfLO "${BASE}/kusari-cli_${VERSION}_linux_amd64.tar.gz"
curl -sSfLO "${BASE}/kusari-cli_${VERSION}_checksums.txt"
# Refuser d'installer si l'empreinte ne correspond pas
sha256sum --check --ignore-missing "kusari-cli_${VERSION}_checksums.txt"
tar -xzf "kusari-cli_${VERSION}_linux_amd64.tar.gz" kusari
sudo install -o root -g root -m 0755 kusari /usr/local/bin/kusari

La vérification doit afficher kusari-cli_2.9.0_linux_amd64.tar.gz: OK. Tout autre message signifie que l'archive est corrompue ou substituée : arrêtez-vous là. Contrôlez ensuite la version installée, elle doit correspondre à celle que vous avez téléchargée.

Fenêtre de terminal
kusari --version
# kusari version v2.9.0 (commit: ..., built at: ...)

L'analyse compare le répertoire de travail à une révision Git de référence : les deux arguments sont positionnels et obligatoires. Le second peut être une branche, un tag ou un hash.

Fenêtre de terminal
# S'authentifier (ouvre le navigateur, puis choix de l'espace de travail)
kusari auth login
# Comparer le dépôt courant à la branche main
kusari repo scan . main
# Sortie SARIF, exploitable par l'onglet Security de GitHub
kusari repo scan . main --output-format sarif > kusari.sarif
# Résultat complet plutôt que tronqué, et trace détaillée en cas de souci
kusari repo scan . main --full-output --verbose

Quand l'application GitHub ne convient pas, par exemple parce que vous voulez maîtriser le déclencheur ou récupérer le SARIF en artefact, Kusari publie un workflow réutilisable. Il ne s'appelle pas au niveau d'une étape mais au niveau du job (jobs.<id>.uses), ce qui explique l'absence de runs-on dans l'extrait. Épinglez-le par empreinte de commit : le tag mobile v1 peut être redéplacé sur un autre commit à tout moment.

.github/workflows/kusari-scan.yml
name: Kusari Security Scan
on:
pull_request:
branches: [main]
permissions: {}
jobs:
kusari-scan:
uses: kusaridev/kusari-ci-templates/.github/workflows/kusari-scan-v1.yml@e43cf4ff8e08218bdb4a3cfa830316e5432d68c5 # v1.5.1
permissions:
contents: read
pull-requests: write
secrets:
KUSARI_CLIENT_ID: ${{ secrets.KUSARI_CLIENT_ID }}
KUSARI_CLIENT_SECRET: ${{ secrets.KUSARI_CLIENT_SECRET }}
with:
fail_on_issues: true # false par défaut : le job passe même en cas de finding
post_comment: true

Le point à retenir est fail_on_issues. Sa valeur par défaut est false : sans ce réglage, le workflow réutilisable réussit toujours et vous n'avez qu'un commentaire informatif, jamais un blocage. Passez-le à true seulement une fois le bruit calibré, sinon vous entraînez l'équipe à contourner le contrôle.

GUAC (Graph for Understanding Artifact Composition) est né chez Kusari : c'est le composant qui stocke et corrèle les métadonnées, là où Inspector travaille sur le diff d'une pull request. Les deux ne se configurent pas au même endroit. Pour alimenter la plateforme avec un SBOM produit par Syft, on utilise l'action d'ingestion, distincte du scan de PR.

.github/workflows/ingest.yml
- name: Générer le SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
format: spdx-json
output-file: sbom.spdx.json
- name: Ingérer le SBOM dans la plateforme Kusari
uses: kusaridev/kusari-ingest@f84027b8069ff1561403db4046c2319fcb0f6d52 # v4.8.0
with:
file-path: ./sbom.spdx.json
tenant-endpoint: ${{ vars.KUSARI_TENANT_ENDPOINT }}
client-id: ${{ secrets.KUSARI_CLIENT_ID }}
client-secret: ${{ secrets.KUSARI_CLIENT_SECRET }}

tenant-endpoint est propre à votre organisation, de la forme https://<identifiant-tenant>.api.us.kusari.cloud. Ne le codez pas en dur dans le workflow : une variable de dépôt évite de repasser sur chaque fichier le jour où le tenant change.

Une question revient dès qu'un cluster Kubernetes entre dans l'équation : faut-il choisir entre Inspector et Kyverno ? Non, parce qu'ils n'agissent pas au même moment. Inspector se prononce avant le merge, sur du code que personne n'a encore construit ; Kyverno se prononce à l'admission, sur un manifeste qui demande à tourner. Une image peut très bien passer la revue de PR puis être refusée au déploiement parce que sa signature manque.

Kusari n'exporte pas ses règles vers Kyverno : les deux configurations se maintiennent séparément. Le point de jonction pratique reste l'inventaire des images autorisées, à garder cohérent entre le dépôt applicatif et les ClusterPolicy du cluster.

Ce tableau n'existe pas pour désigner un gagnant : les quatre outils répondent à des questions différentes. La ligne qui décide vraiment est la première, Focus principal. Si votre douleur est « je découvre les CVE trop tard », Dependabot suffit. Si c'est « personne ne relit la licence ni les workflows des PR », Inspector apporte quelque chose que les trois autres ne couvrent pas ensemble. Les lignes marquées d'un avertissement signalent une capacité indirecte, obtenue via un autre composant, pas une case à cocher.

CritèreKusariGUACDependabotSnyk
Focus principalAnalyse du diff de PR, avis go/no-goVisualisation grapheMises à jour de dépendancesScan de vulnérabilités
Intégration PR✅ Native⚠️ Via intégration✅ Native✅ Native
Consomme SBOM⚠️ Via ingestion plateforme⚠️ Partiel
Consomme SLSA⚠️ Via ingestion plateforme
Policies personnalisées⚠️ Interrupteurs et liste d'exemptions⚠️ Via requêtes✅ Payant
Analyse licences
Analyse de configuration CI✅ Workflows, Dockerfile, Terraform, Helm
Open source⚠️ Client open source, plateforme SaaS

Problème : une pull request ajoute reqeusts au lieu de requests. Le nom est plausible à la relecture humaine, le test passe parce que le paquet malveillant expose la même interface.

Ce qui joue : la détection de typosquatting de l'analyse de dépendances, active par défaut. Aucune configuration n'est nécessaire, il faut seulement ne pas avoir désactivé le scanner.

Résultat : le commentaire de PR pointe la ligne du manifeste et signale la proximité avec un paquet légitime beaucoup plus téléchargé. C'est le cas où Inspector rend le service que ni Dependabot ni un scanner de CVE ne rendent : le paquet est nouveau, il n'a donc aucune vulnérabilité connue.

Problème : une dépendance transitive bascule vers l'AGPL-3.0, incompatible avec la distribution de votre produit. Personne ne relit les licences des dépendances indirectes.

Ce qui joue : le scanner de licences, qui classe les dépendances par type de copyleft.

kusari.yaml
license_scan_enabled: true

Résultat : le commentaire de PR nomme la dépendance et la catégorie de licence. La décision reste humaine : Inspector ne connaît pas votre modèle de distribution, il fournit le signal que personne n'allait chercher.

3. Faire respecter l'épinglage dans les workflows

Section intitulée « 3. Faire respecter l'épinglage dans les workflows »

Problème : un contributeur ajoute uses: actions/checkout@v4. Le tag est mobile, un compromis du dépôt de l'action donnerait à ce workflow un accès au GITHUB_TOKEN du projet.

Ce qui joue : github_action_version_pinning_check_enabled et container_version_pinning_check_enabled, tous deux à true par défaut.

kusari.yaml
github_action_version_pinning_check_enabled: true
container_version_pinning_check_enabled: true

Résultat : un commentaire est posé sur la ligne fautive, en demandant une empreinte de commit complète. Ce contrôle est le plus rentable des trois parce qu'il porte sur une règle objective, sans discussion possible et sans faux positif.

Le blocage se règle au niveau du déclencheur, pas dans kusari.yaml : le fichier de configuration pilote les scanners, pas la sévérité. Ce qui décide de l'échec, c'est fail_on_issues dans le workflow réutilisable, ou l'inscription du statut dans les protections de branche. Les trois étapes ci-dessous correspondent donc à trois réglages distincts, pas à trois valeurs d'une même clé.

  1. Observer sans bloquer

    Laissez fail_on_issues: false et n'ajoutez pas le statut aux protections de branche. Comptez deux à quatre semaines pour mesurer le volume réel de findings sur vos PR habituelles.

  2. Réduire le bruit

    Désactivez les scanners qui font doublon avec un outil déjà en place, et listez dans allowed_packages les paquets internes que l'analyse ne sait pas résoudre. Une équipe qui reçoit trois faux positifs par PR cesse de lire les commentaires.

  3. Rendre le contrôle bloquant

    Passez fail_on_issues: true, puis exigez le statut dans les protections de branche. À ce stade seulement, un finding empêche le merge.

Une exception ne se déclare pas dans un fichier YAML : elle se demande depuis la pull request, ce qui garde la justification à côté du contexte qui l'a motivée. Un membre de l'équipe répond au commentaire d'Inspector avec @kusari-inspector feedback suivi de son argumentaire ; Kusari classe la réponse et crée une proposition en attente.

@kusari-inspector feedback
Faux positif : cette chaîne est un identifiant de test, pas un secret.
Elle est générée à chaque exécution par le script de fixtures.

La proposition apparaît ensuite dans l'onglet Exceptions de l'espace de travail, où un administrateur doit l'approuver pour qu'elle prenne effet. Gardez auto_approve_exceptions: false : avec true, les propositions deviennent des exceptions actives sans relecture, et la personne qui écrit le code devient celle qui accorde la dérogation.

Le rapport d'Inspector n'est pas une preuve d'audit en soi : il vit sur une pull request qui sera fermée. Pour le CRA ou NIS2, ce qui est demandé est la trace durable des décisions, en particulier des dérogations. Deux points d'ancrage existent dans le produit.

  • Le SARIF produit par --output-format sarif s'archive en artefact de workflow, se relit hors ligne et s'ingère dans l'onglet Security de GitHub.
  • L'onglet Exceptions de l'espace de travail conserve qui a demandé une dérogation, sur quel motif, et qui l'a approuvée. C'est ce registre qu'un auditeur demandera, pas les commentaires de PR.

Revoyez ce registre à intervalle fixe. Une exception créée pour une migration « provisoire » survit à l'équipe qui l'a demandée si personne ne la relit.

Deux causes expliquent la majorité des tickets, et aucune n'est un défaut de l'outil : les permissions du workflow appelant et l'état du checkout. Le workflow réutilisable a besoin de pull-requests: write pour commenter, et d'un historique Git complet pour calculer un diff. Lisez la colonne du milieu avant de toucher à la configuration des scanners.

SymptômeCause probableSolution
Aucun commentaire sur la PRPermission pull-requests: write absente du job appelantDéclarer les permissions au niveau du job uses:, elles ne sont pas héritées
Le job échoue au calcul du diffCheckout superficiel, la branche de base est absenteLe workflow réutilisable fait déjà fetch-depth: 0 ; en CLI, récupérer la révision de comparaison avant de lancer le scan
Branche rapportée incorrecteHEAD détaché en CIPasser --override-branch à kusari repo scan
Findings sur un paquet internePaquet non résolvable par l'analyseAjouter son PURL dans allowed_packages
Le job passe malgré des findingsfail_on_issues laissé à falseLe passer à true une fois le bruit calibré

Quand une exécution se comporte autrement qu'attendu, reproduisez-la en local avant de modifier le workflow : le client accepte les mêmes arguments que la CI, et la boucle de retour se compte en secondes au lieu d'un cycle de pipeline. --verbose est un drapeau global, il se place avant ou après la sous-commande sans changer de sens.

Fenêtre de terminal
# Trace détaillée des appels à la plateforme
kusari repo scan . main --verbose
# Résultat complet plutôt que tronqué, archivé en SARIF
kusari repo scan . main --full-output --output-format sarif > kusari.sarif
# Régénérer une configuration propre en écrasant l'existante
kusari config generate --force

Si kusari repo scan échoue immédiatement sans produire de rapport, le problème est presque toujours l'authentification : rejouez kusari auth login et vérifiez l'espace de travail sélectionné avant de suspecter le scanner.

  1. Inspector analyse le diff de la pull request, il ne se contente pas de relire les rapports d'autres outils : secrets, code, dépendances, licences et configuration passent par ses propres moteurs

  2. Le moment compte plus que la couverture, un finding posé avant le merge change le comportement de l'équipe, un rapport publié après ne le change pas

  3. Un seul fichier de configuration, kusari.yaml à la racine du dépôt, composé d'interrupteurs de scanners et d'une liste d'exemptions par PURL

  4. Rien ne bloque par défaut, fail_on_issues vaut false et le statut doit être ajouté aux protections de branche pour empêcher un merge

  5. Les exceptions se demandent depuis la PR avec @kusari-inspector feedback, puis un administrateur les approuve : gardez auto_approve_exceptions: false

  6. La preuve auditable est ailleurs que dans la PR, archivez le SARIF et revoyez périodiquement le registre des exceptions pour le CRA et NIS2

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