Repository navigation
Conversation
…input A [[tool]] entry can declare pgp-sums (the digest list), optionally pgp-sums-signature and pgp-sums-format, and pgp-key-file (an OpenPGP certificate, relative to the input file). The rung sits below cosign-sums and build-provenance and above upstream-sums. A signature that does not verify aborts the run and never falls to a weaker rung; the signer is recorded by fingerprint. The list parsers are private to pgpsums.rs, so entries are reachable only together with who vouched. REQ-SIGNEDSUMS-001 clause 3 now records the maintainer's 2026-11-08 decision that the key is producer input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
With pgp-sums-format = "rust-channel" the real channel-rust-stable manifest, its detached signature and Rust's release key verify through the ladder; an asset named by one of the manifest's urls takes its digest from the verified list and the signer leads with Rust's key fingerprint. Tampered, mis-formatted and unnamed cases are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
A signature says who published a list, not which release it is for. A sha256sums entry's asset names must now carry %V or %R (refused at plan time otherwise), a Rust channel's own version must match the declared version or release, and the recorded assertion names the release. Plans of one release that declare different pgp-sums or upstream-sums are refused before anything is fetched, instead of verifying with only the first plan's declaration. REQ-SIGNEDSUMS-001 clause 2 and DD-034 now say the key is pinned in the operator's committed producer input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
…o v0.48.0 The maintainer chose this on 2026-11-08 (factory-notice 20261107T193300-80b3fc), after clause 1's cold measurement (VER-VERIFYPAR-001, runs 42402881731 and 42438484840). The measured layer carries one large payload, so digesting payloads concurrently gains almost nothing on any disk. On the cold runner, reading was 72% of the wall time; on local NVMe, hashing would dominate. Overlapping the read with the hash inside a payload helps in both cases. Clause 2 now asks for that, with memory still bounded per payload, and says why the first wording was set aside. Clauses 1 and 3-5 are unchanged. The requirement stays proposed and moves to v0.48.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
…elease The rung already enforces this: d024695 added the versioned-name refusal, the rust-channel version check, the recorded binding and the refusal of split declarations. Until now no clause stated it, so the tests that verify it pointed at text that did not say it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
A subkey the primary has revoked, or whose latest binding lacks the SIGN flag, still produced signatures that verified, and verify_detached_signature tried every subkey as a candidate. Keys now carry a standing: a revoked or encryption-only subkey, or a primary whose own self-signatures state flags without SIGN, is tried only so its refusal can be named (with the key's fingerprint) and never vouches. A primary that states no flags (Rust's) may still sign. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Every nightly of a cycle carries the same 1.99.0-nightly version, so a version match let an older, validly signed nightly manifest stand in for a newer one. When the manifest's rust version has a prerelease part (any '-'), only its date binds; a stable version still binds by version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
The template check looked at text, so a version of `latest` still expanded to the same names for every release and an older signed list covered it. Each placeholder a sha256sums entry's templates use must now expand from a value that names one release (digit-led, no moving-tag word), the list or its signature must carry the release, and ingestion re-checks that every asset name contains the concrete version or release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
The primary's flag check accepted the key if ANY self-signature had ever granted SIGN, so a stale one outvoted a newer one that removed it. The newest certification (on a non-revoked user ID, preferring the primary user ID's) or direct-key signature now decides, as the latest binding already did for a subkey. A deciding signature with no key flags states no restriction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Starting with a digit still admitted moving tags such as 1, 3.x, 2-lts and a bare year. A version or release must now be MAJOR.MINOR[.PATCH] with an optional prerelease or build suffix, or a full date; wildcards, fused words (3lts) and moving-word suffixes are refused. Documented as a name heuristic, not a cryptographic binding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
PgpSumsProbe::Verified had public fields, so any code could hold digests that never came out of a verification. It now wraps a private-field struct built only by PgpSumsProbe::from_verified(VerifiedList, ..), with a compile_fail doctest to hold it there, and verify_release overwrites whatever Source::probe reported with NotDeclared before deciding the entry's own state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
…atures The primary's flag check now decides from the newest direct-key signature and the newest certification on the primary (else any live) user ID, refuses if either states flags without SIGN, refuses when nothing eligible remains, and treats a restriction stated anywhere in the certificate, revoked user IDs included, as binding when the eligible signatures are merely silent. A certificate that states no flags anywhere (Rust's) is unrestricted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Clause 1 says the key that actually signed is recorded separately from the identity, but a subkey fingerprint appeared only inside the free-text assertion. pgp-sums now also writes eu.pulseengine.source.proof-signing-key (the primary's fingerprint when the primary signed), carried through the producer spec and the deposit, refused when blank or without a mechanism, and shown by varve inspect --format json as proof_signing_key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
pgpsums.rs described pgp 0.20, cited a source line number, said the advisory gate FAILS (deny.toml now records the ignores and cargo deny passes), and said key flags were not consulted. The concept doc gains the concrete-version rule, the nightly date rule, the signing-key annotation and the hub limit, and REQ-SIGNEDSUMS-001 clauses 1 and 7 state the key-standing, concrete-version and prerelease rules, including that the name check is a heuristic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
1.2.3-nightly, -nightly2, -SNAPSHOT, -dev, -unstable, -tip, -trunk, -develop, -daily, -weekly and -latestbuild passed names_a_release although every build of such a cycle expands to the same names. A cycle label now passes only when a numeric segment follows it (1.2.3-nightly.20261108), and a token containing /, % or whitespace is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
The ingestion re-check was a substring test, so declared 1.2 passed tool-v1.2.3, tool-11.2 and tool-1.2.30-old. The version must now not be preceded by a digit or '.', nor followed by a digit or '.' and a digit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
The 256-bit floor applied only to the signature over the list. A subkey whose deciding binding, or the back-signature it carries, is made with a weaker hash is now refused with CertificateSignatureWeak. The floor is deliberately NOT applied to the primary's own certification: it grants the pinned key nothing, and Rust's own release key certifies itself with SHA-1 and must keep verifying. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
A detached signature must state its creation time in the hashed area (RFC 9580 5.2.3.11), and any critical hashed subpacket other than creation time and the issuer is refused: a critical notation, regular expression or expiry says the signature means something this module cannot act on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
…side A subkey is now revoked if ANY entry with its fingerprint carries a valid SubkeyRevocation, so a duplicated entry without one no longer slips through, and when two bindings share a creation second the one without SIGN wins. REQ-SIGNEDSUMS-001 clause 6 records signature and binding expiry as not checked, beside key expiry, for the same clock reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Whether a build number follows a cycle label is now a flag carried along a reversed walk, which has the same meaning without the index arithmetic whose mutation was equivalent and so could not be killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
… cap On this PR's first run, cargo mutants (varve-producer) was cancelled at 90:17 and (varve-producer (advisories)) at 90:20, before either printed a summary. Neither had a surviving mutant; both were too slow. - varve-producer: plan.rs grew to 87 mutants, taking the shard to 455. - advisories: unchanged at 404, but the larger producer lib suite made each mutant slower. It took 57-74 minutes before. The split, per-file counts from `cargo mutants --list`: - varve-producer (plan): 275. - varve-producer: the remaining 180. - varve-producer (advisories): advisory + cvss, 199. - varve-producer (advisory watch): advisorywatch + statusdraft, 205. The required check stays `cargo mutants (trust-critical gate)`, which needs the whole matrix, so the new shards are gated without a ruleset change. mutation_scope_is_decided_not_drifted stays green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu
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.
This PR adds the
pgp-sumsingestion rung, discharging REQ-SIGNEDSUMS-001 for v0.47.0. A publisher's PGP-signed digest list now counts as proof of origin, and is never confused with an unsigned list. It also carries the REQ-VERIFYPAR-001 rewording, a rivet-only commit.What it does
Input. The producer's committed tool entry gains four fields:
pgp-sumspgp-sums-signature.ascpgp-sums-formatsha256sums(default) orrust-channelpgp-key-fileThe key lives in producer input, not in the realm. The maintainer decided this on 2026-11-08 (factory-notice
20261107T163508-5f01e8): a realms-file field would make every released varve refuse the realms file, and consumers never verify the upstream signature themselves. Clauses 2 and 3 are reworded to match, and DD-034 notes the change. The key is never fetched, and never read from beside the artifact.Ladder position. cosign-sums, then build-provenance, then pgp-sums, then upstream-sums. A failure of any kind is fatal and never falls through to a weaker rung. That covers a bad signature, another key, a revoked certificate, a text-type signature, a weak digest, a missing signature, a fetch error, an unparseable list, an absent asset, a digest mismatch, and the wrong release.
Clause 5. Digests are reachable only through a
VerifiedList, whose fields are private. The ladder'sPgpSumsProbe::Verifiedcan be built only from one. Acompile_faildoctest holds this.What the signed manifest records:
proof = pgp-sums.proof-signer = openpgp:<primary fingerprint> (<user ID>). The identity is the fingerprint; the user ID is only a label.proof-signing-keyfield, holding the fingerprint of the key that actually signed.proof-asserts, which names the release the list was bound to.An older varve reads an unknown proof string as
UnknownProofand prints it. It does not refuse the layer.OCI encoding: it refuses to encode any
pgp-sumsfield rather than silently drop the mechanism.Which key may vouch (clauses 1 and 3)
A subkey vouches only if all of these hold:
SubkeyRevocation.The primary key:
The data signature:
Release binding (clause 7, new)
A signature says who published the digests, not which release they belong to.
sha256sums format:
%Vor%Rmust expand to a token that names exactly one release:MAJOR.MINOR[.PATCH]with an optional suffix, or a full date.latest,ltsorstable.nightly,snapshot,devand similar) not followed by a number./,%and whitespace are refused.rust-channel format:
-nightly,-beta) binds by the manifest's date.Plans that disagree. Plans of one (repo, release) group that disagree on
pgp-sumsorupstream-sumsare refused before anything is fetched.Limits stated in the docs:
pgp-sums. It fails closed.Reviews
Clean-room review of 1bfdc9d. Of nine claims, four were confirmed (R2, R3, R5, R8), four partial and one confirmed with one test that cannot fail.
All fixed, each with a test that fails before the fix.
Adversarial review of 866f161, focused on key usage and release binding.
All fixed, each with a test that fails before the fix.
Automated security reviews raised:
names_a_release.All fixed, each with a test.
Not in this PR. The same replay gap in the existing rungs (cosign-sums, build-provenance, upstream-sums) is tracked privately as a draft security advisory and will be fixed in a following v0.47.0 PR.
Evidence
Negative controls, each restored after:
channel_is_foralways acceptMutants on the final tree, restricted to the changed files:
pgpsums.rs)plan.rs)orchestrate.rs)delta.rsandingest.rs), earlier roundRust channel end to end: the real
channel-rust-stable.tomland its.ascverify against Rust's real key and yield 1213 (url, digest) pairs. An asset gets its digest from the verified list, and the proof signer starts with Rust's fingerprint.Local gates, on fa8f1ec
cargo fmt --check: OK.-D warnings: clean.rivet validate: exits 0.rivet check verification-evidence, trace-gate, claim-check, tracepack-check and review-check all exit 0.tools/systest/install-shadow.sh: exits 0.-S warning: clean.cargo deny check: exits 0.🤖 Generated with Claude Code
https://claude.ai/code/session_019TNtfRjLNhEz82G2ggeeNu