Skip to content

fix(gitlab): never crash on a null field in a GitLab reply - #1287

Open
AlexKantor87 wants to merge 12 commits into
mainfrom
claude/1283-gitlab
Open

AlexKantor87 wants to merge 12 commits into
mainfrom
claude/1283-gitlab

Conversation

@AlexKantor87

@AlexKantor87 AlexKantor87 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1283.

kosli attest pullrequest gitlab and kosli assert pullrequest gitlab crashed when GitLab sent a field as null where the code expected a value. The most common case: merged_at is null on every open merge request, so attesting a commit that belongs to one crashed. For now we treat every field as optional (#1284 will decide which fields are required).

What changed

  • Missing times are 0. One helper, unixOrZero, replaces the direct .Unix() calls on the merge request's created and merged times and the commit's created_at.
  • Users GitLab doesn't name (a deleted account) are never approvers. In reviews we keep one only if it requests changes, because dropping it would read as no objection. This is the same rule Bitbucket has on main.
  • Unreadable list entries still fail. noNullItems returns an error that names the list and index when merge requests, approvals, reviewers or commits contain a null entry. Skipping it could hide a "changes requested" review.
  • Errors where we can't carry on. A merge request with no iid (the next request needs it) or a commit with no id returns a readable error.
  • A null signature or approvals reply reads as unsigned or no approvers, the same as a 404.

Tests

odd_replies_test.go sends realistic replies through V2 and Hybrid (what assert and attest call) once per field, with that field made null. It checks there's never a crash, and never an error except the two cases above. Before the fix, 32 cases crashed. TestOddReplies_ReviewByNoOne covers reviews by deleted accounts.

Full CLI suite against a local server: 3,934 tests passed.

🤖 Generated with Claude Code

AlexKantor87 and others added 3 commits October 21, 2026 17:45
Part of #1283. Provider tests use these to send their evidence code a
realistic reply once per field with that field made null, or a list
emptied, and to name the outcome: ok, error or panic, plus which
evidence fields came back changed or missing.
The evidence code read times, users and list entries from GitLab
replies without nil checks, so a null merged_at (an MR that has not
merged) panicked assert pullrequest gitlab and attest pullrequest
gitlab.

Null times now give 0 and null users an empty name: that part of the
evidence is empty and the rest is kept. A null entry in a list (MRs,
approved_by, reviewers, commits) is an error naming the list and
index, because it is missing data, not an empty entry. A null
approvals or signature reply reads as none.

The new test feeds both command entry points a realistic reply with
each value made null, or each list emptied, and asserts that rule.

Part of #1283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A null signature reply now reads as unsigned, like a 404, so the comment
saying only a 404 does was wrong. Drops a comment that restated its
function name. Part of #1283.
@github-actions github-actions Bot added go Pull requests that update go code fix labels Oct 21, 2026
@claude

claude Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @AlexKantor87's task in 3m 10s —— View job


Follow-up review complete

  • Gather context (diff 337d7a4..c617515, prior review comments)
  • Assess status of each previous finding touched by new commits
  • Review changed lines for new bugs/regressions
  • Post summary comment

Summary comment posted via gh pr comment.

Branch: claude/1283-gitlab

Comment thread internal/gitlab/gitlab.go
Comment thread internal/gitlab/odd_replies_test.go
Comment thread internal/gitlab/odd_replies_test.go
Comment thread internal/gitlab/gitlab.go
Base automatically changed from claude/odd-reply-helper to main October 22, 2026 08:29
AlexKantor87 and others added 6 commits October 22, 2026 10:09
# Conflicts:
#	internal/testHelpers/oddReplies.go
#	internal/testHelpers/oddReplies_test.go
Snyk fails every branch on CVE-2026-111943, a high-severity flaw in Go's
crypto/x509 fixed in 1.26.10; go.mod declared 1.26.7.

The Dockerfile built from golang:1.26-alpine3.23, so each build could
pull a different toolchain. It now names golang:1.26.10-alpine3.23 by
digest. Dependabot can't update a FROM built from ARGs, so the line is
literal, and a docker ecosystem entry (golang only) keeps the digest
current.
An approved_by entry with no user was recorded as an approval by nobody,
which a policy counting approvers would accept: that fails open. A
reviewer with no user can't be matched to its approval either, and a
commit with no id was looked up at commits//signature and recorded as
unsigned with no SHA. Each is now an error naming the list entry, the
same rule as a null entry, checked in one place (noUnreadableItems).
A user without a username counts as no user, since the username is what
attributes and matches the entry.

Part of #1283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependabot bumps the pinned golang image on its own, and none of the
gomod updates touch go.mod's go directive. Snyk reads go.mod, so an
image-only bump would leave the next Go stdlib CVE flagged even though
the image is fixed. The test makes that bump red until go.mod moves too.
…error

GitLab sends a review whose account was deleted with no user. Erroring on
it would fail attestation for every MR ever approved by someone who has
since left, with no workaround. Such a review is now kept in Reviews with
no name and its state, but never counted as an approver, so approval
counting still fails closed. Nameless entries are never matched to each
other, so a nameless change request can't hide a nameless approval. A
commit with no id still errors.

Part of #1283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AlexKantor87

Copy link
Copy Markdown
Contributor Author

This branch now includes #1291's branch, so the Go 1.26.10 change clears the Snyk check here. That's why go.mod, Dockerfile, .github/dependabot.yml and internal/version/toolchain_test.go show in this diff. They're identical to #1291 and drop out of the diff once it merges. Please review those files on #1291, not here.

no search: answers no review finding; it brings in #1291's reviewed files unchanged (git diff origin/claude/go-1.26.10 HEAD on those four files is empty).

…283-gitlab

# Conflicts:
#	.github/dependabot.yml
@claude

claude Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Follow-up review: commits 964a6fc..337d7a4

The only change in this range is .github/dependabot.yml (dependabot cadence tweak, from merging #1291). internal/gitlab/gitlab.go and internal/gitlab/odd_replies_test.go — where all prior findings live — are untouched by these commits.

Status of previous findings (all already resolved by earlier commits 9b4460c / 1002181, which predate this range; GitHub review threads already marked resolved):

  1. gitlab.go:318 — null commit id: resolved, now errors before the signature lookup.
  2. odd_replies_test.go:205 — Hybrid/V2 duplicate subtest: resolved (kept intentionally as a guard, comment added).
  3. odd_replies_test.go:230 — map-literal readability nit: not addressed, author chose to keep current structure as a style preference (no further action needed).
  4. gitlab.go:213 — null-user approval counted as approver: resolved, now excluded from approvers (stays in reviews with empty name), consistent with other providers.

No new issues found — the new commits don't touch any of the previously-reviewed code.

View job run

@mbevc1

mbevc1 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor
  1. An approval from an unnamed user still counts as an approval (gitlabReviews, gitlab.go around line 276). Commit 1002181 removed nameless approvals from Approvers. The loop over approvedBy still adds {Username: "", State: APPROVED} to Reviews, and it adds unnamed “reviewed” entries as COMMENTED. TestOddReplies_ReviewByNoOne asserts this behaviour.
    The comment on PREvidence.Reviews says policies read that list. So the fail-open problem the earlier review raised is still there, through Reviews instead of Approvers.
    Bitbucket on main (from #1270, bitbucket.go:231) drops unnamed reviews and keeps an unnamed change request only. GitLab now behaves differently, although the author’s reply on that thread says “the same rule now applies on all four providers”.

The comment on gitlabUserName says such a review “can’t count as an approval”. That holds for Approvers but not for Reviews.
Suggested fix: in gitlabReviews, skip unnamed entries unless they are change requests, matching Bitbucket, and update the expected results in TestOddReplies_ReviewByNoOne.

  1. The PR description is out of date.
    “Not changed” says a null-user change request can hide a null-user approval. Commit 1002181 fixed that, and the test “change request by no one does not hide an approval by no one” now checks the opposite.
    “Missing users are empty … for the author, approvers and reviewers”: nameless approvers are now dropped, not kept with an empty name.
    “Never an error except a null MR iid”: a null commit id also errors now, per oddRuleExceptions.

AlexKantor87 and others added 2 commits October 25, 2026 14:00
Policies read Reviews as well as Approvers, so an approval or comment
kept there with no name still counted as evidence from nobody: the same
fail-open as an anonymous approver. A review with no account is now kept
in Reviews only when it requests changes, as dropping that would read as
none given. Same rule as Bitbucket's on main. Never an error.

The empty-username guards on matching are gone: with this rule nothing
an unnamed match could hide is ever kept.

Part of #1283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reviewer and approval maps now key every user with no account as "".
Say that this is safe only because add drops every nameless entry except a
change request, so a later change to add doesn't hide an approval silently.

Part of #1283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AlexKantor87

Copy link
Copy Markdown
Contributor Author

Thanks @mbevc1, you're right on both. My earlier reply was wrong: the rule wasn't the same on all four, and the Bitbucket PR had also dropped main's check.

1. Unnamed reviews. Fixed on all four with main's Bitbucket rule. An unnamed user is never an approver. In reviews we keep one only if it requests changes, because dropping it would read as no objection.

The gitlabUserName comment now says this for both lists.

search: grep -nE 'PRApprovals{|Reviews = append|approvers = append|Approvers = ' internal/{github,gitlab,bitbucket,azure}/*.go (non-test)

github.go:454 review := types.PRApprovals{        (after reviewer() keep check, :446)
github.go:464 Approvers = ApprovalsFrom(Reviews)  (APPROVED only)
github.go:806 approvers = append(approvers, login) (keep && APPROVED)
gitlab.go:254,256 approvers = append(...)          (approverName != "")
gitlab.go:282 reviews = append(...)                (inside add, change-request rule)
bitbucket.go:196,202 approvers = append(...)       (approvedBy(p) != "")
bitbucket.go:292 reviews = append(...)             (after change-request rule, :285)
azure.go:312,314 approvers = append(...)           (approverName != "")
azure.go:347 reviews = append(...)                 (after change-request rule, :344)

Every hit is either an approver append that needs a name, or a review append that goes through the change-request-only rule.

mutation: drop the change-request exception in gitlabReviews -> TestOddReplies_ReviewByNoOne red (2 cases)
mutation: drop the unnamed skip in gitlabReviews -> TestOddReplies_ReviewByNoOne red (5 cases)

The other three providers have the same two mutations, and each one fails their tests too.

2. Description. Updated for all four PRs to match what the code does now. That covers the change-request note you mentioned, unnamed approvers being dropped, and the commit id error.

@claude

claude Bot commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Follow-up review: commits 337d7a4..c617515

The new commits (ed4f9b35 fix + c6175154 docs) only touch gitlabReviews and its comment in internal/gitlab/gitlab.go, plus the corresponding cases in odd_replies_test.go.

Status of the 4 inline findings from the Oct 21 review round (all anchored outside the lines these commits touch, already resolved by earlier commits 9b4460c/1002181a, GitHub threads already marked resolved): no change — none of their locations were touched by this diff.

  1. gitlab.go:323 (null commit id) — not touched, stays resolved.
  2. odd_replies_test.go:205 (Hybrid/V2 duplicate) — not touched, stays resolved (kept as a guard).
  3. odd_replies_test.go:230 (map-literal readability nit) — not touched, author's style preference stands.
  4. gitlab.go:213/gitlabApprovers (nameless approval counted as approver) — not touched, stays resolved.

mbevc1's follow-up finding (nameless Reviews entries still fail open, e.g. an approval or "reviewed" comment from a deleted account counted via Reviews even though Approvers excluded it): resolved by ed4f9b35. gitlabReviews now routes every append through a new add helper that drops any entry with an empty name unless its state is ReviewChangesRequested. Traced through all TestOddReplies_ReviewByNoOne cases by hand, including the one where a nameless approver and a nameless change-requester share the collapsed "" map key — the shared key never changes the outcome, because add would drop a nameless approval/comment regardless of what that lookup returns. c6175154 documents exactly this invariant.

No new bugs or regressions found in the changed lines. 0 new inline comments.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants