Summary
A git ref specifying both #tag= and #commit= is accepted, and which one wins is nondeterministic: both option keys write the same internal field while ref options are iterated in Go map order. branch+commit and ref+commit are explicitly rejected, so this combination slipping through is an inconsistency. (Borderline case: it requires self-contradictory user input, but a confused user writing "tag v1.0.0, which is commit deadbeef" gets a silently nondeterministic fetch instead of an error — and a ref like this committed to CI produces non-reproducible builds.)
Reproduction
buf build 'https://github.com/org/repo.git#tag=v1.0.0,commit=deadbeef'
# accepted; fetches either the tag or the commit depending on map iteration order of that run
Root cause
private/buf/buffetch/internal/ref_parser.go:141-142:
case "tag", "commit":
rawRef.GitCommitOrTag = value
Both keys collapse into GitCommitOrTag, and options come from a map (random iteration order), so the last write wins nondeterministically. By the time validateRawRef runs (ref_parser.go:344-347), only one value remains, so the existing branch/ref-vs-commitOrTag conflict checks cannot detect it.
Expected
Reject tag+commit together, matching the existing branch+commit and ref+commit errors.
Found via a full mutest (mutation-testing) run over this repo; verified by source inspection.
Summary
A git ref specifying both
#tag=and#commit=is accepted, and which one wins is nondeterministic: both option keys write the same internal field while ref options are iterated in Go map order.branch+commitandref+commitare explicitly rejected, so this combination slipping through is an inconsistency. (Borderline case: it requires self-contradictory user input, but a confused user writing "tag v1.0.0, which is commit deadbeef" gets a silently nondeterministic fetch instead of an error — and a ref like this committed to CI produces non-reproducible builds.)Reproduction
Root cause
private/buf/buffetch/internal/ref_parser.go:141-142:Both keys collapse into
GitCommitOrTag, and options come from a map (random iteration order), so the last write wins nondeterministically. By the timevalidateRawRefruns (ref_parser.go:344-347), only one value remains, so the existing branch/ref-vs-commitOrTag conflict checks cannot detect it.Expected
Reject
tag+committogether, matching the existingbranch+commitandref+commiterrors.Found via a full mutest (mutation-testing) run over this repo; verified by source inspection.