SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-21

Wat bewijzen reproduceerbare builds echt?

Een checksum bevestigt enkel de integriteit van een bestand. Een reproduceerbare build bewijst dat het binaire bestand exact overeenkomt met de broncode. Leer het verschil.

Wat reproduceerbare builds bewijzen

Reproduceerbare builds bewijzen één specifiek feit: het binaire bestand dat u heeft ontvangen, is het bestand dat exact deze broncode oplevert. Iedereen kan dezelfde broncode nemen, deze opnieuw compileren en de bytes vergelijken. Verificatie is hiermee niet langer iets dat alleen de uitgever kan uitvoeren.

Het Reproducible Builds-project definieert het als volgt: "Een build is reproduceerbaar als bij dezelfde broncode, build-omgeving en build-instructies, elke partij bit-voor-bit identieke kopieën van alle gespecificeerde artefacten kan recreëren." De vergelijking zelf is een hash. De volledige uitdaging ligt in het zodanig vastleggen van de omgeving en de instructies dat twee verschillende machines tot hetzelfde resultaat komen.

Waarom een checksum deze vraag niet beantwoordt

Een gepubliceerde checksum bewijst dat het bestand zonder corruptie is aangekomen. Een handtekening over die checksum bewijst dat het afkomstig is van de persoon die de sleutel bezit. Geen van beide zegt iets over wat er gebeurde voordat het artefact bestond. Als de build-machine van de uitgever is gecompromitteerd, wordt het kwaadaardige binaire bestand op precies dezelfde manier van een checksum en handtekening voorzien als een schoon exemplaar, waardoor elke controle verderop in de keten slaagt. Als een beheerder bouwt vanuit een working tree die nooit is gepusht, geldt hetzelfde.

Dat is de kloof. U kunt de broncode lezen, de handtekening verifiëren, de checksum verifiëren en nog steeds code uitvoeren die nooit in de repository is verschenen. Reproduceerbaarheid is daarom een ander onderwerp dan het verifiëren van een download aan de hand van de gepubliceerde checksum. De checksum beschermt de overdracht. De herbouw beschermt alles wat er vóór de overdracht is gebeurd.

De aanval is niet theoretisch. Het SolarWinds Orion-lek uit 2020 bevond zich precies in die positie: het build-systeem leverde ondertekende artefacten af die niet overeenkwamen met de broncode die door iemand was gecontroleerd. Elke handtekeningcontrole slaagde, omdat handtekeningen beginnen bij het artefact.

Wat een reproduceerbare build niet bewijst

Dit is het aspect dat vaak wordt overschat, dus wees nauwkeurig over de beperkingen.

  • Het zegt niets over de veiligheid van de broncode. Een backdoor die openlijk wordt gecommit, bouwt reproduceerbaar op, en elke herbouwer bevestigt dit omdat zij allemaal dezelfde kwaadaardige broncode hebben gebruikt. Reproduceerbaarheid verplaatst het doel naar de broncodeboom. Iemand moet die boom nog steeds controleren; daarom blijft het beoordelingsbeleid, inclusief beleid voor door AI ondersteunde code in open-sourceprojecten, een afzonderlijke beheersmaatregel.
  • Het zegt niets over de veiligheid van uw inputs. Afhankelijkheden maken deel uit van wat u bouwt. Een kwaadaardig pakket dat tijdens het build-proces wordt opgehaald, wordt in het artefact gecompileerd, en elke herbouwer die dezelfde afhankelijkheid ophaalt, komt tot dezelfde conclusie. Dat is hoe een npm supply chain-aanval een server bereikt, en een reproduceerbare build reproduceert dit getrouw.
  • Het zegt niets over de integriteit van de toolchain. Als de compiler is gecompromitteerd, produceert elke herbouwer die die compiler gebruikt dezelfde gecompromitteerde output, en de oordelen komen allemaal overeen. Reproduceerbaarheid verhoogt de kosten van een dergelijke aanval, maar detecteert deze niet.
  • Het zegt niets over kwetsbaarheden. Een verouderde bibliotheek die bit-voor-bit wordt gereproduceerd, blijft een verouderde bibliotheek met bekende gebreken. Blijf daarom uw server controleren op bekende CVE's volgens uw eigen planning.

