This document explains what attested builds are, why they matter, and how they solve the software verification problem. After reading it, you'll understand the core insight behind attested builds and why Trusted Execution Environments make them possible now.
We assume familiarity with git, package managers, and the concept of cryptographic hashes.
One party wants assurances about another party's software. The specific assurances vary by context:
- A customer deploying your service wants to know it's running the code you claim, built from the source they audited.
- A compliance team wants evidence that a binary was built with specific dependency versions, not newer ones with unknown changes.
- A security auditor wants to verify that the toolchain used to compile a release matches the one specified in your security documentation.
- A regulated enterprise wants proof that sensitive data will be processed only by code that passed their review, not by a modified version.
- A package consumer wants to ensure that the binary they downloaded corresponds to the source code and dependencies they reviewed, not a tampered version.
The common thread: someone who didn't build the software needs to verify claims about how it was built.
At small scale, this is might be manageable through process. You can walk an auditor through your build system, show them your CI logs, explain your release procedures. They decide whether to trust your operational controls.
At scale, process-based trust breaks down. A package registry serves millions of artifacts to millions of consumers. A cloud platform runs workloads for hundreds of thousands of customers. No one can personally audit every build. Consumers rely on claims: signed manifests, published hashes, attestations of policy compliance. But claims are not evidence. The signature proves who made the claim, not that the claim is true.
The theoretical ideal for software verification is reproducible builds: given the same source code, build environment, and build instructions, any party can recreate bit-for-bit identical artifacts. If compilation is fully deterministic, verification becomes straightforward. Compute the expected hash from the source, compare it to the artifact's hash, done.
This is the holy grail because it eliminates trust entirely. You don't need to trust the builder, the build infrastructure, or any claims about the build process. You trust math. If the hashes match, the artifact is correct. Reference measurements become meaningful because anyone can reproduce them.
The problem is that most toolchains are non-deterministic. Compilers embed timestamps. Parallel builds produce outputs in varying order. Linkers record file paths. Archive tools don't preserve consistent ordering. Achieving bit-for-bit identical output requires controlling all of this across your entire dependency tree. A single non-reproducible component anywhere in the chain breaks the guarantee. Projects like Reproducible Builds have made significant progress, but full reproducibility across an entire software ecosystem remains elusive.
Nix is often cited as a solution to the reproducibility problem, but this usually a confusion between two different concepts: reproducible build environments and reproducible build outputs.
Nix provides reproducible build environments. Given a flake.lock or derivation, Nix guarantees you will build with the exact same inputs: the same source, the same dependencies, the same toolchain. This is a significant improvement over traditional package managers where dependency resolution can vary between machines or over time.
But reproducible inputs don't guarantee reproducible outputs. The same source built with the same compiler can still produce different binaries due to non-determinism in the compilation process itself. Nix makes the build environment deterministic, not the build output. Of course, Nix can produce bit by bit reproducible builds as well, but that is depdendent on the underlying toolchain being deterministic, which is not the case for a lot of software out there.
There's also an issue of how do you trust Nix itself? A compromised Nix installation can produce bogus derivations. A malicious actor with access to your build machine can modify the Nix toolchain to inject code regardless of what your flake.lock specifies. The derivation hashes prove consistency with a particular Nix evaluation, but they don't prove that evaluation was honest.
Here is the core insight: if you can cryptographically verify how something was built, and what went into that build, then bind it to the outcome, then that gives you a lot of the guarantees you'd want out of reproducible builds.
It is a shift from asking "does this binary have hash X?" to asking "was this binary with hash X produced by process Y from sources Z in environment W?"
With reproducible builds, you build the same source twice and check whether both outputs are bit-for-bit identical. If they are, you know the artifact matches the source. The problem is that this fails whenever timestamps, file ordering, or compiler optimizations introduce non-determinism. They almost always do.
With attested builds, you take a different approach. You feed your source, dependencies, and toolchain into a hardware-isolated build environment, and you get back both the binary and a provenance record. Verification then becomes three questions: was the build environment tamper-proof? Were the inputs the expected ones? And is the output cryptographically bound to that environment and those inputs? This is a mouthful to state, but it turns out these verifications are much easier to achieve than bit-for-bit reproducible builds. You don't need to eliminate non-determinism from every compiler, linker, and archive tool in your dependency tree. You need to verify the process and bind the result to it.
This reframes the problem entirely. You're not trying to achieve deterministic compilation. You're trying to create a cryptographic chain of evidence that binds your output to auditable inputs through a process that can be independently verified. A verifier checks cryptographic signatures and attestation reports. The verification is independent: anyone can verify without trusting the builder's claims.
Why is the attested builds approach possible now? How do we actually verify the statement "was this binary with hash X produced by process Y from sources Z in environment W?"
One answer is Trusted Execution Environments. TEEs provide a root of trust that is cryptographically attested. Let's unpack what that means.
A TEE is a hardware-isolated execution environment where the CPU itself enforces protections that software cannot override. You can think of it as a hardened and encrypted version of a VM. Even the operating system, hypervisor, and cloud operators cannot read or tamper with code running inside a TEE. Three primitives matter for attested builds.
Isolation. Code runs in a hardware-protected environment. The host system (hypervisor, cloud provider, operators) cannot access memory or tamper with execution. Memory is encrypted with a per-VM key that only the CPU's security processor knows. When the hypervisor reads a guest's memory region, it gets ciphertext. The encryption key is generated by hardware, stored in hardware, and never exposed to any software.
Integrity. The hardware detects if the host tampers with guest memory. Substituting pages, replaying old data, or remapping addresses all trigger faults. The host cannot silently corrupt guest state. This is enforced by hardware checks on every memory access.
Attestation. The guest can prove to a remote party what code it's running. The CPU's security processor measures the initial guest image and signs a report with a key rooted in the silicon. A verifier can check this signature against the hardware vendor's certificate chain to confirm the report came from real hardware, not from software pretending to be a confidential VM.
This does not guarantee zero trust. You trust the CPU vendor's silicon and firmware. You trust that their manufacturing process didn't embed backdoors. You trust that their key management practices keep root keys secure. You trust that the security processor firmware doesn't have exploitable vulnerabilities.
You don't trust the cloud provider's software stack. The hypervisor, host OS, management plane, orchestration systems, and monitoring agents are all outside the trust boundary. They can be compromised, malicious, or buggy without affecting the confidentiality or integrity of your workload. You also don't trust the cloud provider's employees. Administrators with root access to the hypervisor, and anyone in the operational chain cannot read your memory or tamper with it undetected.
This is a much smaller trust surface than trusting the entire infrastructure. The TEE provides a hardware root of trust that allows you to verify that a specific process ran in a specific environment with specific inputs, and that the output is cryptographically bound to that process. This is the foundation that makes attested builds possible now. This creates the root of trust needed to make process verification meaningful. Without TEEs, "we verified the inputs" is just a claim. With TEEs, it's a hardware-signed assertion. Yes it is not zero trust, but it is still a huge upgrade security wise.
Attested builds provide three properties that together close the verification gap.
Supply chain verification. The artifact was built from a specific source commit, with specific dependency versions, using a specific toolchain. Each input is cryptographically identified, not just named. The source is pinned to a git commit hash and tree hash. Dependencies are pinned via lockfiles with each package identified by cryptographic checksum. The toolchain (compiler, linker, build tools) is hashed. All of these become leaves in a Merkle tree, producing a single root hash that uniquely identifies the complete set of build inputs. If any input changes by a single byte, the root changes.
Build process integrity. The build executed in a hardware-isolated environment that can be remotely verified. The TEE attestation proves the build environment wasn't tampered with and that only the specified process ran. Networking is disabled after inputs are loaded. The build environment is isolated: nothing can be injected or exfiltrated during compilation. The TEE generates an attestation report proving the integrity of the loaded environment, signed by hardware and rooted in keys that only the CPU can access.
Verifiable chain of custody. Every link from source commit to running code is cryptographically bound.
Verification is fast. Instead of rebuilding, a client checks cryptographic signatures and attestation reports. This is feasible at scale.
The chain looks like this: source commit (signed by developers) to dependencies (pinned by hash) to toolchain (hashed) to Merkle root of all inputs to TEE attestation (hardware-signed proof of build environment) to output artifacts (signed by TEE-derived key) to runtime measurement (referenced in both build and runtime attestation). Every link is verifiable. The chain connects auditable source to running code with no gaps.
This doesn't require your builds to be reproducible. It requires them to be consistent enough that you can verify the inputs and trust the process. The TEE attestation substitutes for bit-for-bit determinism: instead of proving "anyone would get the same output," you prove "this specific process ran in a verified environment with these specific inputs."