internal/task: add additional lint for CVE and GitHub issue references

Updates golang/go#76157

Change-Id: Ibc46594f6493e7dc2744a832d1ef27255644fa39
Reviewed-on: https://go-review.googlesource.com/c/build/+/773860
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/task/security_release_coalesce.go b/internal/task/security_release_coalesce.go
index e9e76b4..a0d5029 100644
--- a/internal/task/security_release_coalesce.go
+++ b/internal/task/security_release_coalesce.go
@@ -166,8 +166,10 @@
 }
 
 func (x *SecurityReleaseCoalesceTask) CheckChanges(ctx *wf.TaskContext, clNums []string) ([]*gerrit.ChangeInfo, error) {
-	var cls []*gerrit.ChangeInfo
-
+	var (
+		cls      []*gerrit.ChangeInfo
+		lintErrs []error
+	)
 	for _, num := range clNums {
 		ci, err := x.PrivateGerrit.GetChange(ctx, num, gerrit.QueryChangesOpt{Fields: []string{"SUBMITTABLE"}})
 		if err != nil {
@@ -183,12 +185,27 @@
 		if ra["submit"] == nil || !ra["submit"].Enabled {
 			return nil, fmt.Errorf("Change %s is not submittable", internalGerritChangeURL(num))
 		}
+		cm, err := x.PrivateGerrit.GetCommitMessage(ctx, num)
+		if err != nil {
+			return nil, err
+		}
+		if !cveRE.MatchString(cm) {
+			lintErrs = append(lintErrs, fmt.Errorf("Change %s is missing CVE reference", internalGerritChangeURL(num)))
+		}
+		if !githubIssueRE.MatchString(cm) {
+			lintErrs = append(lintErrs, fmt.Errorf("Change %s is missing GitHub issue reference", internalGerritChangeURL(num)))
+		}
 		cls = append(cls, ci)
 	}
 
-	return cls, nil
+	return cls, errors.Join(lintErrs...)
 }
 
+var (
+	cveRE         = regexp.MustCompile(`(?m)^Fixes CVE-\d{4}-\d+`)
+	githubIssueRE = regexp.MustCompile(`(?m)^Fixes (?:golang/go)?#(\d+)`)
+)
+
 func (x *SecurityReleaseCoalesceTask) CreateCheckpoint(ctx *wf.TaskContext, bi branchInfo) (string, error) {
 	publicHead, err := x.PrivateGerrit.ReadBranchHead(ctx, "go", "public")
 	if err != nil {
diff --git a/internal/task/security_release_coalesce_test.go b/internal/task/security_release_coalesce_test.go
index 784bc9e..adada29 100644
--- a/internal/task/security_release_coalesce_test.go
+++ b/internal/task/security_release_coalesce_test.go
@@ -196,8 +196,8 @@
 	}
 
 	privGerrit.commitMessages = map[string]string{
-		"1234": "subject: 1234\n\nbody",
-		"5678": "subject: 5678\n\nother body",
+		"1234": commitMsg1234,
+		"5678": commitMsg5678,
 	}
 
 	head = privGoRepo.History()[0]
@@ -236,21 +236,21 @@
 		"internal-release-branch.go1.4.2": {
 			{
 				changeID: "1234",
-				message:  "[release-branch.go1.4] subject: 1234\n\nbody",
+				message:  "[release-branch.go1.4] " + commitMsg1234,
 			},
 			{
 				changeID: "5678",
-				message:  "[release-branch.go1.4] subject: 5678\n\nother body",
+				message:  "[release-branch.go1.4] " + commitMsg5678,
 			},
 		},
 		"internal-release-branch.go1.3.2": {
 			{
 				changeID: "1234",
-				message:  "[release-branch.go1.3] subject: 1234\n\nbody",
+				message:  "[release-branch.go1.3] " + commitMsg1234,
 			},
 			{
 				changeID: "5678",
-				message:  "[release-branch.go1.3] subject: 5678\n\nother body",
+				message:  "[release-branch.go1.3] " + commitMsg5678,
 			},
 		},
 	}
@@ -259,11 +259,11 @@
 		expected["internal-release-branch.go1.5rc1"] = []cherryPickedCommit{
 			{
 				changeID: "1234",
-				message:  "[release-branch.go1.5] subject: 1234\n\nbody",
+				message:  "[release-branch.go1.5] " + commitMsg1234,
 			},
 			{
 				changeID: "5678",
-				message:  "[release-branch.go1.5] subject: 5678\n\nother body",
+				message:  "[release-branch.go1.5] " + commitMsg5678,
 			},
 		}
 	}
@@ -274,3 +274,8 @@
 		}
 	}
 }
+
+const (
+	commitMsg1234 = "go2/types: type confusion inverts flux capacitor\n\nFixes CVE-1985-0703\nFixes golang/go#1"
+	commitMsg5678 = "cmd/compile: import rustc to fix go\n\nFixes CVE-1970-0001\nFixes #4294967296"
+)