Wat reproduceerbaarheid wegneemt, is één specifieke aanvalspositie: de build-machine en het volledige pad van broncode naar binair bestand. Zolang een pakket niet reproduceerbaar is, kan niemand buiten de uitgever dat pad überhaupt inspecteren.

Waarom dezelfde broncode verschillende bytes oplevert

De meeste software is standaard niet reproduceerbaar, en de oorzaken zijn triviaal. Compilers en archiefformaten leggen gegevens vast over de machine waarop ze zijn uitgevoerd.

  • Een tijdstempel. De formaten tar, ar en zip slaan de wijzigingstijd van bestanden op, waardoor een build op een ander tijdstip het uitvoerbestand verandert.
  • Een pad. Debug-informatie legt het absolute build-pad vast, waardoor een build in /home/alice/src en een build in /build/pkg van elkaar verschillen, zelfs als de code identiek is.
  • Een volgorde. Het uitlezen van een map retourneert items in de volgorde van het bestandssysteem, waardoor de volgorde van de link-opdrachten of de archiefonderdelen per machine verschilt.
  • Een identiteit. Build-scripts sluiten de gebruikersnaam, de hostnaam of de locale in van degene die de build uitvoerde.
  • Een beslissing tijdens de build. Het detecteren van CPU-functies of het genereren van willekeurige waarden zorgt ervoor dat de uitvoer afhangt van de machine in plaats van de broncode.

U kunt de eerste oorzaak in ongeveer tien seconden zien gebeuren:

mkdir -p /tmp/rb && cd /tmp/rb
echo hello > a.txt && tar cf one.tar a.txt
sleep 2
echo hello > a.txt && tar cf two.tar a.txt
sha256sum one.tar two.tar

De twee hashes verschillen omdat de tar-header de wijzigingstijd van a.txt opslaat, en het herschrijven van het bestand heeft die tijd twee seconden vooruit gezet. De inhoud is byte voor byte identiek. Het vastzetten van de metadata lost dit op:

export SOURCE_DATE_EPOCH=1700000000
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf three.tar a.txt
touch a.txt
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf four.tar a.txt
sha256sum three.tar four.tar

Nu komen de hashes overeen, omdat geen enkel veld in de archief-header afkomstig is van de huidige status van de machine. --sort=name corrigeert de volgorde, --mtime corrigeert de klok, en de eigendoms-vlaggen voorkomen dat uw gebruikers-ID wordt vastgelegd.

Het verschil inzien met diffoscope

Wanneer twee builds van elkaar verschillen, vertelt sha256sum u enkel dát ze verschillen en niets meer. diffoscope bestaat om u te vertellen waarom, in een voor mensen leesbare vorm. Het pakt beide kanten recursief uit, zet binaire formaten om naar tekst en vergelijkt de tekst. Het ondersteunt Debian-pakketten, ELF-binaries, tar- en ZIP-archieven, PDF's, SQLite-databases en meer dan honderd andere formaten.

sudo apt install -y diffoscope
diffoscope one.tar two.tar

Voor het bovenstaande tar-paar is het rapport kort. In ingekorte vorm ziet het er als volgt uit:

--- one.tar
+++ two.tar
├── file list
│ @@ -1 +1 @@
│ -rw-r--r-- 0/0  6 2026-08-18 10:14:02.000000 a.txt
│ +rw-r--r-- 0/0  6 2026-08-18 10:14:04.000000 a.txt

Dat is de volledige diagnose: dezelfde grootte, hetzelfde pad, dezelfde rechten, andere wijzigingstijd. Een echt pakket produceert een veel langer rapport, dus schrijf het weg en open het in een browser:

diffoscope --html report.html build1.changes build2.changes

diffoscope geeft exitcode 0 als de invoer identiek is, 1 als ze verschillen en 2 bij problemen; het is dus direct in een CI-job te gebruiken zonder wrapper-script. Installeer op een kleine VPS diffoscope-minimal in plaats van diffoscope: het volledige pakket haalt een grote set aan formaat-helpers binnen die u waarschijnlijk nooit zult gebruiken.

