Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ renovate.jsonc # Renovate config
- Inline comments: one of exactly three things — a behaviour of an external system, a coupling to another file, or a constraint a plausible edit would silently break. Anything else: delete it.
- Comments describe the code as it is, never how it came to be. A decision, an alternative, a past incident, or an answer to review feedback is how it came to be, and belongs in the commit message and the pull request.
- Default to no comment. Re-read every comment you added or reworded before committing.
- Sign every commit with the signing key the environment configures, and commit under the identity that key belongs to. Never pass another user's name or email to `git commit`: a commit whose author does not match the signing key goes out unsigned and GitHub cannot verify it.
- PR titles must follow Conventional Commits format:
- Allowed types: `image`, `ci`, `chore`, `test`, `docs`
- The scope is optional. Examples:
Expand Down
8 changes: 4 additions & 4 deletions deno/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ ARG TARGETARCH
USER root

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Deno publishes no signature for its release archives, so each archive is
# verified against a checksum file committed to this repository (kept in
# sync by .github/workflows/update-material.yml and reviewed like any other
# change).
# Deno publishes neither a signature nor build provenance for its release
# archives, so each archive is verified against a checksum file committed to
# this repository and kept in sync with DENO_VERSION by
# .github/workflows/update-material.yml.
RUN --mount=type=bind,source=deno-amd64.sha256,target=/tmp/deno-amd64.sha256,ro \
--mount=type=bind,source=deno-arm64.sha256,target=/tmp/deno-arm64.sha256,ro \
case "${TARGETARCH}" in \
Expand Down
12 changes: 7 additions & 5 deletions deno/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ Completions are generated at build time with `deno completions bash` and install
## Supply chain

