fix(merge gate): bind pr_merge and tbd_ship merges to the head commit the gate checked (TOCTOU)
The gate evaluates CI for one head sha; a push to the PR branch between that check and the merge would have merged unchecked code. MergePRArgs now carries head_commit_id (documented in Gitea 1.27.1's MergePullRequestOption) and both tools send the sha they evaluated. Tests for both tools; each removal flip-tested red. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SZkhi49hfeSqBp5aZJNXsM
This commit is contained in:
@@ -3,6 +3,7 @@ package tools_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -63,6 +64,8 @@ func shipFakeFull(t *testing.T, runsJSON string, requiredApprovals, mergeStatus
|
||||
case r.Method == http.MethodGet && strings.Contains(p, "/actions/runs"):
|
||||
_, _ = w.Write([]byte(`{"total_count":0,"workflow_runs":` + runsJSON + `}`))
|
||||
case r.Method == http.MethodPost && strings.HasSuffix(p, "/pulls/7/merge"):
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
shipLastMergeBody.Store(string(body))
|
||||
merged.Store(true)
|
||||
w.WriteHeader(mergeStatus)
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
@@ -216,6 +219,23 @@ func TestTBDShip_Resume_ExistingBranchAndPR(t *testing.T) {
|
||||
assert.True(t, merged.Load())
|
||||
}
|
||||
|
||||
// shipLastMergeBody is the body of the most recent merge request the fake received.
|
||||
var shipLastMergeBody atomic.Value
|
||||
|
||||
// TOCTOU: the merge is bound to the head commit whose CI the gate evaluated (gitea-mcp#74).
|
||||
func TestTBDShip_TheMergeIsBoundToTheCommitTheGateChecked(t *testing.T) {
|
||||
var merged, deleted atomic.Bool
|
||||
srv := shipFake(t, shipGreen, &merged, &deleted)
|
||||
defer srv.Close()
|
||||
|
||||
res := callShip(t, srv.URL, shipArgs)
|
||||
|
||||
require.Equal(t, true, res["merged"])
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(shipLastMergeBody.Load().(string)), &payload))
|
||||
assert.Equal(t, "abc", payload["head_commit_id"], "the head sha the CI check used")
|
||||
}
|
||||
|
||||
// ---- gitea-mcp#63: the risk tier is an input to the gate. Only exactly one risk/low label merges. ----
|
||||
const (
|
||||
shipGreen = `[{"id":1,"status":"completed","conclusion":"success","head_sha":"abc"}]`
|
||||
|
||||
Reference in New Issue
Block a user