How Attestable Builds Work
This document explains the architecture and mechanics of attestable builds. After reading it, you'll understand each phase of the build process, how cryptographic binding works at every step, and how verification closes the loop from source to running code.
We assume you've read What Are Attestable Builds? and understand the core concept: attestable builds prove that specific inputs were used to produce specific outputs in a verified environment. This document shows exactly how that works.
Kettle: Lunal's Implementation
Kettle is Lunal's implementation of attestable builds. It handles the entire pipeline: input verification, manifest generation, TEE orchestration, build execution, and provenance signing. When we describe "how attestable builds work" in this document, we're describing how Kettle works.
Kettle is open source and audited. The audit matters because Kettle becomes part of the trusted computing base. If Kettle had vulnerabilities, an attacker could potentially forge provenance or bypass verification. The audit provides assurance that Kettle does what it claims.
The Build Flow
An attestable build has three phases, split across two environments. The first phase happens locally on the developer's machine. The second and third phases happen inside a TEE.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Attestable Build Flow │
│ │
│ DEVELOPER MACHINE │
│ ───────────────── │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 1: Manifest Creation │ │
│ │ │ │
│ │ Source ──┐ │ │
│ │ │ │ │
│ │ Deps ────┼──> Kettle ──> Manifest ──> Merkle Root │ │
│ │ │ │ │
│ │ Toolchain┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ manifest + source archive │
│ v │
│ TRUSTED EXECUTION ENVIRONMENT │
│ ───────────────────────────── │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 2: TEE Setup │ │
│ │ │ │
│ │ Hardened VM ──> Kettle (measured) ──> Inputs Loaded ──> Isolated │ │
│ │ │ │
│ │ Hardware Attestation: "This environment contains Kettle with │ │
│ │ measurement X, loaded with manifest Y" │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 3: Build and Signing │ │
│ │ │ │
│ │ Verify Manifest ──> Execute Build ──> Hash Outputs ──> Sign │ │
│ │ │ │
│ │ Output: Artifacts + Provenance + TEE Attestation │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The split between local and TEE environments is intentional. Manifest creation requires access to your development environment: your source tree, your lockfiles, your toolchain. This happens on a machine you control. The actual build happens in a TEE where the hardware provides isolation and attestation. The manifest bridges these environments, carrying cryptographic commitments from one to the other.
Phase 1: Manifest Creation
Before any build starts, the developer locks in their dependencies and creates a manifest. This happens locally, outside the TEE. The manifest becomes the source of truth for what should be included in the build.
Locking dependencies. The developer ensures their project has a lockfile that pins every dependency to a specific version with a cryptographic checksum. This is standard practice in modern package managers. The lockfile captures the exact dependency graph at a point in time.
Input verification. Kettle walks through every build input and computes its cryptographic hash:
- Source code: git commit hash, tree hash (content-addressed hash of the file tree), repository signature
- Dependencies: each package identified by name, version, and checksum from the lockfile
- Toolchain: hashes of compiler and build tool binaries
The verification ensures that cached artifacts match their expected checksums. Any mismatch fails immediately. You cannot create a manifest with unverified inputs.
Merkle tree construction. All input hashes become leaves in a Merkle tree. The tree is constructed in deterministic order: git information first, then lockfile hash, then dependencies sorted alphabetically, then toolchain hashes. This ordering is fixed by convention so that anyone can reconstruct the same tree from the same inputs.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Input Merkle Tree │
│ │
│ [input_merkle_root] │
│ ────────┬──────── │
│ ┌─────────┴─────────┐ │
│ [subtree] [subtree] │
│ ────┬──── ────┬──── │
│ ┌──────┴──────┐ ┌──────┴──────┐ │
│ [git_commit] [tree_hash] [lockfile] [deps_root] │
│ ────┬──── │
│ ┌────────┼────────┐ │
│ [dep_a] [dep_b] [dep_c] │
│ ... ... ... │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The root of this tree, the input_merkle_root, is a single 32-byte hash that uniquely identifies the complete set of build inputs. If any input changes by a single byte, the root changes.
Manifest generation. Kettle produces a manifest containing the Merkle root, the full list of inputs with their hashes, and metadata about the build configuration. This manifest is the commitment: it says "a valid build of this project must use exactly these inputs."
Developer signature. The developer signs the manifest. This signature attests that the developer reviewed and approved this set of inputs. The signature is optional but recommended. It creates accountability: the signed manifest traces back to a specific person who vouched for these inputs.
The manifest can be stored alongside your source code, published to a registry, or transmitted directly to a TEE build service. It carries everything needed to verify inputs without access to your development machine.
The Merkle structure enables selective disclosure. If someone asks "prove you're building against dependency X version Y," you can provide a Merkle inclusion proof: just the path from that dependency's leaf to the root. The verifier confirms the proof against the published root without seeing your other dependencies.
Phase 2: TEE Environment Setup
The build environment boots inside a TEE with measured boot. This is where hardware takes over and starts generating evidence that software cannot forge.
The Measured Boot Chain
When the TEE initializes, the hardware measures every component that loads. "Measured" means the TEE's security processor computes a hash of the component and extends it into a cumulative measurement register. The extension operation is: new_measurement = hash(old_measurement || component_hash). Because extension is one-way (you cannot "un-extend"), the final measurement depends on exactly what was loaded, in exactly what order.
The measurement chain includes:
| Component | What Gets Measured |
|---|---|
| Firmware | UEFI/boot code that initializes the TEE |
| VM Image | The hardened operating system image |
| Kettle | The attestable build orchestrator |
Kettle is measured as part of the TEE's initial state. This is critical: the attestation report proves not just "this is a TEE" but "this is a TEE running Kettle with measurement X." A verifier can confirm they're talking to a genuine Kettle instance, not arbitrary code claiming to be Kettle.
Hardening the Build Environment
The TEE provides hardware isolation, but defense in depth requires additional hardening layers. The build environment is configured to minimize attack surface:
Hardened VM image. The base image is minimal: only the components needed to run Kettle and execute builds. No unnecessary services, no debugging tools, no package managers. The image is built reproducibly so its measurement is predictable.
Mandatory access control. The VM runs with MAC (Mandatory Access Control) enforced through SELinux or equivalent. MAC policies restrict what processes can access, even if they're running as root. The build process cannot access files or resources outside its designated scope.
System call filtering. Kettle enforces seccomp (secure computing mode) filters on the build process. The filter allows only the system calls needed for compilation and file I/O. Dangerous calls (like those that would enable network access or privilege escalation) are blocked.
Network isolation. After the manifest and inputs are loaded, networking is disabled. The build cannot fetch additional resources, phone home, or exfiltrate data. This isolation is enforced at multiple layers: seccomp blocks network-related syscalls, iptables rules drop all traffic, and the hypervisor can enforce network isolation at the virtual hardware level.
Kettle audit. Kettle's codebase has been audited for security vulnerabilities. The audit examined input validation, cryptographic operations, process isolation, and privilege handling. The audit report is published so verifiers can assess the assurance level.
Loading Inputs
With the environment hardened, Kettle loads the build inputs. There are two approaches:
Network-enabled loading. Kettle fetches dependencies from their registries based on the manifest. Each fetched artifact is verified against the checksum in the manifest. After all inputs are loaded and verified, networking is disabled. This approach is simpler but requires trusting the network path during loading.
Pre-loaded inputs. All dependencies are bundled with the source archive and loaded into the TEE before it boots. The inputs are measured as part of the initial TEE state. This approach eliminates network trust during the build entirely, at the cost of larger input bundles. The measurement includes not just Kettle but the specific inputs, providing stronger binding.
Either way, Kettle verifies that the loaded inputs match the manifest's Merkle root. If anything doesn't match, the build fails.
TEE Attestation
Once the environment is set up and inputs are loaded, the TEE generates an attestation report. This report contains:
- The cumulative measurement (what was loaded)
- Platform information (CPU model, firmware version, security features)
- A hash of the manifest in the report's custom data field
- A signature from a key that only the CPU can access
The signature chains to the CPU vendor's root certificate. A verifier can confirm: "This report was generated by genuine hardware running a TEE with this measurement, and it was loaded with inputs matching this manifest."
This is the bridge between software claims and hardware proof. The manifest says "these inputs should be used." The attestation proves "these inputs were loaded into this measured environment."
Phase 3: Build Execution and Signing
The build runs inside the isolated TEE using the verified inputs.
Executing the Build
Kettle invokes the build toolchain according to the manifest's build configuration. The compilation, linking, and packaging happen normally. Kettle doesn't modify the build process; it wraps it in a verified, isolated environment.
The build runs under the hardening constraints described above: MAC policies restrict file access, seccomp filters block dangerous syscalls, and networking is disabled. If the build attempts anything outside its permitted scope, the operation fails.
This is an ordinary build in an extraordinary context. The difference is not how the code compiles but the evidence generated about that compilation.
Hashing Outputs
When the build completes, Kettle hashes each output artifact. These hashes become part of the provenance. Because the hashing happens inside the TEE, the output hashes benefit from the same isolation guarantees as the build itself.
For artifacts that will run in confidential VMs, Kettle also computes the expected launch measurement. This is the hash that will appear in runtime attestation when the artifact boots. Computing it now, at build time, enables the critical verification: does the runtime measurement match what the build produced?
Signing Provenance
The TEE generates a signing key derived from its hardware root of trust. This key is bound to the specific TEE instance and its measured state: it can only be produced by a TEE with exactly this configuration. A different TEE, or the same TEE with different loaded software, would produce a different key.
Kettle constructs a provenance document containing:
- The manifest (including Merkle root and all input hashes)
- The TEE attestation report
- Output artifact hashes
- Expected launch measurement for runtime verification
- Timestamps and build metadata
The TEE signs this provenance document. The signature cryptographically binds the outputs to the attested build environment and verified inputs. Forging this signature requires compromising the TEE hardware.
What Kettle Produces
A complete build generates three files:
manifest.json: The input commitment created in Phase 1. Contains the Merkle root, full input list, and (if signed) the developer's signature.
provenance.json: The build record created in Phase 3. Contains the manifest, output hashes, and build metadata in SLSA v1.2 format for interoperability with other supply chain tools.
Evidence: The TEE attestation report, base64-encoded. The first 32 bytes of the custom data field contain the SHA256 hash of the provenance, binding the attestation to the provenance content.
These three files together provide the complete evidence chain. The manifest commits to inputs. The provenance binds inputs to outputs. The attestation proves the provenance was generated in a genuine TEE.
The Complete Chain
The result is a cryptographic chain with no gaps:
┌─────────────────────────────────────┐
│ BUILD INPUTS │
│ ┌─────────┐ ┌──────┐ ┌──────────┐ │
│ │ Source │ │ Deps │ │ Toolchain│ │
│ └────┬────┘ └──┬───┘ └────┬─────┘ │
│ └─────────┼─────────┘ │
└─────────────────┼───────────────────┘
│
▼
┌──────────────┐
│ Merkle Root │
└──────┬───────┘
│
▼
┌──────────────┐
│ Manifest │
└──────┬───────┘
│
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄│┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ TEE boundary
│
▼
┌─────────────────────────────────────┐
│ TRUSTED EXECUTION │
│ │
│ Kettle ──▶ Verify ──▶ Build │
│ (measured) Inputs │
│ │ │
│ ▼ │
│ Hash Outputs │
│ │ │
│ ▼ │
│ Sign with TEE Key │
│ │ │
└──────────────────────────┼──────────┘
│
▼
┌─────────────────────────────────────┐
│ OUTPUTS │
│ ┌────────────┐ ┌──────────────┐ │
│ │ Provenance │ │ Attestation │ │
│ └─────┬──────┘ └──────┬───────┘ │
│ └────────┬───────┘ │
└─────────────────┼───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ RUNTIME VERIFICATION │
│ │
│ Live Attestation ══ Expected │
│ from Service Measurement │
│ │
└─────────────────────────────────────┘
Let's trace why an attack at each stage would fail:
Attacker modifies source after commit. The tree hash changes. The Merkle root changes. The manifest no longer matches. Verification fails.
Attacker substitutes a dependency. The checksum doesn't match the lockfile. Input verification fails before the build starts.
Attacker compromises the build machine. The build runs inside the TEE, isolated from the host. The attacker cannot read memory or inject code. If they load different inputs, the measurement changes and attestation verification fails.
Attacker provides a malicious Kettle. The TEE measurement would differ from the expected Kettle measurement. Verifiers reject attestations from unrecognized builds.
Attacker swaps the output artifact. The artifact hash doesn't match the provenance. Verification fails.
Attacker forges provenance. They cannot produce a valid TEE attestation signature without the hardware key. Signature verification fails.
Verification Flow
A client verifying your service checks the complete chain from source to runtime.
| Step | What the client checks |
|---|---|
| 1. Verify TEE attestation | Attestation signature chains to hardware vendor root of trust. Kettle measurement matches expected value. Attestation is fresh. |
| 2. Verify provenance binding | Provenance hash in attestation matches actual provenance document. |
| 3. Verify manifest signature | Developer signature on manifest is valid (if present). |
| 4. Verify input hashes | Recompute Merkle root from inputs. Check against manifest. Optionally verify specific dependencies against registries. |
| 5. Verify artifact binding | Output artifact hashes match provenance. |
| 6. Verify runtime attestation | Request fresh attestation from live service. Launch measurement matches provenance's expected measurement. |
If all checks pass, the client has cryptographic proof that:
- The running code has measurement X
- Measurement X was produced by a build inside an attested TEE running verified Kettle
- That build used exactly the inputs specified in a signed manifest
- Every link in the chain is independently verifiable
Verification takes OOM ~100s of milliseconds. Each step is a cryptographic check against hardware-rooted signatures or known public keys.