`deno` is downloaded directly from [GitHub Releases](https://github.com/denoland/deno/releases).
Deno publishes no signature for its release archives, so each archive is verified against a
SHA-256 checksum file committed to this repository (`deno/deno-<arch>.sha256`) rather than one
fetched from the same server as the binary. The checksum files are kept in sync with the pinned
`DENO_VERSION` by an automated workflow and reviewed like any other change, so later tampering
with the download channel cannot affect builds.
Deno publishes neither a signature nor build provenance for its release archives, only a SHA-256
checksum on the same release, so each archive is verified against a copy of that checksum
committed to this repository (`deno/deno-<arch>.sha256`) rather than one fetched at build time.
The checksum files are taken from the release when the pinned `DENO_VERSION` changes, by an
automated workflow, and reviewed like any other change, so a build accepts only the archive that
was published when the version was pinned, and later tampering with the download channel cannot
affect builds.

## Verifying the image

Expand Down
7 changes: 4 additions & 3 deletions deno/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ materials:
commit_to_pr: true
description: >-
Checksum for deno (amd64) from GitHub Releases, matching the pinned
DENO_VERSION. Deno publishes no signature for its release archives, so
this committed file is the trust anchor the Dockerfile verifies the
downloaded archive against. dl.deno.land is not used here: it serves the
DENO_VERSION. Deno publishes neither a signature nor build provenance
for its release archives, so this copy of the checksum published on the
same release is the trust anchor the Dockerfile verifies the downloaded
archive against. dl.deno.land is not used here: it serves the
LTS channel, whose archives differ from the mainline GitHub Releases the
Dockerfile downloads, so its checksums would not match.
- path: deno-arm64.sha256
Expand Down
9 changes: 3 additions & 6 deletions uv/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ USER root

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# uv's release tarball is verified against a checksum committed to this repo
# (downloaded from https://releases.astral.sh/, kept in sync by
# .github/workflows/update-material.yml and reviewed like any other change)
# instead of one fetched alongside the binary.
# uv's release tarball is verified against a checksum committed to this
# repository, which checksum.sh derives from the tarball's build provenance
# and .github/workflows/update-material.yml keeps in sync with UV_VERSION.
RUN --mount=type=bind,source=uv-amd64.sha256,target=/tmp/uv-amd64.sha256,ro \
--mount=type=bind,source=uv-arm64.sha256,target=/tmp/uv-arm64.sha256,ro \
case "${TARGETARCH}" in \
Expand All @@ -26,11 +25,9 @@ RUN --mount=type=bind,source=uv-amd64.sha256,target=/tmp/uv-amd64.sha256,ro \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& TMPDIR="$(mktemp -d)" \
# Download uv and verify it against the committed checksum
&& UV_TARBALL="uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" \
&& wget -q -T 30 -t 3 -P "${TMPDIR}" "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/${UV_TARBALL}" \
&& env --chdir="${TMPDIR}" sha256sum -c "${UV_SHA256_FILE}" \
# Install uv
&& tar xzf "${TMPDIR}/${UV_TARBALL}" -C "${TMPDIR}" \
&& mv "${TMPDIR}/uv-${UV_ARCH}-unknown-linux-gnu/uv" "${TMPDIR}/uv-${UV_ARCH}-unknown-linux-gnu/uvx" /usr/local/bin/ \
&& rm -rf "${TMPDIR}" \
Expand Down
17 changes: 12 additions & 5 deletions uv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,18 @@ setup does keep the two together.

## Supply chain

`uv` is downloaded directly from [GitHub Releases](https://github.com/astral-sh/uv/releases)
and verified before installation against a checksum committed to this repository.
The checksum is sourced from [releases.astral.sh](https://releases.astral.sh/) and
kept in sync with the pinned version by `.github/workflows/update-material.yml`, so it
is reviewed like any other change rather than fetched alongside the binary at build time.
`uv` is downloaded from [GitHub Releases](https://github.com/astral-sh/uv/releases) and verified
against a checksum committed to this repository (`uv/uv-amd64.sha256`, `uv/uv-arm64.sha256`).

uv publishes a checksum next to each release tarball, but it comes from the same release as the
tarball, so that checksum is not what is committed — the committed one is derived here, and only
from a tarball whose origin has been established. `.github/workflows/update-material.yml` runs
`uv/checksum.sh` whenever the pinned version changes, and the script records a digest only after
`gh attestation verify` has confirmed that the tarball's
[build provenance](https://github.com/astral-sh/uv/blob/main/.github/workflows/release.yml) was
signed by uv's release workflow at the commit the version's tag points to. The resulting checksum
is committed and reviewed like any other change, so what the image build trusts is a digest this
repository accepted, rather than one the release host served alongside the tarball.

Note that this covers the `uv` binary only. Interpreters and packages that uv installs at
runtime are fetched from their own upstreams under uv's own verification, outside this image's
Expand Down
15 changes: 8 additions & 7 deletions uv/build.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
description: "Minimal Dev Container image for Python with uv: Debian, verified upstreams, SLSA provenance"
# VERSION in each url is replaced with the build_args value named by
# version_key; commit_to_pr files are pushed onto version-bump PRs.
materials:
- path: uv-amd64.sha256
url: https://releases.astral.sh/github/uv/releases/download/VERSION/uv-x86_64-unknown-linux-gnu.tar.gz.sha256
command: ["checksum.sh", "amd64"]
version_key: UV_VERSION
commit_to_pr: true
description: >-
Checksum for uv's amd64 release tarball from releases.astral.sh, matching
the pinned UV_VERSION. This committed file is the trust anchor the
Dockerfile verifies the downloaded binary against.
Checksum for uv's amd64 release tarball, matching the pinned UV_VERSION.
The checksum uv publishes comes from the same release as the tarball, so
checksum.sh derives this one instead, accepting the tarball only after
`gh attestation verify` has bound its digest to uv's release workflow at
the commit that version's tag points to. This committed file is the
trust anchor the Dockerfile verifies the downloaded tarball against.
- path: uv-arm64.sha256
url: https://releases.astral.sh/github/uv/releases/download/VERSION/uv-aarch64-unknown-linux-gnu.tar.gz.sha256
command: ["checksum.sh", "arm64"]
version_key: UV_VERSION
commit_to_pr: true
description: >-
Expand Down
52 changes: 52 additions & 0 deletions uv/checksum.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# checksum.sh — print the sha256 line for a uv release tarball, after
# confirming the tarball's build provenance.
#
# The tarball is accepted only once `gh attestation verify` has bound its
# digest to uv's release workflow at the commit the version's tag points to.
# The digest is then committed as trust material and is what the image build
# verifies against, the way every other image here verifies a committed
# checksum. The checksum uv publishes beside the tarball is written by the
# same workflow run, so it is not the material.
#
# Run by scripts/update-material.sh from the materials declared in
# build.yaml, with UV_VERSION taken from the pinned build args. Needs `gh`
# authenticated for the read of a public attestation, and `git` for the tag
# lookup; GitHub-hosted runners provide all three.
#
# Usage:
# UV_VERSION=<version> checksum.sh <amd64|arm64>
set -euo pipefail

ARCH="${1:?Usage: UV_VERSION=<version> checksum.sh <amd64|arm64>}"
: "${UV_VERSION:?UV_VERSION must be set}"

case "$ARCH" in
amd64) UV_ARCH="x86_64" ;;
arm64) UV_ARCH="aarch64" ;;
*) echo "Unsupported architecture: ${ARCH}" >&2; exit 1 ;;
esac

TARBALL="uv-${UV_ARCH}-unknown-linux-gnu.tar.gz"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

wget -q -T 30 -t 3 -P "$TMPDIR" \
"https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/${TARBALL}"

# uv's release workflow is dispatched on main and creates the version's tag at
# the commit it ran on, so the signing certificate names refs/heads/main as
# the source ref and only its source digest ties the tarball to the version.
# For an annotated tag ls-remote lists the peeled commit after the tag object,
# so the last line holds the commit either way.
TAG_COMMIT=$(git ls-remote https://github.com/astral-sh/uv \
"refs/tags/${UV_VERSION}" "refs/tags/${UV_VERSION}^{}" | tail -n 1 | cut -f 1)
: "${TAG_COMMIT:?no tag ${UV_VERSION} in astral-sh/uv}"

gh attestation verify "${TMPDIR}/${TARBALL}" \
--repo astral-sh/uv \
--signer-workflow "astral-sh/uv/.github/workflows/release.yml" \
--source-digest "$TAG_COMMIT" \
--deny-self-hosted-runners >&2

env --chdir="$TMPDIR" sha256sum "$TARBALL"