Skip to content

Add support for git commit verification#3905

Open
omehegan wants to merge 5 commits into
feat/git-checkout-featuresfrom
owen/SUP-6535
Open

Add support for git commit verification#3905
omehegan wants to merge 5 commits into
feat/git-checkout-featuresfrom
owen/SUP-6535

Conversation

@omehegan
Copy link
Copy Markdown
Contributor

@omehegan omehegan commented May 7, 2026

Description

Adds commit-on-branch verification to the checkout phase.

As part of our agent checkout improvement work, we are adding support for git commit verification. This addresses a potential security issue: if an attacker adds a malicious commit to the repo, and is then able to trigger a build which specifies commit: 3v1l5ha123 branch: main we would build that commit as if it were on the main branch, without verifying that this is the case, which could potentially lead to a production deployment of malicious code.

This change introduces a BUILDKITE_GIT_COMMIT_VERIFICATION environment variable, which defaults to false. If set to true, when we are given a commit and branch, before checkout and build, we do the following:

  1. Use git merge-base --is-ancestor to check if the commit really belongs to the specified branch.
  2. If it does, proceed; if it definitely does not, fail and exit.
  3. In ambiguous situations, it's possible that we only have a shallow clone of the repo. In that case we:
  4. Deepen the clone by 50 commits, with the assumption that this should be enough to find the specified SHA.
  5. If we still get an ambiguous result, to a full unshallow and check again.
  6. Proceed if the commit matches, fail and exit if it does not.

In terms of alternatives, the only change I considered was just going straight to unshallow before verifying any commit. But I didn't want users to have to completely give up shallow commits to get verification, so I went with the "deepen first, then unshallow if necessary" approach.

Context

See SUP-6535.

Changes

Adds a BUILDKITE_GIT_COMMIT_VERIFICATION env var to enable to functionality. When set, we perform various git operations in between fetch and checkout, to make sure the provided commit is valid on the provided branch.

Testing

  • Tests have run locally (with go test ./...). Buildkite employees may check this if the pipeline has run automatically.
  • Code is formatted (with go tool gofumpt -extra -w .)

Disclosures / Credits

I used Claude to help me plan the feature and teach me some Go fundamentals. I coded the core functionality myself, and let Claude write the tests.

@omehegan omehegan requested review from a team as code owners May 7, 2026 06:36
@omehegan omehegan marked this pull request as draft May 7, 2026 06:36
@omehegan omehegan changed the base branch from main to feat/git-checkout-features May 8, 2026 03:37
@omehegan omehegan marked this pull request as ready for review May 14, 2026 04:32
Copy link
Copy Markdown
Contributor

@zhming0 zhming0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the value in this change, but I left some questions re overall directions and the cost / gain asymmetry. Also I recommend relocating some codes to another file.

Comment thread internal/job/checkout.go
Comment on lines +883 to +896
if e.Tag != "" {
return nil
}

// Skip if this is a PR build — the commit may be on a merge ref, not the target branch
if e.PullRequest != "" {
return nil
}

// Skip if a custom refspec is set — the fetch may not populate standard branch refs,
// making ancestry verification unreliable
if e.RefSpec != "" {
return nil
}
Copy link
Copy Markdown
Contributor

@zhming0 zhming0 May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I totally see the value of this change adding a level of protection, I worry that it's a bit narrow given all these bypass conditions.

Also on the direction, it's ultimately saying: "under X case, do not trust Buildkite backend". It's a questionable direction for us to invest in though we have prior arts (I think). But I do wonder if we could/should strengthen the backend to not send false information.

It's not strictly a blocker, but something worth a number of words to explain about.

Comment thread internal/job/checkout.go
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this change is largely isolated from the happy path of checkout.go and checkout.go is getting really big. Would it be possible move them to maybe e.g. checkout_verify_commit.go?

This comment applies to the test code in checkout_test.go too

Comment thread internal/job/checkout.go
Comment on lines +844 to +846
// Still 128 - full unshallow as last resort
e.shell.Commentf("Deepening insufficient, performing a full unshallow...")
_ = e.shell.Command("git", "fetch", "--unshallow").Run(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the cost outweigh the gain here? Now the equation become, if someone put a random commit SHA in the build api, our agent will basically do a repo clone -> which depending on situation can take a long time.

An realistically, building for a old commit in CI is pretty rare 🤔

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants