Skip to main content

Overview

Standard secrets are stored by Tinfoil and injected at deploy time, which means Tinfoil infrastructure can access their values. Private secrets keep the value out of Tinfoil’s reach: it lives in your own secret store, behind a keyserver you run, and is released directly to your secure enclave only after the enclave proves — with hardware attestation — that it is running the exact release you approved. Tinfoil never handles the value. It transits an attested TLS connection from your keyserver into the enclave, and exists in plaintext only inside enclave memory.

How it works

Your tinfoil-config.yml declares a keyserver-url pointing at your keyserver. That file is part of what the enclave’s attestation measures, which has a simple consequence: the release you approve in your keyserver policy also fixes which keyserver the enclave will talk to. Tinfoil cannot point a running deployment at a different keyserver, and anyone who ships a config with a different URL has made a different release, which your keyserver will refuse until you pin its tag. Your enclave gets secrets only from your keyserver, and your keyserver gives them only to code you approved. On every boot:
  1. The enclave requests a single-use nonce from your keyserver.
  2. It builds a fresh hardware attestation document over that nonce. The document embeds everything needed to verify it: the CPU and GPU evidence, and the Sigstore artifacts that authenticate the running code as a specific release of your repo.
  3. It sends the document to your keyserver over mutual TLS.
  4. The keyserver verifies the document offline using the Tinfoil SDK — the same verification Tinfoil clients perform — and checks it against your policy: the pinned repo and release tag, and your deployment’s domain. It answers only over the TLS connection that proves possession of the key inside the attestation, so a copied or relayed attestation document is useless.
  5. Only if every check passes, the keyserver returns the secrets, and the enclave injects them into your container as environment variables.
The release decision is yours alone: your keyserver, your policy, your secret store. Authorizing a new release is a one-line policy change (bump the pinned tag). Enclaves that fail verification — wrong repo, wrong tag, debug mode enabled, stale or replayed evidence — receive nothing.

Requirements

  • cvm-version 0.13.0 or later in tinfoil-config.yml.
  • Your release workflow must use tinfoilsh/measure-image-action v0.10 or later.
  • Keyserver v0.0.2 or later (ghcr.io/tinfoilsh/keyserver:v0.0.2).
  • Every release needs a freshness witness before an enclave can boot from it. Tinfoil publishes one automatically for the latest release of each repository it deploys. If a brand-new release fails to start, wait a minute and relaunch.
  • Your config repo must be public, like all Tinfoil Containers config repos (see Private images — the Docker image and your secrets stay private).
  • The keyserver must be reachable from the enclave at every boot, serve /challenge and /fetch directly at the keyserver-url (redirects are refused), and present a TLS certificate from a publicly trusted CA (Let’s Encrypt works; self-signed certificates are rejected).

Run a keyserver

The reference keyserver is a small, auditable Go server: tinfoilsh/keyserver. It fronts HashiCorp Vault (KV v2), AWS Secrets Manager, or a local file for isolated deployments. Its policy pins which build may receive which secrets:
policy.yaml
domain is required; the keyserver refuses to load a policy without it. Config repos are public, so anyone can deploy your exact repo and tag and produce an identical, validly-attested measurement — without a domain pin, that copy would also pass verification and receive your secrets. The domain pin requires the caller to hold a CA-issued certificate for your deployment’s domain, which only your deployment can obtain, so a copy is refused. See the keyserver README for running the server and connecting each backend.

Wire your deployment

Declare the secrets on your container and point keyserver-url at your keyserver:
tinfoil-config.yml
Setting keyserver-url switches the whole deployment to private secrets: every secret declared in the config is fetched from your keyserver at boot, and none can come from Tinfoil. Selecting a standard secret at launch for a container that has keyserver-url set makes the boot fail closed rather than silently substitute the value. Delivery is fail-closed in every other way too: if any declared secret can’t be fetched and verified, the workload does not start.
Debug enclaves never talk to your keyserver. In debug mode the enclave ignores keyserver-url and takes every declared secret from the dashboard, so select them (and an SSH key) when you launch. The keyserver would refuse a debug enclave anyway, since its attestation says it is debuggable.
Credentials for pulling private images are the one exception: Tinfoil still supplies those, even with keyserver-url set.

Operating notes

  • Fetches happen at every enclave boot — first deploy, updates, and relaunches all create a new enclave that must reach your keyserver. A keyserver outage doesn’t affect running enclaves, but blocks new ones from starting.
  • Rotation: update the value in your secret store, then redeploy. The new enclave fetches the current value.
  • New releases: update the pinned tag in your keyserver policy before deploying the new version, or the new enclave will be refused. The keyserver reads its policy at startup, so restart it after editing.
  • Audit: the keyserver logs every release with the verified repo and tag, and every denial with its reason, giving you a complete record of who received which secrets.
  • Diagnosing a refused boot: an enclave the keyserver refuses never becomes ready, and the deployment status does not yet show why. The keyserver’s log has the reason.
For a minimal end-to-end example, see tinfoilsh/confidential-secrets-demo.