Aller au contenu principal

Journal de transparence de la provenance

fremforge signe une attestation de provenance pour chaque artefact construit sur la plateforme. Une signature prouve que l’attestation vient de nous. Elle ne prouve pas, à elle seule, que nous ne l’avons pas remplacée depuis par une autre : la même clé signerait la remplaçante de façon tout aussi convaincante.

Le journal de transparence comble cette lacune. Chaque attestation est inscrite dans un arbre de Merkle en ajout seul publié sur log.frem.sh, et le journal signe périodiquement un checkpoint : une déclaration de la taille de l’arbre et de son hachage racine à un instant donné. Comme l’arbre est en ajout seul et que chaque checkpoint doit étendre le précédent de façon prouvable, nous ne pouvons pas retirer ni modifier une attestation après coup sans produire un checkpoint qui contredise un checkpoint déjà publié.

Ce que contient le journal, et ce qu’il ne contient pas

Une feuille est un condensat, pas une attestation. Chaque entrée est le SHA-256 d’une enveloppe DSSE, et rien d’autre.

C’est délibéré, et c’est ce qui permet au journal d’être entièrement public. Si vous détenez votre propre enveloppe, vous pouvez prouver qu’elle figure dans le journal : hachez les octets, récupérez une preuve d’inclusion et vérifiez-la par rapport à un checkpoint. Quiconque ne détient pas votre enveloppe apprend seulement qu’une valeur de 32 octets a été journalisée à un certain index. Les noms de dépôts, les SHA de commits, les noms d’artefacts et les heures de build restent privés.

Publier les enveloppes elles-mêmes n’ajouterait rien à votre preuve, tout en rendant chaque build de chaque client énumérable pour toujours, dans une structure qui, par construction, ne peut jamais rien supprimer.

Ce qu’il prouve aujourd’hui, et ce qu’il ne prouve pas

Lisez ceci avant de vous y fier.

Un checkpoint porte la signature de fremforge et une cosignature de témoin.

Notre signature rend le journal inviolable de façon détectable : si vous détenez une preuve et que nous réécrivons ensuite l’historique, votre preuve cesse d’être vérifiable et vous pouvez démontrer la contradiction.

La cosignature du témoin est ce que notre propre signature ne pourra jamais être : un tiers qui refuse de cosigner deux checkpoints incohérents. Depuis le 16 septembre 2026, chaque checkpoint est cosigné par witness.stagemole.eu, exploité par Mullvad VPN AB. Il porte aussi un horodatage, pour lequel un checkpoint C2SP n’a pas de champ : sans lui, un checkpoint périmé mais valablement signé ne se distingue pas d’un checkpoint actuel.

Un seul témoin ne constitue pas un quorum, et nous préférons le dire plutôt que de laisser le mot « témoin » en faire plus qu’il ne peut. Un cosignataire unique augmente le coût de vous montrer un arbre différent de celui de tous les autres ; il ne le rend pas impossible, car il ne peut pas détecter une collusion entre le journal et son seul témoin. L’exploitant décrit par ailleurs cette instance comme une instance de préproduction plutôt que de production.

À savoir si vous évaluez l’indépendance : les seuls témoins de niveau production dans l’Union européenne (Glasklar Teknik, Mullvad VPN et Tillitis) ont une société mère commune, Amagicom AB, ce que leurs propres responsables publient. En ajouter d’autres apporterait de la disponibilité, pas de l’indépendance. Nous ne présenterons pas cet ensemble comme des parties indépendantes.

La racine de confiance publie la liste actuelle sous transparency_log.witnesses. fremforge attest-verify vérifie chaque entrée et indique le nombre qu’il a vérifié.

Une cosignature manquante fait désormais échouer la vérification.

Comme la racine de confiance déclare un témoin, attest-verify exige au moins une cosignature pour valider. Si le témoin est injoignable au moment où nous publions, le checkpoint part avec notre seule signature et votre vérification échoue : la vérification du témoin indique 0/1 verified.

C’est le comportement voulu : un témoin déclaré qui est silencieusement absent est précisément le cas dont vous voulez être averti. Mais cela signifie qu’une exécution en échec n’est pas automatiquement une preuve de falsification. Regardez quelle vérification a échoué avant d’en tirer une conclusion, et consultez Points de terminaison pour récupérer vous-même le checkpoint.

Points de terminaison

CheminContenu
https://log.frem.sh/checkpointLe checkpoint signé actuel
https://log.frem.sh/tile/<level>/<index>Les tuiles de l’arbre de Merkle
https://log.frem.sh/tile/entries/<index>Les lots d’entrées (les condensats journalisés)

Les formats sont les standards communautaires C2SP tlog-checkpoint, signed-note et tlog-tiles ; ce n’est pas une invention de fremforge, et les outils tiers existants savent donc les lire.

Vérifier que votre attestation est journalisée

Une seule commande, qui ne nécessite aucun compte fremforge :

fremforge attest-verify <asset>.intoto.jsonl --human
✓ envelope signatures        6/6 verify under fremforge-slsa-builder-v2
✓ checkpoint signature       signed by log.frem.sh
✓ tiles hash to the checkpoint root
✓ every attestation is in the log
                             6/6 at leaf indices: fremforge-linux-x64@255, …
✓ append-only since last run
✓ 7/7 checks passed (computed on this machine — the server was not asked
  for a verdict)

Elle se termine avec un code non nul si une vérification échoue : elle s’intègre donc telle quelle à la CI.

Un artefact tout juste publié peut ne pas figurer dans le journal pendant quelques minutes. Le journal ingère selon un cycle de quinze minutes : une attestation peut donc être signée et publiée avant d’apparaître dans l’arbre. Pendant cette fenêtre, attest-verify indiquera NOT LOGGED et se terminera avec un code non nul, à juste titre, puisqu’elle n’y est pas encore. Relancez un peu plus tard. Si l’artefact a plus d’environ quinze minutes et indique toujours NOT LOGGED, il s’agit d’un véritable échec, qui mérite de nous être signalé.

Chaque hachage et chaque signature sont recalculés sur votre machine. On ne nous demande que des octets : la racine de confiance, le checkpoint, les tuiles. Rien dans le résultat n’est un verdict que nous vous aurions fourni, et c’est tout l’intérêt : un outil de vérification qui demande à la partie vérifiée si tout va bien n’a rien vérifié.

Un .intoto.jsonl est au format JSON Lines : une enveloppe par artefact, de sorte qu’un lot de publication en contient une par binaire, plus le SBOM. Toutes sont vérifiées.

Conservez le fichier d’état

attest-verify enregistre l’arbre qu’il a vu dans ~/.config/fremforge/tlog-state.json (modifiable avec --state=<path>) et, lors de l’exécution suivante, vérifie que le nouvel arbre contient toujours celui-ci, inchangé. C’est la vérification qui ne nécessite aucun tiers, et c’est elle qui détecte une vue divergente qui vous viserait spécifiquement : exécutez-la donc plus d’une fois, depuis un endroit que vous conservez, et ne supprimez pas le fichier pour effacer un échec. Elle ne fait délibérément pas avancer l’arbre enregistré lorsque la vérification échoue, de sorte qu’une réécriture détectée reste détectée à chaque exécution suivante.

Ou faites-le à la main

La commande est une commodité, pas une dépendance. Si vous préférez ne pas faire confiance à notre binaire non plus, ce qui est une position raisonnable pour le seul outil dont le rôle est de se méfier de nous, la procédure ci-dessous n’utilise que curl, jq et sha256sum, et les formats sont des standards communautaires que les clients tlog-tiles tiers savent aussi lire.

Il vous faut votre enveloppe <asset>.intoto.jsonl, livrée avec l’artefact.

# 1. The log's public key, from the trust root.
curl -sSfL https://www.frem.sh/.well-known/slsa-trust-root.json -o trust-root.json
jq -r '.transparency_log.vkey' trust-root.json

La vkey a la forme log.frem.sh+<keyhash>+<base64 key>. Ne la découpez que sur les deux premiers + : le dernier champ est en base64 et contient lui-même des +.

# 2. The digest of YOUR envelope. This is the leaf content.
#
# ONE LINE AT A TIME, AND INCLUDING ITS TRAILING NEWLINE — that is the unit
# the log commits to. Hashing the bundle as a whole finds nothing, which
# looks exactly like "your attestation was never logged".
head -c $(( $(head -1 <asset>.intoto.jsonl | wc -c) )) <asset>.intoto.jsonl | sha256sum
# 3. The current checkpoint, and the entries the log has published.
curl -sSfL https://log.frem.sh/checkpoint

Un checkpoint se compose de l’origine, de la taille de l’arbre, du hachage racine en base64, d’une ligne vide, puis d’une ou plusieurs lignes de signature commençant par un tiret cadratin (—). Votre condensat apparaît dans l’un des lots d’entrées sous tile/entries/ ; sa position est son index de feuille, et les tuiles sous tile/<level>/ fournissent la preuve d’inclusion de cette feuille jusqu’au hachage racine sur lequel le checkpoint s’engage.

N’importe quel client tlog-tiles peut faire ce parcours pour vous. Si vous préférez ne pas en exécuter un, la propriété utile est plus simple et mérite tout de même d’être vérifiée : votre condensat doit apparaître, et la taille de l’arbre du checkpoint ne doit jamais faire que croître.

Vérifier que le journal n’a pas réécrit l’historique

fremforge attest-verify le fait pour vous d’une exécution à l’autre ; voir Conservez le fichier d’état ci-dessus. À la main :

Conservez tout checkpoint que vous avez vu. Plus tard, récupérez-en un plus récent et demandez au journal une preuve de cohérence entre les deux tailles. Si le nouvel arbre n’étend pas l’ancien, la preuve ne peut pas être produite, et un journal incapable de la produire a réécrit l’historique.

C’est la vérification qu’un témoin effectue en continu, et c’est pourquoi les témoins comptent davantage que toute vérification ponctuelle faite à la main.

Lien avec les autres mécanismes d’intégrité

fremforge exploite trois mécanismes distincts, faciles à confondre :

MécanismeCouvreOù
Chaîne d’auditLes actions d’administration sur votre organisationChaîne de hachage par client, ancrée en WORM
Signature de commits sans cléLes commits GitFulcio auto-hébergé + TSA RFC3161, sans Rekor
Ce journalLes attestations de provenance de buildlog.frem.sh

Ils sont indépendants. En particulier, la signature de commits sans clé n’utilise toujours aucun journal de transparence : sa primitive d’intégrité est l’horodatage RFC3161, comme le décrit la vue d’ensemble de la sécurité. Ce journal ne couvre que les attestations de provenance.

Souveraineté

Le journal fonctionne sur T Cloud Public dans eu-de, signé avec une clé Ed25519 détenue par fremforge. Ce n’est pas Rekor, et il ne dépend ni de sigstore.dev, ni d’aucun journal exploité aux États-Unis, ni d’un CDN public situé hors de l’UE.

Les témoins constituent une exception délibérée en devenir : la valeur d’un témoin tient à ce qu’il n’est pas nous, et un témoin peut donc tout à fait être exploité ailleurs. Un témoin ne voit que des checkpoints (une origine, une taille d’arbre et un hachage racine), jamais vos dépôts, vos artefacts ni vos builds. Tout témoin que nous ajouterons sera listé dans la racine de confiance, de sorte que l’ensemble est vérifiable plutôt que présumé.