image: a digest-pinned ref only trusts a record that hashes back to it - #13
Merged
Merged
Conversation
closer-claudio
force-pushed
the
claudio/20260913-digest-pinned-resolve
branch
3 times, most recently
from
September 13, 2026 19:17
3a535b0 to
df123ca
Compare
Resolve built the Image for a cached ref@platform from the record's two fields, digest and config, and for a digest-pinned ref never checked either against the ref. Under the default pull policy that record is the whole story — no registry is consulted — so a record that was edited, or written for something else, redirected `debian@sha256:X` to another rootfs with another entrypoint, offline, with nothing left to notice. The digest the user pinned was the one thing the run did not verify. A plain digest comparison would be wrong: a multi-platform ref names an index, and the record legitimately holds the per-platform manifest's digest, not the index's. So the record now keeps the chain as raw bytes — the index the ref named (when it is one), the platform manifest, the config blob — and a digest-pinned ref is resolved from bytes, not fields: sha256(index) must equal the ref, the index must list sha256(manifest) for the platform (or, with no index, the manifest must hash to the ref), the manifest's config descriptor must equal sha256(config blob), and the digest and config the Image carries are re-derived from those bytes. A record that fails any link is refused with ErrResolve naming what disagrees. A tag's record is taken as is, as before: a tag can name anything. Records written before the chain was kept still serve tags. For a pinned ref they are re-resolved once under missing (the registry answer is recorded with its chain) and refused under never, naming --pull=always. What this does not do: the flattened rootfs blob is a derived artifact and is trusted from the cache as before; the chain reaches the manifest, not the layers. The record cannot be edited undetected any more; the store still can. That is the boundary the content cache has always had. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: closer-claudio <claudio@farcloser.world>
closer-claudio
force-pushed
the
claudio/20260913-digest-pinned-resolve
branch
from
September 13, 2026 19:18
df123ca to
464a4ad
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolvebuilt the Image for a cachedref@platformfrom the record's two fields — digest and config — and for a digest-pinned ref never checked either against the ref. Under the default pull policy the record is the whole story (no registry), so an edited or misfiled record redirecteddebian@sha256:Xto another rootfs with another entrypoint, offline, silently. The one thing the user pinned was the one thing the run did not verify.Why not "compare the digests"
A multi-platform ref names an index; the record legitimately holds the per-platform manifest digest, not the index's. A plain comparison would refuse every multi-arch pinned ref — including forkcloser/curl's
debian:…@sha256:invocation.What this does
The record keeps the chain as raw bytes — index (when the ref named one), platform manifest, config blob — and a digest-pinned ref is resolved from bytes, not fields:
sha256(index) == ref, and the index listssha256(manifest)for the platform — or, with no index,sha256(manifest) == ref;== sha256(config blob);Any broken link →
ErrResolvenaming what disagrees. Tag records are taken as-is, as before.Records predating the chain still serve tags; for a pinned ref they re-resolve once under
missing(the answer is recorded with its chain) and refuse undernever, naming--pull=always.Boundary: the flattened rootfs blob is a derived artifact and is trusted from the cache as before — the chain reaches the manifest, not the layers. The record can no longer be edited undetected; the store still can. Same boundary the content cache always had, now stated.
Verification
just testgreen. New tests: chain verified via index and via a direct manifest; five alterations refused (digest field swapped, whole chain swapped, manifest swapped under the right index, config blob swapped, config blob edited); a legacy record refused for a pinned ref underneverwith the hint, and still serving a tag.just lint: every code lane green. Thelinkslane is red in my sandbox only —third_party/vz/README.md→lima-vm.io, untouched here, blocked by the sandbox network allowlist; CI has the network.Context: found while assessing whether allowlisting
~/Library/Caches/osseinfor agent sessions widens the trust boundary. Independent of #10; whichever merges second rebases (both touchresolutionandResolve).