BénéficesDoc CLIVersion 0.1Démarrer

Continuum Academy · Chapitre 04

Comprendre SLSA Build Provenance

Dans le chapitre précédent, nous avons ouvert une attestation in-toto. Nous savons lire subject, predicateType et predicate. Il reste une question : que met-on réellement dans le Predicate pour décrire comment un logiciel a été construit ?

Reprenons payment-service-4.7.2.tar.gz. Nous allons remplacer les trois champs pédagogiques du chapitre 03 par SLSA Build Provenance, en suivant la spécification approuvée SLSA v1.2. Notre objectif est de lire le récit structuré d’un build : ses entrées, sa plateforme, son exécution et sa sortie.

1 — SLSA ne remplace pas in-toto

Le Statement conserve _type, subject, predicateType et predicate. in-toto organise l’affirmation et son sujet ; SLSA définit ici le contenu d’une provenance de build. C’est une manière pédagogique de distinguer leurs rôles, pas une nouvelle définition normative de l’enveloppe. DSSE reste la couche séparée étudiée au chapitre 03.

Le predicateType devient exactement https://slsa.dev/provenance/v1. L’URI du type et l’URL de documentation sont différentes. La page https://slsa.dev/spec/v1.2/build-provenance explique le modèle ; son adresse ne doit pas remplacer l’URI dans le JSON. Le v1 du type reste donc correct avec la spécification SLSA v1.2.

2 — Notre Statement devient une provenance SLSA

Voici d’abord son ossature. Les objets vides indiquent les parties à compléter : cet extrait ne décrit pas encore un build exploitable. Le digest est fictif et abrégé.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{
    "name": "payment-service-4.7.2.tar.gz",
    "digest": { "sha256": "3a16...9f21" }
  }],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {},
    "runDetails": {}
  }
}

subject représente ce qui est sorti du build. Les deux parties du Predicate décrivent ce qui définit ce build et ce qui concerne son exécution. Nous n’ajoutons ni provenance au sommet du Statement, ni signature dans le Predicate.

3 — Les deux grandes moitiés

buildDefinition : quel build devait être exécuté, avec quelles entrées ? On y trouve le type de build, les paramètres et les dépendances résolues.

runDetails : quelle plateforme l’a exécuté, et que savons-nous de cette exécution particulière ? On y trouve le builder, les métadonnées et éventuellement des artefacts annexes.

Même Statement, un autre Predicate

Le sujet reste payment-service-4.7.2.tar.gz. Nous remplaçons le modèle inventé au chapitre 03 par la structure SLSA Build Provenance.

À gauche, le Predicate pédagogique du chapitre 03. À droite, le type SLSA et ses deux parties. Le sujet et le schéma du Statement sont communs.

Structure conservée · digest abrégé

_type
https://in-toto.io/Statement/v1
subject
payment-service-4.7.2.tar.gz
sha256: 3a16…9f21

03 · Predicate pédagogique, non standard

predicateType
https://example.com/attestation/build/v1
predicate
source · commit · builder

04 · SLSA Build Provenance

predicateType
https://slsa.dev/provenance/v1
predicate
buildDefinition

Quel build, avec quelles entrées ?

runDetails

Quelle plateforme, quelle exécution ?

Il ne suffit pas de changer l’URI : le Predicate doit respecter le modèle qu’elle désigne.

La définition permet de comprendre le travail à réaliser. Les détails d’exécution permettent de distinguer une invocation d’une autre. Deux exécutions pourraient partager la même définition tout en ayant des identifiants et des horaires différents. Cette séparation ne garantit pas que leurs sorties seront identiques : elle organise les informations nécessaires pour examiner la fabrication.

4 — buildType : de quel build parlons-nous ?

buildDefinition.buildType est une URI identifiant un modèle de build : comment interpréter ses paramètres, ses dépendances et les instructions associées. Ce n’est ni le nom du CI, ni une commande shell, ni le chemin d’un fichier YAML.

Pour notre exemple, utilisons https://example.com/buildtypes/payment-service/v1.

EXEMPLE PÉDAGOGIQUE — URI NON STANDARD. Nous définissons ce modèle fictif ainsi : prendre un dépôt et une référence, résoudre cette référence, puis exécuter release/build.yml dans la révision obtenue pour produire l’archive. Les champs repository et ref appartiennent à cette interface choisie pour le chapitre.

Dans un système réel, la définition du build type doit expliquer ces règles sans ambiguïté, y compris la signification des paramètres externes et internes. Une URI sans définition interprétable ne suffit pas au consommateur.

5 — externalParameters : ce que l’utilisateur peut influencer

{
  "externalParameters": {
    "repository": "https://git.example.org/payments/payment-service",
    "ref": "refs/heads/main"
  }
}

Cet extrait de buildDefinition décrit la demande adressée à la plateforme : construire depuis ce dépôt et cette référence. repository et ref ne sont pas des champs standards SLSA ; leur structure dépend de notre buildType. externalParameters désigne l’interface externe du build.

Ces valeurs viennent de ce que l’appelant peut influencer. Le modèle SLSA les considère comme non fiables par défaut et exige leur vérification par le consommateur. Celui-ci pourrait, par exemple, accepter uniquement ce dépôt et la branche de livraison prévue. Une valeur enregistrée fidèlement peut rester une valeur interdite par sa politique.

6 — resolvedDependencies : ce qui a réellement été résolu

La demande contient refs/heads/main. Cette référence est mouvante : le prochain commit peut déplacer la branche. Pour notre invocation, elle se résout vers le commit déjà rencontré dans les chapitres précédents.

{
  "resolvedDependencies": [{
    "uri": "git+https://git.example.org/payments/payment-service@refs/heads/main",
    "digest": {
      "gitCommit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16"
    }
  }]
}

Demander une branche, enregistrer un commit

La référence demandée et la révision résolue ont deux emplacements distincts. Exemple fictif : aucune résolution Git n’est exécutée dans cette page.

La demande conserve refs/heads/main. La dépendance consigne le commit résolu pour cette exécution, même si la branche avance ensuite.

REQUESTED · Demandé

buildDefinition.externalParameters

repository
https://git.example.org/payments/payment-service
ref
refs/heads/main

Cette référence peut désigner un autre commit demain.

Résolution

RESOLVED · Résolu

buildDefinition.resolvedDependencies

uri
git+https://git.example.org/payments/payment-service@refs/heads/main
digest.gitCommit
7d34a8b4e1f98c2040e9844ab754ae068943bb16

Cette valeur identifie la révision déclarée pour cette exécution.

Le commit reste celui des chapitres précédents. Il est fictif ; les deux panneaux ne représentent pas deux artefacts de sortie.

externalParameters conserve ce qui a été demandé. resolvedDependencies consigne ce qui a été résolu, lorsque cette information est connue. L’URI relie ici la dépendance à la demande ; digest.gitCommit identifie la révision déclarée. Retrouver main aujourd’hui ne permet pas de remplacer cette valeur enregistrée hier.

Ce champ est une liste de dépendances du build, pas un emplacement réservé à Git. Un compilateur téléchargé, une archive source ou d’autres composants nécessaires peuvent aussi y figurer. Notre exemple ne garde que le dépôt pour rester lisible. La liste n’est pas automatiquement exhaustive : une dépendance absente ne doit pas être supposée inexistante.

7 — internalParameters : ce que contrôle la plateforme

buildDefinition.internalParameters contient les paramètres sous le contrôle de l’entité représentée par builder.id. Leur schéma dépend, lui aussi, du build type. Ils peuvent aider au débogage, à la réponse à incident ou à la reproduction du build.

La distinction porte sur qui contrôle l’entrée. Un paramètre choisi par l’appelant ne devient pas interne parce qu’on le range dans ce champ. Dans le modèle SLSA, la confiance accordée aux paramètres internes dépend de celle accordée à la plateforme. Ce champ est facultatif ; notre exemple complet l’omet.

8 — runDetails.builder : qui a exécuté le build ?

{
  "runDetails": {
    "builder": {
      "id": "https://example.com/builders/payments-release/v1"
    }
  }
}

Cette URI pédagogique désigne notre plateforme de build et son périmètre de confiance. Elle ne désigne pas nécessairement une machine unique, un développeur ou une clé. Une plateforme peut comprendre plusieurs machines et les services qui orchestrent l’exécution et enregistrent la provenance.

BUILDER ≠ SIGNER

builder.id déclare quelle plateforme a réalisé le build. Le signataire authentifie l’attestation selon le mécanisme employé. Un même signataire peut couvrir plusieurs builders ; cela ne l’autorise pas à déclarer n’importe quelle plateforme. Le consommateur doit accepter un couple signataire–builder approprié, pas simplement deux noms rencontrés dans le document.

9 — metadata : cette exécution précise

runDetails.metadata peut contenir invocationId, startedOn et finishedOn. Le premier identifie une invocation particulière ; les deux autres enregistrent son début et sa fin. Deux builds lancés depuis le même commit peuvent ainsi avoir des métadonnées différentes.

Ces informations servent à retrouver des journaux, rapprocher des événements ou comprendre une durée. Leur présence ne certifie pas l’heure à elle seule : ce sont les horaires déclarés par la plateforme. Une signature du document ne les transforme pas en horodatage indépendant. Les garanties dépendent du système qui les produit et du modèle de confiance.

10 — byproducts : ce qui a été produit autour du build

runDetails.byproducts peut référencer des artefacts annexes utiles au débogage ou à une enquête : un journal ou une configuration évaluée, par exemple. SLSA n’impose pas cette liste d’exemples et ne demande pas de conserver tous les fichiers intermédiaires.

L’archive destinée au consommateur reste dans subject, avec les autres sorties principales éventuelles. Placer cette archive uniquement dans byproducts changerait le sens du document. Nous omettons ce champ facultatif dans le JSON suivant.

11 — Le build complet

Le Statement réunit maintenant les champs que nous venons de lire. La structure suit SLSA Build Provenance ; le build type, la plateforme et les valeurs sont fictifs. Le digest de sortie contient 64 caractères hexadécimaux, sans archive réelle associée. Ce document n’est pas une preuve d’exécution ni une attestation signée prête à vérifier.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "payment-service-4.7.2.tar.gz",
      "digest": {
        "sha256": "3a16000000000000000000000000000000000000000000000000000000009f21"
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://example.com/buildtypes/payment-service/v1",
      "externalParameters": {
        "repository": "https://git.example.org/payments/payment-service",
        "ref": "refs/heads/main"
      },
      "resolvedDependencies": [
        {
          "uri": "git+https://git.example.org/payments/payment-service@refs/heads/main",
          "digest": {
            "gitCommit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16"
          }
        }
      ]
    },
    "runDetails": {
      "builder": {
        "id": "https://example.com/builders/payments-release/v1"
      },
      "metadata": {
        "invocationId": "https://example.com/builds/payment-service/2026-09-18-001",
        "startedOn": "2026-09-18T08:15:00Z",
        "finishedOn": "2026-09-18T08:17:00Z"
      }
    }
  }
}

Relisez d’abord subject, puis predicate.buildDefinition et predicate.runDetails. Le dépôt demandé, le commit résolu et le builder ont chacun leur place. L’identifiant d’invocation et les dates décrivent cette exécution, sans modifier le modèle de build.

12 — Lecture de gauche à droite

Le schéma relie les valeurs du JSON plutôt que de les présenter comme une liste de propriétés. La demande fournit les paramètres ; la résolution fournit des dépendances. Le build type explique comment les employer. La plateforme exécute le build et l’archive devient son sujet de sortie.

Des entrées à l’archive livrée

Suivons une seule invocation. Les paramètres, le type de build et les dépendances appartiennent à buildDefinition ; la plateforme et les métadonnées appartiennent à runDetails.

Les entrées et le modèle définissent le build exécuté par la plateforme. Les dépendances résolues alimentent cette exécution, dont subject désigne la sortie.

predicate.buildDefinition

01 · Demande

externalParameters

https://git.example.org/payments/payment-service

refs/heads/main

02 · Dépendances résolues

resolvedDependencies

gitCommit

7d34a8b4e1f98c2040e9844ab754ae068943bb16

03 · Modèle de build

buildType

https://example.com/buildtypes/payment-service/v1

04 · Plateforme · Exécution

runDetails.builder.id

https://example.com/builders/payments-release/v1

05 · Sortie principale

subject

payment-service-4.7.2.tar.gz

sha256: 3a16…9f21

Lecture des relations déclarées, pas une preuve que l’exécution a eu lieu. URI de buildType et de builder pédagogiques ; digest de sortie fictif et abrégé.

Les dépendances résolues sont des entrées du build, y compris lorsqu’elles sont obtenues pendant son exécution. Le schéma ne les place pas après la sortie. Les métadonnées sont rattachées à l’invocation : elles n’ajoutent pas une étape de fabrication. Cette lecture simplifie les relations déclarées, sans imposer une chronologie universelle aux plateformes.

13 — Ce que peut vérifier un consommateur

Après les contrôles d’enveloppe du chapitre 03, le destinataire confronte la provenance à ses attentes :

