> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinfoil.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# CVM image: TCB and reproducible builds

> What is inside the confidential VM image that runs every Tinfoil enclave, how its trusted computing base is kept small, and how to rebuild it yourself and compare against the published release.

## Overview

Every Tinfoil enclave boots from the same confidential VM (CVM) image, published from the
open-source [`cvmimage`](https://github.com/tinfoilsh/cvmimage) repository.
Alongside the firmware, it makes up most of the trusted computing
base (TCB): the kernel, the initrd, and the root filesystem that runs the reverse proxy, the
container runtime, and your workload.

The image is designed around two properties:

* **Small, declared TCB.** The image is built additively from an empty tree. Every file that
  ships is declared in the repository, and every external input is pinned by hash.
* **Byte-for-byte reproducible.** The build is hermetic. Anyone can rebuild the image from the
  release commit on their own machine and obtain artifacts identical to the ones Tinfoil ships.

Together, these let an auditor trace the measurement a running enclave attests to all the way
back to public source code, without trusting Tinfoil's build machines.

## How this relates to the transparency log

Tinfoil's [primary verification path](/verification/verification-in-tinfoil#leveraging-transparency-logs)
does not require you to rebuild anything. GitHub Actions builds each release and publishes the
resulting measurements to Sigstore, and the SDKs compare the enclave's attestation against that
signed record at connection time.

Reproducible builds close the remaining gap in that model: the transparency log proves *which*
binary GitHub built, but not that the binary matches the source it claims to come from. A
reproducible build lets you check that link independently. If your rebuild produces the same
artifacts as the release, the measurement in the transparency log is the measurement of the
published source, not of something a compromised builder could have inserted.
As a defense in depth, the Sigstore entries also prove that every build was done on an
isolated GitHub worker without Tinfoil access.

The two mechanisms are complementary. Connection-time verification relies on the transparency
log and is fast enough to run on every request. Auditing uses the reproducible
build to confirm the log entries are honest.

## Trusted computing base

The measured image contains only what is listed below. Anything not declared in the repository
does not ship.

| Component                       | Source                                                          | Declaration                                                 |
| ------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
| Linux kernel                    | Ubuntu kernel source, pinned snapshot, custom hardened config   | `nix/kernel.nix`, `kernel/`                                 |
| NVIDIA kernel modules           | Three modules built against the custom kernel                   | `nix/nvidia-modules.nix`                                    |
| Initrd                          | Fixed cpio archive, Zstandard compressed                        | `nix/initrd.nix`                                            |
| Tinfoil runtime binaries        | Go: boot, PID 1, container manager, egress, shim, volume worker | `nix/go.nix`, `tinfoil/`                                    |
| GPU attestation                 | `nvattest` and `libnvat`                                        | `nix/nvattest.nix`                                          |
| Runtime libraries and CA bundle | Ubuntu packages from a pinned snapshot, payload only            | `nix/runtime-packages.nix`, `nix/runtime-packages-lock.nix` |
| NVIDIA and Docker payloads      | Fixed-output archive fetches                                    | `nix/runtime-sources.nix`                                   |
| Runtime policy files            | containerd, Docker, nftables, and NVIDIA runtime configuration  | `image/rootfs/`                                             |

Notable properties of the Tinfoil CVM image:

* **Additive assembly.** `nix/rootfs.nix` starts from an empty tree and installs only declared
  paths. Ubuntu packages are extracted into staging trees; maintainer scripts do not run, and
  manuals, headers, service units, and other undeclared files never enter the image.
* **Stateless disk.** The image is a fixed 2 GiB read-only EROFS root partition plus its
  dm-verity hash partition. There is no writable partition and no EFI system partition. QEMU supplies the kernel,
  initrd, and firmware directly.
* **Fail-closed runtime policy.** The measured configuration in `image/rootfs/` disables
  inter-container communication, prevents runtime kernel module loading, restricts container
  capabilities, and installs a default-deny firewall. See
  [`docs/runtime-policy.md`](https://github.com/tinfoilsh/cvmimage/blob/main/docs/runtime-policy.md).

The [OVMF firmware](/verification/attestation-architecture#ovmf-firmware) is measured alongside
the image but is built from a separate repository.

## Keeping the TCB small

The trusted computing base (TCB) is everything that can break confidentiality if it is wrong.
The host and hypervisor are outside it by design, so what remains is the firmware, the kernel,
and the guest userspace. The image is built to keep that set small and legible.

**Purpose-built, not general-purpose.** The image does one job: boot, attest, and run
containers with optional GPU access. The kernel includes only what that job needs, all
of it built in at compile time. Userspace has no systemd, udev, shell, or
package manager; a small supervisor orchestrates a handful of fixed services.

**Remove whole categories, not individual pieces.** The kernel has no drivers for hardware
the VM does not have, no legacy protocols, no 32-bit compatibility, and only the filesystems
the image uses. Anything that would let running code change the kernel is switched off.
Userspace follows the same rule: Docker and the NVIDIA stack ship only the parts the
workload needs.

**Trust nothing from the host.** Whatever the hypervisor provides at boot is either measured and
covered by attestation, or reduced to a fixed shape with hardcoded possible values. The image does
not read hardware description tables from the host, does not use DHCP, does not parse disk
metadata to find its root, and has no console. For instance, disks are found by their fixed address, and the
only network settings the host can supply are an address and a gateway.

**Lock down what remains.** Once the GPU driver is loaded, module loading is disabled for good
before any workload starts. Every Tinfoil service runs with a fixed allowlist of system calls
and the minimum privileges it needs, so a compromised service cannot reach into the kernel or
its neighbors.

## What gets measured

A release produces four artifacts: `tinfoilcvm.vmlinuz`, `tinfoilcvm.initrd`, `tinfoilcvm.raw`,
and `tinfoilcvm.roothash`. These are the values bound into attestation:

* The **kernel** and **initrd** are measured by the CPU at boot and appear in the hardware
  attestation report.
* The **root hash** is the dm-verity root of the disk image. It is passed on the kernel command
  line, so it is measured too, and the kernel rejects any disk block that does not match.

The release workflow also publishes a manifest containing the SHA-256 of each artifact and the
root hash, and attests the whole set with GitHub's build provenance. This manifest is what the
`cvm-version` field in your `tinfoil-config.yml` pins.

## Build hermeticity

Every builder, whether GitHub's CI, a release runner, or an auditor, installs Nix through the
same `nix/install.sh` script. It installs a pinned official Nix release, verifies its checksum,
and configures the daemon to build artifact hermetically using a clean sandbox.
The only network access permitted at evaluation time is the hash-pinned
Nixpkgs archive.

External inputs enter the build only through fixed-output derivations: fetches that declare their
expected hash before the sandbox permits network access. A derivation without a declared hash
builds with no network at all.

## Reproducing a release

You need an x86\_64 Linux host with systemd, `sudo`, `curl`, `tar`, and `xz`. The build runs
inside the Nix sandbox, so the host distribution does not affect the result.

<Steps>
  <Step title="Check out the release commit">
    ```sh theme={"dark"}
    git clone https://github.com/tinfoilsh/cvmimage
    cd cvmimage
    git checkout <CVM_VERSION>
    ```
  </Step>

  <Step title="Install the pinned Nix release">
    ```sh theme={"dark"}
    ./nix/install.sh
    export PATH="/nix/var/nix/profiles/default/bin:$PATH"
    ```

    The installer refuses to run on top of an existing, unverified Nix installation. It does not
    modify your shell profile.
  </Step>

  <Step title="Build the shipping image">
    ```sh theme={"dark"}
    nix-build -I . -A shipping-image -o result
    ```

    This builds the kernel, NVIDIA modules, runtime binaries, initrd, and root filesystem, then
    finalizes the disk image. The first build downloads and compiles all pinned inputs and can take
    a while.
  </Step>

  <Step title="Compare against the published release">
    ```sh theme={"dark"}
    sha256sum result/tinfoilcvm.vmlinuz result/tinfoilcvm.initrd result/tinfoilcvm.raw
    cat result/tinfoilcvm.roothash
    ```

    The [GitHub release](https://github.com/tinfoilsh/cvmimage/releases) for the same tag
    publishes these hashes in a manifest signed with Sigstore build provenance. If they match,
    the release is exactly what this source produces, and the root hash is the value bound into
    the enclave's attestation.
  </Step>
</Steps>

## Auditing the inputs

Reproducing the build proves the artifacts match the source. Two further checks let you review
what the source pulls in.

**Review every external input.** Each byte that enters the build from outside the repository is
declared with its URL and hash in one of these files:

| File                            | Inputs                                            |
| ------------------------------- | ------------------------------------------------- |
| `nixpkgs.lock.json`             | The single pinned Nixpkgs source                  |
| `nix/runtime-sources.nix`       | NVIDIA driver, Docker, and busybox archives       |
| `nix/runtime-packages-lock.nix` | The Ubuntu package closure from a pinned snapshot |
| `nix/kernel.nix`                | The Ubuntu kernel source package                  |
| `nix/nvattest.nix`              | The `nvattest` C and Rust dependency sources      |

These files are the readable inventory. To confirm nothing else in the build graph fetches from
the network, the repository's
[audit guide](https://github.com/tinfoilsh/cvmimage/blob/main/docs/build.md#2-enumerate-every-external-input)
shows how to enumerate every fixed-output derivation from the instantiated graph and compare it
against the declarations above.

**List every file in the root filesystem.** Build the rootfs archive on its own and inspect it:

```sh theme={"dark"}
nix-build -I . -A rootfs-archive -o result-rootfs
tar -tvf result-rootfs
```

Every entry traces to a declared package path, a Nix-built binary, or a file under
`image/rootfs/`.

For a fuller walkthrough of the build graph and the ownership of each output, see
[`docs/build.md`](https://github.com/tinfoilsh/cvmimage/blob/main/docs/build.md) in the
repository.
