Do not write random values into build output. If you need something random looking, such as an identifier, seed a pseudo-random generator with a fixed value taken from the source, like a file, a changelog or the commit. Then every build of the same source produces the same values.
Where randomness hides
The reproducible-builds documentation says random data will make builds unreproducible and must be avoided. Common places it appears:
- Unique identifiers generated for a package, installer or filesystem.
- Build ids and hash seeds that tools generate on the fly.
- Temporary file names that end up recorded in output. The documentation notes intermediate temporary files can lead to paths embedded in the final output.
- Link time optimization, where the documentation says GCC users write random identifiers into object files, and a random seed option can be given to fix that.
- Filesystem images, where UUIDs and labels not set explicitly make images differ. The system image guidance lists unset UUIDs and labels as a general problem and shows setting hash seeds and UUIDs explicitly when creating a filesystem, and a deterministic seed variable for one filesystem tool.
The commandments phrase it as a rule: do not gamble and record random numbers such as UUIDs, keys, hash seeds or address layout values.
The standard fix
Replace "generate random" with "derive from the source".
- Choose a seed source. The documentation suggests reading the value from a file, a changelog or version control. A commit id is a good choice because it is unique to the source and fixed.
- Seed a pseudo-random generator with that value, and use it wherever the build needs an arbitrary value.
- Or derive the value directly. A name based identifier computed from a stable string, such as the project name plus version, gives the same result every time.
- Document the choice so a new developer does not "improve" it back to random.
import hashlib, subprocess
commit = subprocess.check_output(["git","rev-parse","HEAD"]).decode().strip()
build_id = hashlib.sha256(("myapp:" + commit).encode()).hexdigest()[:32]
Treat this as a build identifier alone. Do not use a derived value where you need real secrecy, such as a signing key or an access token.
What must stay random
Some values must be random at runtime: session tokens, encryption keys, nonces. Reproducibility is about the build, not the running program. The distinction to hold: values generated when the software runs are fine, values frozen into the artifact during the build should be derived.
| Value | At build time | At run time |
|---|---|---|
| Artifact identifier | Derive from source | n/a |
| Filesystem UUID in an image | Set explicitly | n/a |
| Session token | Never bake in | Generate randomly |
| Signing key | Never derive from source | Generate and store securely |
Finding the culprits
- Build twice and compare the outputs with a byte level diff tool.
- Look at the differing regions. Hex strings that look like identifiers are the usual sign.
- Search the build scripts and tool options for a seed or identifier setting.
- Set it explicitly and rebuild.
Where Safeguard fits
Safeguard scans dependencies and produces SBOMs rather than inspecting build identifiers. Free scan: gold.safeguard.sh/scan.
Quick check
Is a derived identifier as unique as a random one?
It is unique per source revision, which is what a build identifier needs. It is not suitable for secrets.
Should tests also avoid randomness?
Tests can use random inputs, but log the seed so a failing run can be repeated.
Sources
- Reproducible Builds: Randomness
- Reproducible Builds: Commandments
- Reproducible Builds: System images
Last reviewed: 19 October 2026.