Attente du consommateur Valeur à examiner
Le fichier reçu est le sujet déclaré Digest calculé du fichier ↔ subject.digest
La demande concerne le dépôt et la référence autorisés buildDefinition.externalParameters
Les sources et dépendances sont celles attendues buildDefinition.resolvedDependencies
Le modèle de build est pris en charge et accepté buildDefinition.buildType
La plateforme est autorisée pour ce signataire runDetails.builder.id et identité de signature vérifiée

SLSA fournit des informations et des exigences ; il n’exécute pas automatiquement ces comparaisons. L’outil et la politique du consommateur doivent les mettre en œuvre. Une provenance utile doit aussi correspondre à ce que le consommateur attend, au-delà d’un JSON valide. Le guide Verifying artifacts développe cette démarche.

14 — Une provenance peut être valide et inacceptable

Supposons une livraison distincte de notre JSON fictif : sa provenance est correctement structurée, sa signature est valide et le digest de l’archive correspond au sujet. Le consommateur attend toutefois le commit 7d34a8b…. La provenance déclare un autre commit.

Le document peut décrire fidèlement un build du mauvais code. Il faut refuser selon cette politique, sans prétendre que la signature est cassée. Le même raisonnement vaut pour une branche interdite ou une plateforme non autorisée pour le signataire vérifié.

CRYPTOGRAPHICALLY VALID ≠ POLICY ACCEPTABLE

Cette distinction permet de produire un résultat précis : « signature valide, artefact correspondant, révision non autorisée ». Elle indique aussi ce qu’il faut examiner pour expliquer le refus.

15 — Ce que SLSA Build Provenance ne prouve pas

Cette provenance ne garantit automatiquement ni l’absence de vulnérabilités ou de code malveillant, ni la sécurité des dépendances, ni la conformité réglementaire. Elle ne rend pas une plateforme compromise honnête et ne crée pas de confiance dans un builder inconnu.

L’assurance dépend du modèle de confiance, de la plateforme et des propriétés SLSA réellement satisfaites. Un récit plus détaillé fournit davantage de valeurs à contrôler ; il ne suffit pas, à lui seul, à renforcer la fiabilité du système qui les a enregistrées.

16 — SLSA Provenance ≠ SLSA Level

FORMAT ≠ ASSURANCE LEVEL

Produire un document au format SLSA Build Provenance n’attribue pas automatiquement un niveau SLSA. Le format organise des informations ; les niveaux portent sur les exigences satisfaites par le build et par la production de ces informations.

Il faut donc examiner les propriétés de la plateforme et du processus, pas chercher un label magique dans notre JSON. Les niveaux du Build Track constituent un sujet distinct que nous ne détaillons pas ici.

17 — Build provenance et Source provenance

SLSA v1.2 emploie provenance comme concept général et distingue notamment Build provenance et Source provenance. Ce chapitre concerne uniquement Build Provenance : relier les sorties d’un build au processus et aux entrées qui les ont produites.

Mentionner un commit dans resolvedDependencies ne transforme pas notre document en Source provenance. Nous identifions ici une entrée du build, sans décrire le processus de création et de gestion de cette révision source.

18 — Et Continuum Attest ?

Le CLI conserve un receipt natif puis le convertit via attest export --format in-toto en Statement avec Predicate SLSA et enveloppe DSSE. Le code actuel applique ce mapping précis :

  • buildType : https://alien6.com/attest/pipeline/v1 ; externalParameters.pipelineHash reçoit le hash du pipeline.
  • subject reprend le nom et output_hash de chaque étape, sous digest.blake3 ; resolvedDependencies reprend ses input_hash de la même manière.
  • builder.id devient https://alien6.com/attest@v suivi de la version du CLI. invocationId dérive du hash du contenu canonique du receipt ; la fin vient de son timestamp et le début est calculé en retranchant sa durée.

Ces empreintes sont celles des ensembles déclarés par étape : ce mapping ne fournit pas automatiquement notre couple dépôt/référence et notre commit Git. attest import --format in-toto vérifie le profil exporté, sa cohérence et sa signature avec le magasin de confiance local ; il ne recalcule pas l’artefact livré et n’applique pas notre politique de révision attendue. La référence CLI documente ces commandes.

19 — À retenir

subject              → ce qui a été produit
buildType            → le modèle de build
externalParameters   → l’interface externe et ses valeurs
resolvedDependencies → les dépendances effectivement résolues
builder.id           → la plateforme déclarée
runDetails           → cette exécution particulière

Nous savons maintenant lire une provenance de build. La prochaine étape consiste à produire et vérifier une preuve nous-mêmes : 05 — Produire sa première preuve avec Continuum Attest, à venir.