feat(evidence): aicr evidence sign — sign an existing pushed bundle#1446
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds the signing-only Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/user/cli-reference.md`:
- Around line 2654-2656: Add the missing blank line before the fenced synopsis
block in the evidence sign section so the Markdown follows the
blanks-around-fences rule. Update the nearby “Synopsis” content in the docs to
place an empty line between the bold label and the ```shell fence, keeping the
existing `aicr evidence sign <pointer> [flags]` snippet unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: c5cef9c4-7262-46e1-9ba9-d6de74321df0
📒 Files selected for processing (9)
docs/user/cli-reference.mdpkg/cli/evidence.gopkg/cli/evidence_sign.gopkg/cli/evidence_sign_test.gopkg/evidence/attestation/emit.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/sign.gopkg/evidence/attestation/sign_test.gopkg/evidence/verifier/fetch.go
d94a597 to
47b2d6d
Compare
Coverage Report ✅
Coverage BadgeMerging this branch will decrease overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/cli/evidence.go`:
- Around line 40-47: The top-level evidence usage summary is missing the new
sign subcommand, so update the usage/help text in the evidence command setup to
list sign alongside digest, publish, and verify. Make sure the string that feeds
aicr evidence --help is kept in sync with the Commands slice in the evidence
command definition, including evidenceSignCmd().
In `@pkg/evidence/attestation/emit.go`:
- Around line 389-392: The comment around PointerSignerFromSignature and the
helper’s own docstring are inaccurate about zero-Rekor handling; update both to
match the actual behavior that only RekorLogIndex values greater than 0 are
preserved and index 0 is treated the same as absent. Use the
PointerSignerFromSignature helper and the in.Signer assignment in emit.go to
locate the wording, and revise the text so it no longer claims index 0 is
distinguished from “no Rekor entry.”
In `@pkg/evidence/attestation/sign.go`:
- Around line 78-91: `SignExisting` in `pkg/evidence/attestation/sign.go` still
allows mutating `Attestations[0]` without enforcing the unsigned
single-attestation invariant. Add input validation in `SignExisting` (and/or its
helper path) to reject more than one attestation, reject any existing signer,
and require a non-empty `Bundle.Digest` before proceeding; keep the checks
alongside the existing `opts.Pointer`, `PointerPath`, and `Artifact` validations
so non-CLI callers cannot accidentally clobber provenance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: cc3c4d4c-b44f-4ec7-b9cb-6d298c8c0bcc
📒 Files selected for processing (9)
docs/user/cli-reference.mdpkg/cli/evidence.gopkg/cli/evidence_sign.gopkg/cli/evidence_sign_test.gopkg/evidence/attestation/emit.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/sign.gopkg/evidence/attestation/sign_test.gopkg/evidence/verifier/fetch.go
47b2d6d to
4c0fb3e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/cli/evidence.go (1)
28-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winKeep the parent help text consistent with
sign.The summary paragraph still says only
publishreaches the OCI registry and Sigstore, butsignnow does too.aicr evidence --helpwill contradict itself until that sentence is updated.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/cli/evidence.go` around lines 28 - 43, Update the parent help text in the evidence CLI to match the current behavior of sign and publish. In the Cobra command setup for the evidence command, adjust the Description sentence that currently says only publish reaches the OCI registry and Sigstore so it also mentions sign, keeping the summary consistent with the subcommand list and the sign/publish flow.
♻️ Duplicate comments (1)
pkg/evidence/attestation/sign.go (1)
92-100: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReject pointer/artifact digest mismatches before signing.
BuildArtifactStatementandAttachSigstoreBundleAsReferrerboth useopts.Artifact.Digest, but the pointer you write back still keepsatt.Bundle.Digest. Right now a non-CLI caller can pass a resolved descriptor for one artifact and a pointer for another; this function will sign/attach the former while leaving the pointer pointing at the latter.🔧 Proposed fix
if opts.Artifact.Digest == "" || opts.Artifact.MediaType == "" || opts.Artifact.Size <= 0 { return errors.New(errors.ErrCodeInvalidRequest, "artifact descriptor (digest, mediaType, size) is required to sign an existing bundle") } + if att.Bundle.Digest != opts.Artifact.Digest { + return errors.New(errors.ErrCodeConflict, + "pointer bundle.digest does not match resolved artifact digest") + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/evidence/attestation/sign.go` around lines 92 - 100, The signing flow in sign.Bundle should validate that the pointer bundle digest matches opts.Artifact.Digest before proceeding, since BuildArtifactStatement and AttachSigstoreBundleAsReferrer currently sign the artifact descriptor while leaving att.Bundle.Digest unchanged. Add a digest equality check in the existing precondition block, returning an invalid request error when att.Bundle.Digest and opts.Artifact.Digest differ, so only matching pointer/artifact pairs can be signed and attached.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/evidence/attestation/pointer.go`:
- Around line 89-96: WritePointerFile currently uses os.WriteFile, which can
leave a partially written Pointer file if the process or filesystem fails
mid-write. Update WritePointerFile to write MarshalPointer(p) to a temp file in
the same directory, ensure the temp file is closed with Close() error checking,
then atomically replace the target with rename and preserve the existing error
wrapping in the WritePointerFile path.
---
Outside diff comments:
In `@pkg/cli/evidence.go`:
- Around line 28-43: Update the parent help text in the evidence CLI to match
the current behavior of sign and publish. In the Cobra command setup for the
evidence command, adjust the Description sentence that currently says only
publish reaches the OCI registry and Sigstore so it also mentions sign, keeping
the summary consistent with the subcommand list and the sign/publish flow.
---
Duplicate comments:
In `@pkg/evidence/attestation/sign.go`:
- Around line 92-100: The signing flow in sign.Bundle should validate that the
pointer bundle digest matches opts.Artifact.Digest before proceeding, since
BuildArtifactStatement and AttachSigstoreBundleAsReferrer currently sign the
artifact descriptor while leaving att.Bundle.Digest unchanged. Add a digest
equality check in the existing precondition block, returning an invalid request
error when att.Bundle.Digest and opts.Artifact.Digest differ, so only matching
pointer/artifact pairs can be signed and attached.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 1493e338-c6eb-43bc-af2e-32930b8f8e33
📒 Files selected for processing (9)
docs/user/cli-reference.mdpkg/cli/evidence.gopkg/cli/evidence_sign.gopkg/cli/evidence_sign_test.gopkg/evidence/attestation/emit.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/sign.gopkg/evidence/attestation/sign_test.gopkg/evidence/verifier/fetch.go
4c0fb3e to
1d7a945
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
pkg/evidence/attestation/sign.go (1)
92-108: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReject pointer/descriptor digest mismatches before signing.
SignExistingrequires both digests but never verifies they match. A non-CLI caller can sign and attach a referrer foropts.Artifact.Digestwhile patching a pointer whosebundle.digeststill names a different artifact, corrupting the pointer’s provenance.Proposed fix
if reference == "" || att.Bundle.Digest == "" { return errors.New(errors.ErrCodeInvalidRequest, "pointer has no pushed bundle to sign (bundle.oci/bundle.digest are empty)") } if opts.Artifact.Digest == "" || opts.Artifact.MediaType == "" || opts.Artifact.Size <= 0 { return errors.New(errors.ErrCodeInvalidRequest, "artifact descriptor (digest, mediaType, size) is required to sign an existing bundle") } + if att.Bundle.Digest != opts.Artifact.Digest { + return errors.New(errors.ErrCodeConflict, + "pointer bundle.digest does not match resolved artifact digest") + } bundle, _, err := loadOnDiskBundle(opts.BundleDir)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/evidence/attestation/sign.go` around lines 92 - 108, SignExisting currently checks that both the pointer bundle digest and the artifact descriptor digest are present, but it does not verify they refer to the same artifact before signing. In sign.go, update SignExisting to compare att.Bundle.Digest against opts.Artifact.Digest (using the existing digest normalization style in this flow) and return an invalid-request error when they differ, before loadOnDiskBundle and BuildArtifactStatement are used. Keep the check close to the existing validation for reference, digest, and artifact descriptor so mismatches are rejected before any signing or referrer attachment happens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/cli/evidence_sign.go`:
- Around line 98-102: The evidence signing command currently only reads
cmd.Args().First() in evidence_sign.go, so extra positional arguments are
ignored and can lead to signing the wrong pointer. Update the argument
validation in the sign command to enforce exactly one positional argument before
proceeding, and return an invalid-request error when the arg count is not one so
the ArgsUsage contract for a single <pointer> is respected.
In `@pkg/evidence/attestation/sign_test.go`:
- Around line 61-70: The negative-path tests in SignExisting currently only
check for a non-nil error, which can miss regressions in validation behavior.
Update the table-driven cases in sign_test.go to include the expected structured
error code for each scenario, then assert the returned error with errors.Is(...)
in the SignExisting test cases rather than only calling t.Fatalf on nil. Use the
existing SignExisting, SignExistingOptions, and the test case fields around
tt.name, tt.pointer, and tt.path to locate and extend the assertions.
---
Duplicate comments:
In `@pkg/evidence/attestation/sign.go`:
- Around line 92-108: SignExisting currently checks that both the pointer bundle
digest and the artifact descriptor digest are present, but it does not verify
they refer to the same artifact before signing. In sign.go, update SignExisting
to compare att.Bundle.Digest against opts.Artifact.Digest (using the existing
digest normalization style in this flow) and return an invalid-request error
when they differ, before loadOnDiskBundle and BuildArtifactStatement are used.
Keep the check close to the existing validation for reference, digest, and
artifact descriptor so mismatches are rejected before any signing or referrer
attachment happens.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 5eb583a7-cd90-4082-a629-de0d9566d9d3
📒 Files selected for processing (9)
docs/user/cli-reference.mdpkg/cli/evidence.gopkg/cli/evidence_sign.gopkg/cli/evidence_sign_test.gopkg/evidence/attestation/emit.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/sign.gopkg/evidence/attestation/sign_test.gopkg/evidence/verifier/fetch.go
1d7a945 to
ec33792
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
pkg/evidence/attestation/sign.go (1)
92-109: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReject pointer/artifact digest mismatches before signing.
Line 107 signs
opts.Artifact.Digest, but the pointer written back still keepsatt.Bundle.Digest. If those digests diverge, this path attaches a referrer to one artifact while the committed pointer still references another, which breaks later verification and leaves provenance inconsistent across layers.Proposed fix
reference := att.Bundle.OCI if reference == "" || att.Bundle.Digest == "" { return errors.New(errors.ErrCodeInvalidRequest, "pointer has no pushed bundle to sign (bundle.oci/bundle.digest are empty)") } if opts.Artifact.Digest == "" || opts.Artifact.MediaType == "" || opts.Artifact.Size <= 0 { return errors.New(errors.ErrCodeInvalidRequest, "artifact descriptor (digest, mediaType, size) is required to sign an existing bundle") } + if att.Bundle.Digest != opts.Artifact.Digest { + return errors.New(errors.ErrCodeConflict, + "pointer bundle.digest does not match the resolved artifact digest") + } bundle, _, err := loadOnDiskBundle(opts.BundleDir)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/evidence/attestation/sign.go` around lines 92 - 109, Reject pointer/artifact digest mismatches in the signing flow by validating that opts.Artifact.Digest matches att.Bundle.Digest before BuildArtifactStatement or any referrer attachment in sign.go; if they differ, return an invalid request error and avoid signing. Keep the check near the existing bundle/artifact validation in the sign path so the pointer updated by the caller remains consistent with the artifact actually signed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/cli/evidence_sign.go`:
- Around line 167-188: Move the signable-pointer validation out of the CLI
layer: `assertUnsignedSignable` in `pkg/cli/evidence_sign.go` duplicates the
invariant already enforced by `attestation.SignExisting`. Add or export a
validator from `pkg/evidence/attestation` (near `SignExisting` in `sign.go`) and
make the CLI call that shared check instead of maintaining its own business
rule. Keep `pkg/cli` focused on wiring, and update any CLI tests to exercise the
shared attestation validation path.
In `@pkg/evidence/attestation/sign.go`:
- Around line 145-148: Avoid updating opts.Pointer.Attestations[0].Signer before
WritePointerFile succeeds, because signPointer currently mutates the caller’s
in-memory pointer too early. In signPointer, keep the patched signer in a
temporary local copy, write that copy through WritePointerFile, and only assign
back to opts.Pointer after the write completes successfully. Make sure the retry
path and the att.Signer nil guard remain based on the original pointer state
until persistence is confirmed.
---
Duplicate comments:
In `@pkg/evidence/attestation/sign.go`:
- Around line 92-109: Reject pointer/artifact digest mismatches in the signing
flow by validating that opts.Artifact.Digest matches att.Bundle.Digest before
BuildArtifactStatement or any referrer attachment in sign.go; if they differ,
return an invalid request error and avoid signing. Keep the check near the
existing bundle/artifact validation in the sign path so the pointer updated by
the caller remains consistent with the artifact actually signed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 9925b991-ad2e-40a7-8b15-115725b41a5d
📒 Files selected for processing (9)
docs/user/cli-reference.mdpkg/cli/evidence.gopkg/cli/evidence_sign.gopkg/cli/evidence_sign_test.gopkg/evidence/attestation/emit.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/sign.gopkg/evidence/attestation/sign_test.gopkg/evidence/verifier/fetch.go
Add the sign-existing leg that completes the two-phase publish flow: a contributor pushes an unsigned bundle and commits the pointer (`evidence publish --no-sign`), then this command signs it later — the only step that needs Fulcio/Rekor egress, so it runs in CI. `aicr evidence sign <pointer>`: - Loads + validates the committed pointer, failing closed unless it is a single, already-pushed, not-yet-signed attestation (never re-signs). - Pulls the referenced bundle (verifier.MaterializeBundle) to reconstruct the predicate the signature binds and to resolve the artifact's full descriptor — MaterializedBundle now carries MediaType/Size, which a pointer (digest only) cannot supply. - attestation.SignExisting signs the predicate with ambient OIDC, attaches the Sigstore Bundle as an OCI referrer of the existing artifact (no re-emit), then patches the pointer's signer block in place and writes it back. Like Publish, it returns only an error and writes its artifact as a side effect; the registry-bound happy path is not unit-testable. Refactor: buildPointerInputsFromOutcome reuses the new PointerSignerFromSignature helper (shared zero-Rekor rule). WritePointer now delegates to a new WritePointerFile that writes to an exact path. Completes #1434 (sign-existing); follow-up to the --no-sign PR. Tests: pointer-state guards (assertUnsignedSignable: already-signed, no-bundle, multi-attestation), SignExisting input guards, the signature->pointer-signer mapping, and exact-path pointer writes.
ec33792 to
f3256ca
Compare
Summary
Adds
aicr evidence sign <pointer>— the sign-existing leg that completes the two-phase publish flow. A contributor pushes an unsigned bundle and commits the pointer (evidence publish --no-sign); this command signs it later (the only step that needs Fulcio/Rekor egress), attaches the Sigstore referrer, and patches the pointer's signer block in place.Motivation / Context
Completes the CLI/verifier work for the evidence-gate pilot. This is the second of the two CLI PRs and completes #1434; it is the step the fork-based CI signing workflow (#1433) will call.
Fixes: #1434
Related: #1431, #1433 (fork workflow consumes this)
Type of Change
Component(s) Affected
cmd/aicr,pkg/cli)docs/,examples/)pkg/evidence/{attestation,verifier}Implementation Notes
MaterializedBundlenow carriesMediaType/Size(populated from the pulled manifest descriptor) — the subject descriptor needed to attach a referrer, which a pointer (digest only) can't supply.attestation.SignExistingreads the predicate from the pulled bundle (no re-emit), signs with ambient OIDC, attaches the Sigstore Bundle as an OCI referrer, then patches the pointer's signer block and writes it back. MirroringPublish, it returns only an error and writes its artifact as a side effect — the registry-bound happy path isn't unit-testable.evidence signfails closed viaassertUnsignedSignable: refuses anything that isn't a single, already-pushed, not-yet-signed attestation (never re-signs over an existing signature).buildPointerInputsFromOutcomereuses the newPointerSignerFromSignature(shared zero-Rekor rule);WritePointerdelegates to a newWritePointerFile(exact-path write).Testing
New unit tests: pointer-state guards (
assertUnsignedSignable),SignExistinginput guards, signature→pointer-signer mapping, exact-path pointer writes. Registry/Fulcio-bound happy path not unit-tested (consistent withPublish).Risk Assessment
MaterializedBundlefields; existing paths unchanged.Rollout notes: Backwards-compatible. Depends conceptually on the
--no-signPR (produces the unsigned pointers this consumes) but has no compile dependency on it.Checklist
make testwith-race)make lint)git commit -S)