← the record

Review a2a3af29d0c0

internal/transparency at 6951ca4c11, reviewed by codex:gpt-6-astra, verified by claude-code:claude-fable-5-1.

repo
git@github.com:pdbethke/corralai
commit
6951ca4c1173d359100724f0a64c64f605f57246
scope
internal/transparency · go
reviewer
codex:gpt-6-astra (codex-cli 0.154.0)
verifier
claude-code:claude-fable-5-1 (2.1.272 (Claude Code)) different model than the reviewer different vendor: openai → anthropic
substrate
workspace (a detached worktree at the commit; not a jail)
started
2026-09-15 13:07 UTC
audited party
Peter D Bethke · co-authored: Claude Fable 5.1
files shown
9 file(s)

Findings (1)

R1 medium reproduced confirmed by a person

toEntry accepts a non-nil but empty inclusion proof, allowing Anchor to return success and the build caller to record anchored=true for an entry that VerifyInclusion rejects as incomplete.

where
internal/transparency/rekor.go:206
tier
REPRODUCED (declared REPRODUCED)
the script that was executed
#!/bin/sh
set -eu
f=internal/transparency/review_incomplete_anchor_test.go
[ ! -e "$f" ] || exit 1
out=$(mktemp)
trap 'rm -f "$f" "$out"' EXIT HUP INT TERM
cat > "$f" <<'GO'
package transparency

import (
    "encoding/base64"
    "fmt"
    "strings"
    "testing"
    "github.com/go-openapi/strfmt"
    "github.com/sigstore/rekor/pkg/generated/models"
)

func TestReviewIncompleteAnchorProof(t *testing.T) {
    idx, tm := int64(7), int64(1700000000)
    id := "00"
    w := &rekorWitness{}
    entry, err := w.toEntry(models.LogEntryAnon{
        LogIndex: &idx,
        IntegratedTime: &tm,
        LogID: &id,
        Body: base64.StdEncoding.EncodeToString([]byte(`{"kind":"dsse"}`)),
        Verification: &models.LogEntryAnonVerification{
            InclusionProof: &models.InclusionProof{},
            SignedEntryTimestamp: strfmt.Base64([]byte("present")),
        },
    })
    if err != nil {
        t.Fatalf("defect not demonstrated: constructor rejected incomplete proof: %v", err)
    }
    ok, reason := w.VerifyInclusion(entry, []byte(`{"payload":"cA==","signatures":[{"sig":"QUFB"}]}`))
    if ok || !strings.Contains(reason, "incomplete") {
        t.Fatalf("expected rejection specifically for incomplete proof; got ok=%v reason=%q", ok, reason)
    }
    fmt.Printf("DEFECT: toEntry returned nil error with proof=%s; VerifyInclusion returned false: %s\n", entry.InclusionProof, reason)
}
GO
if timeout 50s env GOPROXY=off GOSUMDB=off GOTOOLCHAIN=local go test ./internal/transparency -run '^TestReviewIncompleteAnchorProof$' -count=1 -timeout=10s -v > "$out" 2>&1; then
    cat "$out"
    grep -q '^DEFECT:' "$out"
else
    cat "$out"
    exit 1
fi
what it printed
=== RUN   TestReviewIncompleteAnchorProof
DEFECT: toEntry returned nil error with proof={"checkpoint":null,"hashes":null,"logIndex":null,"rootHash":null,"treeSize":null}; VerifyInclusion returned false: inclusion proof is incomplete: validation failure list:
checkpoint in body is required
hashes in body is required
logIndex in body is required
rootHash in body is required
treeSize in body is required
--- PASS: TestReviewIncompleteAnchorProof (0.00s)
PASS
ok  	github.com/pdbethke/corralai/internal/transparency	0.007s

refutation — claude-code:claude-fable-5-1 STANDS

I tried to refute on three fronts and failed on all. (1) The constructor: rekor.go:206 is a nil-pointer check only; nothing between it and the Entry literal at rekor.go:234 validates the proof's required fields, and json.Marshal of a zero models.InclusionProof succeeds, producing exactly the all-null proof the reviewer's output shows. (2) The verifier: rekor.go:287 calls parseInclusionProof (rekor.go:643-652), which calls proof.Validate and returns 'inclusion proof is incomplete', so the Entry Anchor hands back is one VerifyInclusion necessarily refuses — the claim's asymmetry is exact. (3) The callers: internal/brain/buildcert.go:172-187 and internal/advpool/gate.go:1185-1198 both set anchored=true on a nil Anchor error after only a json.Marshal, with no VerifyInclusion round-trip, so anchored=true is recorded for an unverifiable entry. The Anchor re-fetch at rekor.go:144 does not close this: it fires only when InclusionProof is nil, not when it is an empty struct. The only counter-argument available is that the public Rekor may never actually return a non-nil empty proof, but that is an argument about the peer, not about the code, and the repository's own stated rule at rekor.go:209-215 is that the building door must require what the verifying door requires. The reviewer's script is a faithful reproduction of the claim as stated.

human verdict

confirmed by pdbethke — real, reproduced: toEntry accepted an empty inclusion proof, so Anchor succeeded and anchored=true was recorded for an entry VerifyInclusion rejects. The reviewer's script exited 0 and the verifier failed to refute it. Fixed in #347 (toEntry now calls parseInclusionProof); TestAnchorRefusesAnEmptyInclusionProof failed on main before the fix.

ref a2a3af29d0c0b3525644b8332926a35b52999da4f8c86b77e46b710e232e921f#R1

Looked at and could not break (7)

Recorded so that "found nothing here" can be told apart from "never looked here".

Reviewer's opinion

Finding 1 leaves a validation gap between constructing and verifying witness entries: an incomplete proof can be stored as successfully anchored, although verification necessarily rejects it. Review covered all nine scoped files and relevant callers and dependency code. No code was executed; the reproduction script is supplied for independent execution.

Verifier's opinion

The single finding is accurate and the reviewer's script demonstrates exactly what it claims. toEntry at internal/transparency/rekor.go:206 checks only that Verification.InclusionProof is a non-nil pointer, while VerifyInclusion at rekor.go:287 goes through parseInclusionProof, which runs the swagger model's own Validate and refuses an incomplete proof. The same asymmetry that round four fixed for the SET (the comment at rekor.go:209-218 states the rule: what the verifier requires, the constructor must require) is left open for the proof's completeness. Both build callers — internal/brain/buildcert.go:172-187 and internal/advpool/gate.go:1185-1198 — marshal the returned Entry and set anchored=true without calling VerifyInclusion, so the consequence stated in the finding is real. I could not narrow the claim: the Anchor re-fetch at rekor.go:144 triggers only on a nil pointer, so a non-nil-but-empty proof in the create response flows straight to toEntry.