Wat SOURCE_DATE_EPOCH oplost en waar de beperkingen liggen

SOURCE_DATE_EPOCH is een omgevingsvariabele die één getal bevat: de tijdstempel van de laatste wijziging van de broncode, uitgedrukt in seconden sinds 1 januari 1970 UTC. Een build-tool die deze variabele ondersteunt, gebruikt dit getal op elke plek waar het anders de huidige tijd bij het besturingssysteem zou opvragen. Stel deze in vanuit versiebeheer, zodat de waarde de broncode volgt in plaats van het build-moment:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

In een Debian-pakket exporteert debhelper deze waarde automatisch vanuit het changelog-bestand. Handmatige instelling in debian/rules ziet er als volgt uit:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)

Ondersteuning is per tool geregeld, nooit globaal. cmake 3.8 en nieuwer, gcc 7 en nieuwer, rpm vanaf 4.13 en Docker buildx 0.10 en nieuwer lezen deze variabele. Uw eigen scripts doen dit niet, tenzij u ze daarvoor programmeert. Als een script date aanroept, geef de variabele dan mee:

BUILD_DATE="$(date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"

Eén regel is van belang bij de implementatie. Als de variabele al is ingesteld, is die waarde de huidige tijd voor uw build-proces; overschrijf daarom nooit de waarde die door de aanroeper is meegegeven.

Container-images kennen hetzelfde probleem in een andere vorm. Docker buildx 0.10 en nieuwer geeft SOURCE_DATE_EPOCH vanuit uw shell door aan de build als build-argument. Tijdstempels van bestanden binnen de lagen moeten door de exporter worden herschreven; BuildKit heeft dit toegevoegd in versie 0.13. De gedocumenteerde methode pusht het resultaat naar een registry:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
docker buildx build --output type=image,name=registry.example.com/app:1.0,push=true,rewrite-timestamp=true .

Uw eigen build testen met reprotest

reprotest bouwt dezelfde broncode tweemaal en wijzigt de omgeving tussen de twee builds opzettelijk, waarna de resultaten worden vergeleken. De variaties vormen de kern van dit proces. Standaard varieert het build-pad, de tijd, de tijdzone, de locale, de umask, de hostname, de gebruiker en groep, het aantal CPU's, de home-directory en de bestandsvolgorde.

sudo apt install -y reprotest
reprotest . -- null

Alles na -- selecteert de backend voor de build-omgeving, waarbij null staat voor het systeem waarop u werkt. Voeg -vv -d toe om de tijdelijke mappen te behouden voor inspectie, zoals in reprotest . -vv -- null -d. Gebruik reprotest auto -- null om het programma zelf te laten bepalen met wat voor soort broncode-structuur het te maken heeft.

Sommige variaties vereisen privileges of extra pakketten; deze falen met een duidelijke melding wanneer ze niet kunnen worden uitgevoerd. Schakel deze liever uit dan het gehele proces als root uit te voeren:

reprotest --vary=-user_group,-domain_host,-fileordering auto -- null

Alles wat reprotest rapporteert, is iets dat een rebuilder later publiekelijk zou hebben gerapporteerd, onder vermelding van de naam van uw project.

Wat een rebuilder-oordeel betekent

Een rebuilder is een machine die niet van de uitgever is. Deze neemt de gepubliceerde broncode en de vastgelegde build-omgeving, bouwt het pakket opnieuw en vergelijkt de eigen output met het artefact in het archief. Het oordeel is alleen waardevol omdat de machine onafhankelijk is.

Debian legt de omgeving vast in een .buildinfo-bestand dat dpkg-buildpackage naast het .deb schrijft. De velden zijn het meest interessante onderdeel. Installed-Build-Depends somt elk geïnstalleerd pakket op dat de build kan beïnvloeden, inclusief exacte versies. Build-Path legt vast waar de build is uitgevoerd. Environment registreert de omgevingsvariabelen waarvan bekend is dat ze relevant zijn. Checksums-Sha256 beslaat de outputs. Dat bestand is het recept voor een tweede poging:

