diff --git a/internal/relui/buildrelease_test.go b/internal/relui/buildrelease_test.go
index b93c1bd..9f14739 100644
--- a/internal/relui/buildrelease_test.go
+++ b/internal/relui/buildrelease_test.go
@@ -249,7 +249,7 @@
wd := workflow.New(workflow.ACL{})
deps.gerrit.wantReviewers = []string{"heschi", "dmitshur"}
- v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, major, kind, workflow.Const(deps.gerrit.wantReviewers))
+ _, v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, major, kind, workflow.Const(deps.gerrit.wantReviewers))
workflow.Output(wd, "Published Go version", v)
w, err := workflow.Start(wd, map[string]any{
@@ -504,7 +504,7 @@
// Run the release.
wd := workflow.New(workflow.ACL{})
- v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, 26, task.KindMinor, workflow.Slice[string]())
+ _, v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, 26, task.KindMinor, workflow.Slice[string]())
workflow.Output(wd, "Published Go version", v)
w, err := workflow.Start(wd, map[string]any{
@@ -549,7 +549,7 @@
// Run the release.
wd := workflow.New(workflow.ACL{})
- v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, 26, task.KindMinor, workflow.Slice[string]())
+ _, v := addSingleReleaseWorkflow(deps.buildTasks, deps.milestoneTasks, deps.versionTasks, wd, 26, task.KindMinor, workflow.Slice[string]())
workflow.Output(wd, "Published Go version", v)
w, err := workflow.Start(wd, map[string]any{
diff --git a/internal/relui/workflows.go b/internal/relui/workflows.go
index 64160bd..f483fd0 100644
--- a/internal/relui/workflows.go
+++ b/internal/relui/workflows.go
@@ -427,15 +427,15 @@
coordinators := wf.Param(wd, releaseCoordinators)
- published := addSingleReleaseWorkflow(build, milestone, version, wd, r.major, r.kind, coordinators)
+ nextVersion, published := addSingleReleaseWorkflow(build, milestone, version, wd, r.major, r.kind, coordinators)
securitySummary := wf.Const("")
securityFixes := wf.Slice[string]()
if r.kind == task.KindMinor || r.kind == task.KindRC {
if r.useMetadata {
milestoneNum := wf.Param(wd, task.SecurityMilestoneParameter)
- securitySummary = wf.Task1(wd, "Get short security content summary from metadata", comm.GetSecuritySummary, milestoneNum)
- securityFixes = wf.Task1(wd, "Get security release notes from metadata", comm.GetSecurityReleaseNotes, milestoneNum)
+ securitySummary = wf.Task2(wd, "Get short security content summary from metadata", comm.GetSecuritySummary, milestoneNum, wf.Slice(nextVersion))
+ securityFixes = wf.Task2(wd, "Get security release notes from metadata", comm.GetSecurityReleaseNotes, milestoneNum, wf.Slice(nextVersion))
} else {
securitySummary = wf.Param(wd, securitySummaryParameter)
securityFixes = wf.Param(wd, securityFixesParameter)
@@ -492,8 +492,8 @@
wd := wf.New(wf.ACL{Groups: []string{groups.ReleaseTeam}})
coordinators := wf.Param(wd, releaseCoordinators)
- currPublished := addSingleReleaseWorkflow(build, milestone, version, wd.Sub(fmt.Sprintf("Go 1.%d", currentMajor)), currentMajor, task.KindMinor, coordinators)
- prevPublished := addSingleReleaseWorkflow(build, milestone, version, wd.Sub(fmt.Sprintf("Go 1.%d", prevMajor)), prevMajor, task.KindMinor, coordinators)
+ currNextVersion, currPublished := addSingleReleaseWorkflow(build, milestone, version, wd.Sub(fmt.Sprintf("Go 1.%d", currentMajor)), currentMajor, task.KindMinor, coordinators)
+ prevNextVersion, prevPublished := addSingleReleaseWorkflow(build, milestone, version, wd.Sub(fmt.Sprintf("Go 1.%d", prevMajor)), prevMajor, task.KindMinor, coordinators)
var (
securitySummary wf.Value[string]
@@ -501,8 +501,8 @@
)
if useMetadata {
milestoneNum := wf.Param(wd, task.SecurityMilestoneParameter)
- securitySummary = wf.Task1(wd, "Get short security content summary from metadata", comm.GetSecuritySummary, milestoneNum)
- securityFixes = wf.Task1(wd, "Get security release notes from metadata", comm.GetSecurityReleaseNotes, milestoneNum)
+ securitySummary = wf.Task2(wd, "Get short security content summary from metadata", comm.GetSecuritySummary, milestoneNum, wf.Slice(prevNextVersion, currNextVersion))
+ securityFixes = wf.Task2(wd, "Get security release notes from metadata", comm.GetSecurityReleaseNotes, milestoneNum, wf.Slice(prevNextVersion, currNextVersion))
} else {
securitySummary = wf.Param(wd, securitySummaryParameter)
securityFixes = wf.Param(wd, securityFixesParameter)
@@ -540,7 +540,10 @@
func addSingleReleaseWorkflow(
build *BuildReleaseTasks, milestone *task.MilestoneTasks, version *task.VersionTasks,
wd *wf.Definition, major int, kind task.ReleaseKind, coordinators wf.Value[[]string],
-) wf.Value[task.Published] {
+) (
+ nextVersion wf.Value[string],
+ _ wf.Value[task.Published],
+) {
kindVal := wf.Const(kind)
branch := fmt.Sprintf("release-branch.go1.%d", major)
if kind == task.KindBeta {
@@ -550,7 +553,7 @@
startingHead := wf.Task1(wd, "Read starting branch head", version.ReadBranchHead, branchVal)
// Select version, check milestones.
- nextVersion := wf.Task2(wd, "Get next version", version.GetNextVersion, wf.Const(major), kindVal)
+ nextVersion = wf.Task2(wd, "Get next version", version.GetNextVersion, wf.Const(major), kindVal)
timestamp := wf.Task0(wd, "Timestamp release", now)
versionFile := wf.Task2(wd, "Generate VERSION file", version.GenerateVersionFile, nextVersion, timestamp)
wf.Output(wd, "VERSION file", versionFile)
@@ -784,7 +787,7 @@
wf.Output(wd, "Google Docker image status", dockerResult)
wf.Output(wd, "Published to website", published)
- return published
+ return nextVersion, published
}
// sourceSpec encapsulates all the information that describes a source archive.
diff --git a/internal/task/announce.go b/internal/task/announce.go
index 998f5cb..13f86fb 100644
--- a/internal/task/announce.go
+++ b/internal/task/announce.go
@@ -15,6 +15,7 @@
"net/http"
"net/mail"
"net/url"
+ "slices"
"strings"
"text/template"
"time"
@@ -32,6 +33,7 @@
"golang.org/x/build/internal/secret"
"golang.org/x/build/internal/workflow"
"golang.org/x/build/maintner/maintnerd/maintapi/version"
+ "golang.org/x/build/relmeta"
"golang.org/x/net/html"
)
@@ -934,18 +936,21 @@
// Includes {N} security fixes to {the standard library|the toolchain|the standard library and the toolchain}.
//
// It returns an empty string when there are 0 security fixes.
-func (t SecurityCommunicationTasks) GetSecuritySummary(ctx *workflow.TaskContext, milestoneNum string) (string, error) {
+//
+// versions are the Go versions that the security summary is intended to cover.
+func (t SecurityCommunicationTasks) GetSecuritySummary(ctx *workflow.TaskContext, milestoneNum string, versions []string) (string, error) {
rm, err := fetchReleaseMilestone(ctx, t.PrivateGerrit, milestoneNum)
if err != nil {
return "", err
}
- ctx.Printf("fetched security release milestone containing %d security fixes", len(rm.Patches))
- if len(rm.Patches) == 0 {
+ patches := getPatchesForVersions(rm, versions)
+ ctx.Printf("fetched security release milestone containing %d security fix(es), of which %d apply to %s", len(rm.Patches), len(patches), versions)
+ if len(patches) == 0 {
// No security fixes.
return "", nil
}
var std, toolchain bool
- for _, p := range rm.Patches {
+ for _, p := range patches {
if p.Toolchain {
toolchain = true
} else {
@@ -961,19 +966,42 @@
case std && toolchain:
components = "the standard library and the toolchain"
}
- return fmt.Sprintf("Includes %d security fixes to %s.", len(rm.Patches), components), nil
+ var what = "security fix"
+ if len(patches) > 1 {
+ what = "security fixes"
+ }
+ return fmt.Sprintf("Includes %d %s to %s.", len(patches), what, components), nil
}
// GetSecurityReleaseNotes fetches a list of descriptions, one for each distinct security fix
// included in the release identified by milestoneNum, in Markdown format.
-func (t SecurityCommunicationTasks) GetSecurityReleaseNotes(ctx *workflow.TaskContext, milestoneNum string) (releaseNotes []string, _ error) {
+//
+// versions are the Go versions that the security release notes are intended to cover.
+func (t SecurityCommunicationTasks) GetSecurityReleaseNotes(ctx *workflow.TaskContext, milestoneNum string, versions []string) (releaseNotes []string, _ error) {
rm, err := fetchReleaseMilestone(ctx, t.PrivateGerrit, milestoneNum)
if err != nil {
return nil, err
}
- ctx.Printf("fetched security release milestone containing %d security fixes", len(rm.Patches))
- for _, p := range rm.Patches {
+ patches := getPatchesForVersions(rm, versions)
+ ctx.Printf("fetched security release milestone containing %d security fix(es), of which %d apply to %s", len(rm.Patches), len(patches), versions)
+ for _, p := range patches {
releaseNotes = append(releaseNotes, p.ReleaseNote)
}
return releaseNotes, nil
}
+
+// getPatchesForVersions returns security patches from rm that apply to versions.
+func getPatchesForVersions(rm relmeta.ReleaseMilestone, versions []string) (patches []*relmeta.SecurityPatch) {
+ relevantVersions := make(map[string]bool)
+ for _, v := range versions {
+ // Convert to the semver syntax used in the [relmeta.ReleaseMilestone.TargetReleases] field.
+ relevantVersions[strings.ReplaceAll(strings.TrimPrefix(v, "go"), "rc", ".0-rc.")] = true
+ }
+ for _, p := range rm.Patches {
+ if !slices.ContainsFunc(p.TargetReleases, func(v string) bool { return relevantVersions[v] }) {
+ continue
+ }
+ patches = append(patches, p)
+ }
+ return patches
+}
diff --git a/internal/task/announce_test.go b/internal/task/announce_test.go
index 301807f..1554fce 100644
--- a/internal/task/announce_test.go
+++ b/internal/task/announce_test.go
@@ -729,8 +729,9 @@
This is CVE-2022-24675 and https://go.dev/issue/51853.
target_releases:
- - go1.3.1
- - go1.4.1
+ - 1.23.15
+ - 1.24.1
+ - 1.25.0-rc.2
- id: 40027190
is_toolchain: true
package: cmd/go
@@ -741,10 +742,11 @@
release_note: |-
cmd/go: unexpected command execution in untrusted VCS repositories
+ This issue only affects Go 1.24.
+
This is CVE-2025-4674 and https://go.dev/issue/74380.
target_releases:
- - go1.3.1
- - go1.4.1`
+ - 1.24.1`
smRepo := NewFakeRepo(t, "security-metadata")
head := smRepo.History()[0]
@@ -754,9 +756,10 @@
tasks := SecurityCommunicationTasks{
PrivateGerrit: NewFakeGerrit(t, smRepo),
}
+
t.Run("Summary", func(t *testing.T) {
ctx := &workflow.TaskContext{Context: t.Context(), Logger: &testLogger{t: t}}
- got, err := tasks.GetSecuritySummary(ctx, "100001")
+ got, err := tasks.GetSecuritySummary(ctx, "100001", []string{"go1.23.15", "go1.24.1"})
if err != nil {
t.Fatal(err)
}
@@ -765,9 +768,21 @@
t.Errorf("summary mismatch (-want +got):\n%s", diff)
}
})
+ t.Run("Summary-single-minor", func(t *testing.T) {
+ ctx := &workflow.TaskContext{Context: t.Context(), Logger: &testLogger{t: t}}
+ got, err := tasks.GetSecuritySummary(ctx, "100001", []string{"go1.23.15"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := "Includes 1 security fix to the standard library."
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("summary mismatch (-want +got):\n%s", diff)
+ }
+ })
+
t.Run("ReleaseNotes", func(t *testing.T) {
ctx := &workflow.TaskContext{Context: t.Context(), Logger: &testLogger{t: t}}
- got, err := tasks.GetSecurityReleaseNotes(ctx, "100001")
+ got, err := tasks.GetSecurityReleaseNotes(ctx, "100001", []string{"go1.23.15", "go1.24.1"})
if err != nil {
t.Fatal(err)
}
@@ -781,10 +796,31 @@
This is CVE-2022-24675 and https://go.dev/issue/51853.`,
`cmd/go: unexpected command execution in untrusted VCS repositories
+This issue only affects Go 1.24.
+
This is CVE-2025-4674 and https://go.dev/issue/74380.`,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("release note mismatch (-want +got):\n%s", diff)
}
})
+ t.Run("ReleaseNotes-rc-only", func(t *testing.T) {
+ ctx := &workflow.TaskContext{Context: t.Context(), Logger: &testLogger{t: t}}
+ got, err := tasks.GetSecurityReleaseNotes(ctx, "100001", []string{"go1.25rc2"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := []string{
+ `encoding/pem: fix stack overflow in Decode
+
+A large (more than 5 MB) PEM input can cause a stack overflow in Decode, leading the program to crash.
+
+Thanks to Juho Nurminen of Mattermost who reported the error.
+
+This is CVE-2022-24675 and https://go.dev/issue/51853.`,
+ }
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("release note mismatch (-want +got):\n%s", diff)
+ }
+ })
}
diff --git a/internal/task/security_release_coalesce_test.go b/internal/task/security_release_coalesce_test.go
index adada29..df798b2 100644
--- a/internal/task/security_release_coalesce_test.go
+++ b/internal/task/security_release_coalesce_test.go
@@ -151,8 +151,8 @@
changelists:
- https://go.dev/cl/123456
target_releases:
- - go1.3.1
- - go1.4.1
+ - 1.3.1
+ - 1.4.1
- id: 40027190
package: runtime
track: PRIVATE
@@ -160,8 +160,8 @@
- https://go-internal-review.git.corp.google.com/c/security-metadata/+/1234
- https://go-internal-review.git.corp.google.com/c/security-metadata/+/5678
target_releases:
- - go1.3.1
- - go1.4.1`})
+ - 1.3.1
+ - 1.4.1`})
privGerrit := &fakeCoalesceGerrit{
FakeGerrit: NewFakeGerrit(t, privGoRepo, smRepo),
diff --git a/relmeta/relmeta.go b/relmeta/relmeta.go
index bc302d6..03c38d1 100644
--- a/relmeta/relmeta.go
+++ b/relmeta/relmeta.go
@@ -25,18 +25,23 @@
// SecurityPatch is a self-contained body
// of work that addresses a vulnerability.
type SecurityPatch struct {
- ID int64 `yaml:"id"`
- Track GoSecurityTrack `yaml:"track"`
- Toolchain bool `yaml:"is_toolchain"`
- Package string `yaml:"package"`
- Changelists []string `yaml:"changelists"`
- ReleaseNote string `yaml:"release_note"`
- TargetReleases []string `yaml:"target_releases,omitempty"`
- GitHubIssueID int64 `yaml:"github_issue_id"`
- Credits []string `yaml:"credits"`
- CVE string `yaml:"cve"`
- CWE string `yaml:"cwe"`
- VulnReport report.Report `yaml:"vuln_report"`
+ ID int64 `yaml:"id"`
+ Track GoSecurityTrack `yaml:"track"`
+ Toolchain bool `yaml:"is_toolchain"`
+ Package string `yaml:"package"`
+ Changelists []string `yaml:"changelists"`
+ ReleaseNote string `yaml:"release_note"`
+ // TargetReleases is a list of the target Go versions for this security patch.
+ // Versions are expressed in SemVer syntax with no "v" prefix rather than the
+ // https://go.dev/doc/toolchain#name syntax.
+ // For example, ["1.25.12", "1.26.5", "1.27.0-rc.2"] means this patch applies
+ // to Go 1.25.12, Go 1.26.5, and Go 1.27 Release Candidate 2.
+ TargetReleases []string `yaml:"target_releases"`
+ GitHubIssueID int64 `yaml:"github_issue_id"`
+ Credits []string `yaml:"credits"`
+ CVE string `yaml:"cve"`
+ CWE string `yaml:"cwe"`
+ VulnReport report.Report `yaml:"vuln_report"`
}
type GoSecurityTrack string