ADR-0023: A pin is a digest, and a signature is a different question
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Proposed Date: 2026-09-11
Context
F3-R8 asks that a plugin’s referenced images be “signed and pinned”, and that
an unpinned or unsigned one be “flagged rather than silently accepted”. It
treats those as one obligation with one remedy. They are two obligations with
different failure modes, and the conflation hides a problem in each.
The pinning half is stricter than the project currently practises. Every one
of the nineteen bundled images is pinned to a tag — lscr.io/linuxserver/sonarr:4.0.15,
qmcgaw/gluetun:v3.40.0 — and not one is pinned to a digest. E1-R1 asks for
“explicit versions” and no “floating tag”, and a tag satisfies that reading:
4.0.15 is explicit and does not float in the sense latest does.
It floats in the sense that matters. A tag is a name its publisher can repoint at
any time, so the image a maintainer reviewed when they moved the pin and the
image an operator pulls six months later can differ with nothing in the
repository having changed. That is precisely the reasoning
ADR-0009 applied to workflow actions — “a mutable ref
is a real supply-chain surface”, and every uses: in the estate is pinned to a
SHA because of it. The project holds its CI to a standard it does not hold the
software that runs on its users’ machines to, and plugins make the gap
untenable: F3-R8 as written would hold a stranger’s image to a bar the bundled
stack does not meet.
The signing half has nothing underneath it. There is no artefact-signing
mechanism anywhere in the estate — no cosign, no minisign, no GPG. Release
artefacts carry checksums and build attestations, which are useful and are not
signatures. Signing is owed by L1-R2, and L1 ships at 1.0.0.
F5-R3 — “Catalogue releases MUST be signed, and a signature that does not
verify MUST be refused” — is scheduled before that. So a requirement due in the
plugin versions depends on a capability not owed until after them, and nothing
detects it: F5’s frontmatter declares requires: [F3], the ordering check reads
that frontmatter, and L1 is not in it.
There is also a distinction the single word “signed” obscures. A catalogue release is signed by this project, and its signature answers did the thing I fetched come from the review I am relying on. An image is signed, if at all, by a publisher this project has no relationship with, and its signature answers a question about them. Requiring both under one word would make the second a condition of installing anything, which would exclude most of the container ecosystem, including images the bundled stack itself depends on.
Decision
A pin is a digest. A signature is a separate obligation with a separate remedy. And pinning applies to the bundled stack on the same terms it applies to a plugin.
-
An image is named by an immutable digest. The human-readable tag is recorded alongside it and shown wherever a version is shown, because a digest is not something an operator can read and
E1-R2requires an update to state what the jump is. The tag is a label; the digest is what runs. -
The digest is of the multi-architecture index, not of one platform’s image. The stack is required to run on
linux/amd64andlinux/arm64, and pinning a single platform’s digest would produce a stack that resolves on one machine and fails on another with an error about a manifest rather than about an architecture. -
This binds the bundled stack.
E1-R1is amended to require it. We do not hold a stranger’s contribution to a bar the nineteen services we chose ourselves do not meet, and the honest correction is to raise ours rather than lower theirs. -
An absent digest is a refusal; an absent signature is an unproven. A digest can always be obtained — it is a property of the image, available from any registry that serves it — so its absence is a fault in the manifest and is refused before anything runs. A signature may simply not exist, because its publisher never made one, so its absence is reported as unproven and is never reported as verified.
F3-R8is split accordingly. -
A signature that is claimed and does not verify is refused, for both kinds. That is worse than no signature and is treated as worse, exactly as
F5’ssignature-unverifiedstate already says. -
Artefact signing is one capability with two consumers, and the earlier consumer sets its date. The catalogue needs it before the release pipeline does; the mechanism is chosen once and serves both, rather than the catalogue inventing a second one because the first has not arrived.
-
A digest that is later found to be bad is replaced, not revoked. There is no revocation list and no callback: a pinned digest is inert data on the operator’s machine, and the answer to a bad one is an update that names a different one. What lemonfiber owes is that the update path works and says what changed — not a mechanism by which a stranger can cause an already- installed plugin to stop working.
Alternatives considered
| Option | Why it lost |
|---|---|
| Keep tag pinning, for the bundled stack and plugins alike | The status quo, and it means the reviewed artefact and the running artefact can differ silently. It is also the position the project has already rejected for CI in ADR-0009, and holding a lower standard for the software that touches the operator’s library than for the software that builds it is not defensible when it is written down side by side. |
| Digest-pin plugins, leave the bundled stack on tags | What F3-R8 says today. Holds a stranger to a bar we do not meet, which is both unfair and unpersuasive: the first contributor to notice will ask, and the answer would have to be “we have not got round to it”. |
| Require a verified image signature before installing anything | Excludes most of the container ecosystem, including several images the bundled stack depends on. A requirement that the bundled stack itself would fail is not a standard, it is a wish. |
| Treat an unsigned image as equivalent to an unverified one | Collapses “nobody made a claim” into “a claim did not check out”. They are different facts and an operator deciding whether to proceed needs to be able to tell them apart — the same distinction C1 already draws between a check that failed and one that could not be run. |
| Pin a single platform’s digest | Produces a stack that resolves on the maintainer’s machine and fails on an operator’s, with a diagnostic about a manifest rather than about an architecture. |
| Build a revocation list the operator’s machine consults | A callback to a project-run service, on a product whose account of what leaves the machine is a closed list and whose whole proposition is that it does not phone home. The cure is worse than the disease, and the disease is already treated by an update. |
Defer F5-R3 until L1 delivers signing |
Leaves the catalogue serving unsigned releases in the versions where it is introduced — which is exactly when its review claim is doing the most work, since nobody yet has a reason to trust it. |
Consequences
Every image reference in the estate changes shape, and the tooling that advances them changes with it. Nineteen bundled services gain digests beside their tags, the manifest gains a field, and the update flow has to move both together — a pin bump that advanced the tag and left the digest would be worse than either alone, because it would read as reviewed.
Reviewing a pin bump gets harder in a way that is honest. A digest is unreadable, so a reviewer cannot see from the diff what moved. What they can see is that exactly one thing moved and it names a version; the check that the digest belongs to the tag is mechanical and belongs in CI rather than in a person’s head.
F5-R3 now has something to stand on, and it arrives earlier than L1
planned. Artefact signing stops being a release-engineering detail owed at
1.0.0 and becomes a prerequisite of the version that introduces the catalogue.
The version manifests have to reflect that, and the ordering check cannot see it
because it reads requires: frontmatter that does not mention L1.
The bundled stack’s own supply chain is stated rather than assumed. Whether each of the nineteen publishers signs their images becomes a fact the catalogue records, and an honest answer to “is this stack signed end to end” is mostly not, and here is which. That is a worse-sounding answer than silence and a much better one than an implication.
Revisit if the container ecosystem’s signing practice becomes near-universal, at which point requiring a verified signature stops excluding most of it and the unproven state can become a refusal.
Related
- ADR-0009 — the same reasoning about mutable refs, applied to CI
- ADR-0021 — the generated entry a digest is written into
- F3 —
F3-R8andF3-R25, the two halves this separates - F5 — catalogue signing, and what a signature vouches for
- E1 —
E1-R1, the bundled stack’s pinning rule this raises - L1 —
L1-R2, the signing capability whose date this moves - plugin-manifest — where a plugin’s digest and tag are declared