sudo apt install -y devscripts mmdebstrap
debrebuild --buildresult=./artifacts --builder=mmdebstrap hello_2.10-2_amd64.buildinfo

debrebuild leest het buildinfo-bestand en haalt de exacte afhankelijkheidsversies die erin staan op van snapshot.debian.org, zodat een rebuild vandaag de pakketversies kan gebruiken die bestonden op de dag van de oorspronkelijke build. De mmdebstrap-builder heeft geen chroot-configuratie en geen superuser-rechten nodig. Vergelijk de artefacten die deze produceert met de kopie uit het archief met behulp van diffoscope.

Arch Linux draait rebuilderd, dat dit continu uitvoert en de oordelen publiceert:

rebuildctl -H https://reproducible.archlinux.org pkgs ls --name rebuilderd

De statussen zijn GOOD, BAD en UNKWN, en de letterlijke interpretatie van elk is op een andere manier onjuist. GOOD betekent dat een onafhankelijke partij dezelfde bytes heeft verkregen; dit is een sterke uitspraak over de build, maar zegt niets over de broncode. BAD is bijna nooit een aanval: de gebruikelijke oorzaak is een tijdstempel of een pad dat in het pakket niet is vastgezet, en daarom kan rebuilderd een diffoscope-rapport aan de foutmelding koppelen. UNKWN betekent dat niemand het heeft getest, en een niet-getest pakket is geen geslaagd pakket.

De operationele regel is daarom kort. Een BAD-oordeel is een reden om het rapport te lezen. Als het rapport tijdstempels, build-paden of de volgorde van leden toont, dient u een bug in het pakketbeheer in. Als het rapport andere uitvoerbare code toont zonder dergelijke verklaring, stop dan met het uitrollen van die build en escaleer het probleem.

Hoe reproduceerbaar is Debian op dit moment?

ChartDebian packages reproducible in the test framework, amd64
The data behind this chart
[
  {
    "label": "unstable",
    "percent_reproducible": 94.2,
    "tested_count": "41,163"
  },
  {
    "label": "forky",
    "percent_reproducible": 93.4,
    "tested_count": "39,059"
  },
  {
    "label": "experimental",
    "percent_reproducible": 67.0,
    "tested_count": "588"
  }
]

Op de dag dat dit bericht werd geschreven, was unstable op amd64 voor 94.2% reproduceerbaar over 41,163 geteste pakketten. Experimental stond op 67.0%, over een veel kleinere en veel nieuwere steekproef van 588 pakketten; dit is wat u kunt verwachten van pakketten die nog niet volledig zijn gecorrigeerd.

Deze cijfers zijn afkomstig van de Debian-pagina op tests.reproducible-builds.org, geraadpleegd op 2026-08-18, toen de pagina de stempel "Last update: 2026-08-18 16:02 UTC" droeg. Deze cijfers veranderen continu. Raadpleeg de tracker in plaats van deze paragraaf over zes maanden nog aan te halen.

Eén kanttekening is belangrijker dan het percentage. Dit framework bouwt elk pakket tweemaal op eigen hardware, waarbij de omgeving tussen de twee builds varieert, en vergelijkt vervolgens de twee resultaten. Het meet of een pakket reproduceerbaar kan bouwen. Het is geen controle of de .deb die in het archief staat overeenkomt met het resultaat; dat is de afzonderlijke taak van een rebuilder die vergelijkt met het gepubliceerde artefact. Beide getallen zijn nuttig. Ze beantwoorden verschillende vragen, en mensen citeren de eerste alsof het de tweede is.

Wat u op uw eigen server moet doen

