fix(evidence): relocate idempotency, strict digest, package docs#1543
Conversation
…kage-write docs Non-blocking follow-ups to NVIDIA#1538 (NVIDIA#1530): - attestation: relocation idempotency now closes the committed-both case. The EEXIST recovery recognized only same-inode dests (os.SameFile), so after a git round trip — where the flat source and the committed nested copy are distinct inodes but byte-equal — a retry hit a spurious conflict. Add a content-equality fallback (bounded read + bytes.Equal); only a dest holding DIFFERENT bytes is a real immutable-pointer conflict. - attestation: isHexDigest now requires exactly sha256:<64 lowercase hex>, so a malformed digest (sha256:nothex, truncated, wrong algo) is rejected rather than any hex-ish string. Test fixtures updated to full-length digests. - docs: document the fork package-WRITE prerequisites for the CI sign leg — the package's Actions access must include the fork with the Write role, and a locally-pushed package may be linked to NVIDIA/aicr rather than the fork. Not changed here: --relocate trusts the on-disk signer without cryptographic bundle verification — the same metadata-only trust the contract gate applies (not a new escalation), tracked repo-wide in NVIDIA#1535. Signed-off-by: Nathan Hensley <[email protected]>
Recipe evidence checkNo leaf overlays affected by this PR. This gate is warning-only and never blocks merge. |
📝 WalkthroughWalkthroughThe changes tighten evidence pointer handling by requiring full canonical Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ 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 `@pkg/evidence/attestation/pointer_test.go`:
- Around line 207-209: The RelocatePointerToCanonical conflict test currently
only checks for any error, so it could miss a regression that returns the wrong
error type. Update the assertion in the RelocatePointerToCanonical test to
verify the structured conflict code by checking ErrCodeConflict on the returned
error, keeping the existing canonical-path conflict contract pinned.
🪄 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: a025db1e-7659-4dfe-9aef-dd9202f1128f
📒 Files selected for processing (4)
docs/contributor/evidence-publishing.mdpkg/cli/evidence_sign_test.gopkg/evidence/attestation/pointer.gopkg/evidence/attestation/pointer_test.go
…1543) Review feedback (CodeRabbit): the different-content clobber test only checked err != nil, so an INTERNAL regression would still pass. Assert the structured ErrCodeConflict via stderrors.Is so the EEXIST conflict contract stays pinned. Signed-off-by: Nathan Hensley <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/evidence/attestation/pointer_test.go (1)
323-324: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPin
ErrCodeInvalidRequestin the invalid-digest cases.This only checks
err != nil, so the test still passes if the canonical-digest guard regresses and relocation later fails on the missingx.yamlwith an internal filesystem error. Assert the structured invalid-request code here to keep the pre-filesystem rejection contract pinned.Suggested change
- if _, err := RelocatePointerToCanonical(filepath.Join(t.TempDir(), "x.yaml"), evil); err == nil { - t.Errorf("digest %q: expected a rejection, got nil", digest) - } + if _, err := RelocatePointerToCanonical(filepath.Join(t.TempDir(), "x.yaml"), evil); !stderrors.Is(err, errors.New(errors.ErrCodeInvalidRequest, "")) { + t.Errorf("digest %q: expected ErrCodeInvalidRequest, got %v", digest, err) + }🤖 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/pointer_test.go` around lines 323 - 324, The invalid-digest test currently only checks for a non-nil error, so it can miss a regression where `RelocatePointerToCanonical` fails later for the wrong reason. Update the invalid-digest cases in `pointer_test.go` to assert the structured `ErrCodeInvalidRequest` from the error returned by `RelocatePointerToCanonical`, keeping the canonical-digest rejection contract pinned before any filesystem lookup 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.
Outside diff comments:
In `@pkg/evidence/attestation/pointer_test.go`:
- Around line 323-324: The invalid-digest test currently only checks for a
non-nil error, so it can miss a regression where `RelocatePointerToCanonical`
fails later for the wrong reason. Update the invalid-digest cases in
`pointer_test.go` to assert the structured `ErrCodeInvalidRequest` from the
error returned by `RelocatePointerToCanonical`, keeping the canonical-digest
rejection contract pinned before any filesystem lookup happens.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: aca290aa-4948-4d92-8c3a-20394bbaca42
📒 Files selected for processing (1)
pkg/evidence/attestation/pointer_test.go
mchmarny
left a comment
There was a problem hiding this comment.
Approving — clean, well-tested follow-up to #1538. (Re-reviewed at 7d99f4d: the only change since the prior head was tightening the conflict test to assert ErrCodeConflict via stderrors.Is instead of bare non-nil — strictly better.)
- Strict digest (
isHexDigest→ exactsha256:+64 lowercase hex) is correctly wired intocanonicalPointerSuffixbeforefilepath.Join, alongside thefilepath.IsLocal(p.Recipe)guard. Both attacker-influenced path segments now fail closed — this fully closes the digest-traversal concern from #1538, and the previously-spurioussha256:..case now genuinely exercises the guard. - Idempotent recovery via
pointerAlreadyPlaced(same-inode OR byte-identical) correctly handles the committed-both git-round-trip case; deterministic serialization means byte-equality ⟺ same signing output, so only differing bytes trip the conflict. Boundedos.Open+io.LimitReaderread, fail-closed on read error — matches repo patterns. - Package-write docs are the right operator-side complement to the workflow
packages: writegrant.
Tests cover the new paths well (byte-identical-distinct-inode recovery, different-content conflict with code assertion, same-inode recovery, full strict-digest matrix). The --relocate unverified-signer item is appropriately scoped out to #1535. Nothing blocks merge once e2e lands.
Summary
Non-blocking follow-ups to the merged evidence relocate flow (#1538 / #1530): close the relocation idempotency gap on a committed-both retry, tighten the bundle-digest guard to a canonical
sha256:<64-hex>, and document the fork package-write prerequisites for the CI signing leg.Motivation / Context
These were raised as non-blocking review items on #1538 and tracked for a quick follow-up.
Fixes: N/A
Related: #1538, #1530, #1535
Type of Change
Component(s) Affected
cmd/aicr,pkg/cli)pkg/errors,pkg/k8s) —pkg/evidence/attestationdocs/,examples/)Implementation Notes
pkg/evidence/attestation/pointer.go): theos.LinkEEXIST recovery recognized only same-inode dests (os.SameFile), so after a git round trip — where the flat source and the committed nested copy are distinct inodes but byte-equal — a retry hit a spuriousErrCodeConflict. Added a bounded-read +bytes.Equalfallback (pointerAlreadyPlaced); only a dest holding different bytes is a real immutable-pointer conflict.isHexDigest): now requires exactlysha256:+ 64 lowercase hex, rejectingsha256:nothex, truncated hashes, and wrong-algorithm prefixes instead of any hex-ish string. Test fixtures updated to the real 64-char h100 digest.docs/contributor/evidence-publishing.md): documented the two GHCR write prerequisites the sign leg needs — the package's Actions access must include the fork with the Write role, and a locally-pushed package may be linked toNVIDIA/aicrrather than the fork and must be re-linked.--relocatetrusts the on-disk signer block without cryptographic bundle verification — the same metadata-only trust the contract gate applies (not a new escalation), tracked repo-wide in Evidence pointer-contract gate trusts pointer-supplied signer (no crypto verification) #1535.Testing
New/updated tests: relocation recovery on byte-identical dest (distinct inode), conflict on different-content dest, same-inode partial-move recovery, and digest-guard cases for non-hex / too-short / too-long / wrong-prefix.
Risk Assessment
Rollout notes: No migration. N/A.
Checklist
make testwith-race)make lint)git commit -S)