U gaat geen distributie opnieuw opbouwen. De onderdelen die u naar een gewone server overzet, zijn kleiner en goedkoop.

  • Pin de toolchain. Een basis-image waarnaar wordt verwezen via een tag kan zonder waarschuwing veranderen. Verwijs naar een image via de digest en leg de digest vast bij de release.
  • Leg de inputs vast. Bewaar het lockfile, de image-digest en de compilerversie bij het artifact. Een build waarvan u de omgeving niet kunt reconstrueren, kan niet opnieuw worden gebouwd en is dus nooit te controleren.
  • Bouw tweemaal in CI en laat de job falen wanneer de outputs verschillen. Dit kost één extra build en detecteert non-determinisme op de dag dat iemand het introduceert, in plaats van een jaar later tijdens een incident.
  • Verwijder de paden die de compiler insluit. Voor Go verwijdert go build -trimpath -buildvcs=false de build-directory en de version control-stempel, en go version -m ./app toont wat er daadwerkelijk in het binaire bestand is terechtgekomen.
  • Bewaar de hash van wat u heeft uitgerold. Wanneer u moet weten of het draaiende binaire bestand overeenkomt met een bronrevisie, is dat record het enige dat antwoord kan geven.

De CI-controle bestaat uit vier regels:

set -eu
./build.sh && mv dist/app app.1
./build.sh && mv dist/app app.2
diffoscope --text - app.1 app.2

diffoscope geeft een non-zero exitcode wanneer de twee artifacts verschillen, waardoor de job uit zichzelf faalt en een leesbare uitleg in het logbestand achterlaat. Dat is het hele idee, teruggebracht tot één repository: de bewering dat een binair bestand afkomstig is uit een broncode-tree moet iets zijn wat een tweede machine kan controleren.

FAQ

Betekent een reproduceerbare build dat de software veilig is?

Nee. Het bewijst enkel dat het binaire bestand overeenkomt met de broncode. Een achterdeur die in de publieke broncode is opgenomen, bouwt reproduceerbaar en elke herbouwer bevestigt dit, omdat zij allemaal dezelfde kwaadaardige broncode hebben gebruikt. Een pakket met een bekende CVE reproduceert perfect en blijft kwetsbaar. Reproduceerbaarheid elimineert één aanvalspunt: de build-machine en het pad van broncode naar binair bestand. Het lezen van de broncode en het bijhouden van kwetsbaarheden zijn afzonderlijke taken die reproduceerbaarheid niet voor u uitvoert.

Waarom verschillen mijn twee builds terwijl er niets in de broncode is veranderd?

Bijna altijd is de oorzaak een tijdstempel, een pad of een volgorde. Archiefformaten zoals tar en zip slaan wijzigingstijden van bestanden op, waardoor een checkout die op een ander moment is gemaakt, andere bytes oplevert. Debug-informatie registreert de absolute build-directory, waardoor /home/alice/src en /build/pkg verschillende binaire bestanden produceren uit identieke code. Directory-reads retourneren items in de volgorde van het bestandssysteem, waardoor de objectbestanden op een link-regel op een andere machine in een andere volgorde kunnen staan. Voer diffoscope build1 build2 uit; het rapport geeft aan welke van deze factoren de oorzaak is, zodat u niet hoeft te gissen.

Wat is SOURCE_DATE_EPOCH en moet ik dit instellen?

Het is een standaard omgevingsvariabele die één getal bevat: de laatste wijzigingstijd van de broncode, in seconden sinds 1 januari 1970 UTC. Tools die dit ondersteunen, gebruiken die waarde op plaatsen waar ze anders de systeemklok zouden raadplegen. Stel deze in vanuit versiebeheer met export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct). Het is niet automatisch en het is geen algemene oplossing. Alleen tools die het implementeren lezen deze variabele, en uw eigen build-scripts moeten deze zelf uitlezen. Een script dat date aanroept, blijft dus de huidige tijd stempelen totdat u dit aanpast.

Wat moet ik doen wanneer een herbouwer BAD rapporteert?

Lees het rapport voordat u iets anders doet. Een BAD-oordeel betekent dat één onafhankelijke herbouw niet dezelfde bytes heeft opgeleverd, en de gebruikelijke oorzaak is non-determinisme in de verpakking in plaats van een aanval. rebuilderd kan om precies deze reden een diffoscope-rapport genereren. Als de verschillen tijdstempels, build-paden of bestandsvolgordes zijn, is dat een verpakkingsfout die het melden waard is. Als het verschil uitvoerbare code betreft zonder dergelijke verklaring, stop dan met het uitrollen van die build, bewaar de artefacten en escaleer het naar de uitgever.

#reproducible-builds#supply-chain#diffoscope#debian#verification