From 30a840a6f1ae793f6967cb68c9c5fd4c5c16759b Mon Sep 17 00:00:00 2001
From: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
Date: Tue, 22 Sep 2026 13:42:12 +1200
Subject: [PATCH 1/4] feat: fallback to truncated description if advisory does
not have a summary
---
go/internal/website/list_models.go | 44 +++++++++++++++++
go/internal/website/list_models_test.go | 60 +++++++++++++++++++++++
website/frontend3/src/templates/list.html | 2 +-
3 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/go/internal/website/list_models.go b/go/internal/website/list_models.go
index 0c5340f4a5e..f37700b1c6c 100644
--- a/go/internal/website/list_models.go
+++ b/go/internal/website/list_models.go
@@ -4,6 +4,8 @@ import (
"fmt"
"strings"
"time"
+ "unicode"
+ "unicode/utf8"
"github.com/google/osv.dev/go/internal/models"
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -113,6 +115,48 @@ func (v ListedVulnerabilityDisplay) RemainingPackageCount() int {
return max(0, len(v.Packages)-5)
}
+// truncate ensures that the given string is shorter than 80 characters.
+//
+// If the string is longer than that limit, it's trimmed and suffixed with an ellipsis.
+// Ideally the string will be trimmed at the space that's closest to the limit to
+// preserve whole words; if a string has no spaces before the limit, it'll be forcefully truncated.
+func truncate(str string) string {
+ count := 0
+ truncateAt := -1
+
+ for i, c := range str {
+ if unicode.IsSpace(c) {
+ truncateAt = i
+ }
+
+ count++
+
+ if count >= 80 {
+ // ideally we want to keep words whole when truncating,
+ // but if we can't find a space just truncate at the limit
+ if truncateAt == -1 {
+ _, size := utf8.DecodeRuneInString(str[i:])
+ truncateAt = i + size
+ }
+
+ return str[:truncateAt] + "..."
+ }
+ }
+
+ return str
+}
+
+func (v ListedVulnerabilityDisplay) Describe() string {
+ builder := strings.Builder{}
+ if v.Details == "" {
+ builder.WriteString("See record for full details")
+ } else {
+ builder.WriteString(truncate(v.Details))
+ }
+
+ return builder.String()
+}
+
func cvssRank(t osvschema.Severity_Type) int {
switch t {
case osvschema.Severity_CVSS_V4:
diff --git a/go/internal/website/list_models_test.go b/go/internal/website/list_models_test.go
index 7c5bff81858..16e96c94a77 100644
--- a/go/internal/website/list_models_test.go
+++ b/go/internal/website/list_models_test.go
@@ -2,8 +2,10 @@ package website
import (
"reflect"
+ "strings"
"testing"
"time"
+ "unicode/utf8"
"github.com/google/osv.dev/go/internal/models"
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -164,3 +166,61 @@ func TestDisplayPackages(t *testing.T) {
t.Errorf("DisplayPackages() = %v, want %v", got, want)
}
}
+
+func TestDescribe(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ text string
+ want string
+ }{
+ {
+ text: "",
+ want: "See record for full details",
+ },
+ {
+ text: " ",
+ want: " ",
+ },
+ {
+ text: " ",
+ want: " ",
+ },
+ {
+ text: "this is a short description",
+ want: "this is a short description",
+ },
+ {
+ text: strings.Repeat("!", 100),
+ want: strings.Repeat("!", 80) + "...",
+ },
+ {
+ text: strings.Repeat("\u754c", 100),
+ want: strings.Repeat("\u754c", 80) + "...",
+ },
+ {
+ text: strings.Repeat("\u754c", 30) + " " + strings.Repeat("\u8a9e", 60),
+ want: strings.Repeat("\u754c", 30) + "...",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ display := ListedVulnerabilityDisplay{
+ Details: tt.text,
+ }
+
+ got := display.Describe()
+
+ if got != tt.want {
+ t.Fatalf("Describe() = %q, want %q", got, tt.want)
+ }
+ if !utf8.ValidString(got) {
+ t.Fatalf("Describe() returned invalid UTF-8: %q", got)
+ }
+ })
+ }
+}
diff --git a/website/frontend3/src/templates/list.html b/website/frontend3/src/templates/list.html
index 81360cd4bdb..f896134a30d 100644
--- a/website/frontend3/src/templates/list.html
+++ b/website/frontend3/src/templates/list.html
@@ -95,7 +95,7 @@
Vulnerabilities
{{ if .Summary }}
{{ .Summary }}
{{ else }}
- See record for full details
+ {{ .Describe }}
{{ end }}
From 198bd449c344aa6798bd7bf2b969d5402e905ac6 Mon Sep 17 00:00:00 2001
From: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
Date: Wed, 23 Sep 2026 07:05:25 +1200
Subject: [PATCH 2/4] feat: apply to datastore
---
.../datastore/listed_vulnerability.go | 49 ++++++++++++++-
.../datastore/listed_vulnerability_test.go | 61 +++++++++++++++++++
2 files changed, 109 insertions(+), 1 deletion(-)
diff --git a/go/internal/database/datastore/listed_vulnerability.go b/go/internal/database/datastore/listed_vulnerability.go
index 5398798db1e..cd07a0ce056 100644
--- a/go/internal/database/datastore/listed_vulnerability.go
+++ b/go/internal/database/datastore/listed_vulnerability.go
@@ -18,6 +18,8 @@ import (
"regexp"
"slices"
"strings"
+ "unicode"
+ "unicode/utf8"
"github.com/ossf/osv-schema/bindings/go/osvschema"
)
@@ -72,9 +74,54 @@ func removeVariants(ecosystem string) string {
return ""
}
+// truncate ensures that the given string is shorter than 80 characters.
+//
+// If the string is longer than that limit, it's trimmed and suffixed with an ellipsis.
+// Ideally the string will be trimmed at the space that's closest to the limit to
+// preserve whole words; if a string has no spaces before the limit, it'll be forcefully truncated.
+func truncate(str string) string {
+ count := 0
+ truncateAt := -1
+
+ for i, c := range str {
+ if unicode.IsSpace(c) {
+ truncateAt = i
+ }
+
+ count++
+
+ if count >= 80 {
+ // ideally we want to keep words whole when truncating,
+ // but if we can't find a space just truncate at the limit
+ if truncateAt == -1 {
+ _, size := utf8.DecodeRuneInString(str[i:])
+ truncateAt = i + size
+ }
+
+ return str[:truncateAt] + "..."
+ }
+ }
+
+ return str
+}
+
+func describe(vuln *osvschema.Vulnerability) string {
+ summary := vuln.GetSummary()
+
+ if summary == "" {
+ summary = vuln.GetDetails()
+
+ if summary != "" {
+ summary = truncate(summary)
+ }
+ }
+
+ return summary
+}
+
func NewListedVulnerabilityFromProto(vuln *osvschema.Vulnerability) *ListedVulnerability {
published := vuln.GetPublished().AsTime()
- summary := vuln.GetSummary()
+ summary := describe(vuln)
allEcosystems := make(map[string]struct{})
allPackages := make(map[string]struct{})
diff --git a/go/internal/database/datastore/listed_vulnerability_test.go b/go/internal/database/datastore/listed_vulnerability_test.go
index 578cf4dada9..364e062f3e3 100644
--- a/go/internal/database/datastore/listed_vulnerability_test.go
+++ b/go/internal/database/datastore/listed_vulnerability_test.go
@@ -1,8 +1,10 @@
package datastore
import (
+ "strings"
"testing"
"time"
+ "unicode/utf8"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
@@ -106,3 +108,62 @@ func TestNewListedVulnerabilityFromProto(t *testing.T) {
t.Errorf("NewListedVulnerabilityFromProto mismatch (-want +got):\n%s", diff)
}
}
+
+func TestNewListedVulnerabilityFromProto_Summary(t *testing.T) {
+ tests := []struct {
+ name string
+ text string
+ want string
+ }{
+ {
+ text: "",
+ want: "See record for full details",
+ },
+ {
+ text: " ",
+ want: " ",
+ },
+ {
+ text: " ",
+ want: " ",
+ },
+ {
+ text: "this is a short description",
+ want: "this is a short description",
+ },
+ {
+ text: strings.Repeat("!", 100),
+ want: strings.Repeat("!", 80) + "...",
+ },
+ {
+ text: strings.Repeat("\u754c", 100),
+ want: strings.Repeat("\u754c", 80) + "...",
+ },
+ {
+ text: strings.Repeat("\u754c", 30) + " " + strings.Repeat("\u8a9e", 60),
+ want: strings.Repeat("\u754c", 30) + "...",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ vuln := &osvschema.Vulnerability{
+ Id: "TEST-123",
+ Published: timestamppb.New(time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC)),
+ Summary: "This is a vuln",
+ Details: tt.text,
+ }
+
+ got := NewListedVulnerabilityFromProto(vuln).Summary
+
+ if got != tt.want {
+ t.Fatalf("Summary = %q, want %q", got, tt.want)
+ }
+ if !utf8.ValidString(got) {
+ t.Fatalf("Summary returned invalid UTF-8: %q", got)
+ }
+ })
+ }
+}
From 2fdff353242dd8523ce849dac296941ceb4758ec Mon Sep 17 00:00:00 2001
From: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
Date: Wed, 23 Sep 2026 07:10:30 +1200
Subject: [PATCH 3/4] revert: remove changes from website
This reverts commit 7d46fa9e6aca6ea5b457d8a2d00be0138fc38290.
---
go/internal/website/list_models.go | 44 -----------------
go/internal/website/list_models_test.go | 60 -----------------------
website/frontend3/src/templates/list.html | 2 +-
3 files changed, 1 insertion(+), 105 deletions(-)
diff --git a/go/internal/website/list_models.go b/go/internal/website/list_models.go
index f37700b1c6c..0c5340f4a5e 100644
--- a/go/internal/website/list_models.go
+++ b/go/internal/website/list_models.go
@@ -4,8 +4,6 @@ import (
"fmt"
"strings"
"time"
- "unicode"
- "unicode/utf8"
"github.com/google/osv.dev/go/internal/models"
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -115,48 +113,6 @@ func (v ListedVulnerabilityDisplay) RemainingPackageCount() int {
return max(0, len(v.Packages)-5)
}
-// truncate ensures that the given string is shorter than 80 characters.
-//
-// If the string is longer than that limit, it's trimmed and suffixed with an ellipsis.
-// Ideally the string will be trimmed at the space that's closest to the limit to
-// preserve whole words; if a string has no spaces before the limit, it'll be forcefully truncated.
-func truncate(str string) string {
- count := 0
- truncateAt := -1
-
- for i, c := range str {
- if unicode.IsSpace(c) {
- truncateAt = i
- }
-
- count++
-
- if count >= 80 {
- // ideally we want to keep words whole when truncating,
- // but if we can't find a space just truncate at the limit
- if truncateAt == -1 {
- _, size := utf8.DecodeRuneInString(str[i:])
- truncateAt = i + size
- }
-
- return str[:truncateAt] + "..."
- }
- }
-
- return str
-}
-
-func (v ListedVulnerabilityDisplay) Describe() string {
- builder := strings.Builder{}
- if v.Details == "" {
- builder.WriteString("See record for full details")
- } else {
- builder.WriteString(truncate(v.Details))
- }
-
- return builder.String()
-}
-
func cvssRank(t osvschema.Severity_Type) int {
switch t {
case osvschema.Severity_CVSS_V4:
diff --git a/go/internal/website/list_models_test.go b/go/internal/website/list_models_test.go
index 16e96c94a77..7c5bff81858 100644
--- a/go/internal/website/list_models_test.go
+++ b/go/internal/website/list_models_test.go
@@ -2,10 +2,8 @@ package website
import (
"reflect"
- "strings"
"testing"
"time"
- "unicode/utf8"
"github.com/google/osv.dev/go/internal/models"
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -166,61 +164,3 @@ func TestDisplayPackages(t *testing.T) {
t.Errorf("DisplayPackages() = %v, want %v", got, want)
}
}
-
-func TestDescribe(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- text string
- want string
- }{
- {
- text: "",
- want: "See record for full details",
- },
- {
- text: " ",
- want: " ",
- },
- {
- text: " ",
- want: " ",
- },
- {
- text: "this is a short description",
- want: "this is a short description",
- },
- {
- text: strings.Repeat("!", 100),
- want: strings.Repeat("!", 80) + "...",
- },
- {
- text: strings.Repeat("\u754c", 100),
- want: strings.Repeat("\u754c", 80) + "...",
- },
- {
- text: strings.Repeat("\u754c", 30) + " " + strings.Repeat("\u8a9e", 60),
- want: strings.Repeat("\u754c", 30) + "...",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- display := ListedVulnerabilityDisplay{
- Details: tt.text,
- }
-
- got := display.Describe()
-
- if got != tt.want {
- t.Fatalf("Describe() = %q, want %q", got, tt.want)
- }
- if !utf8.ValidString(got) {
- t.Fatalf("Describe() returned invalid UTF-8: %q", got)
- }
- })
- }
-}
diff --git a/website/frontend3/src/templates/list.html b/website/frontend3/src/templates/list.html
index f896134a30d..81360cd4bdb 100644
--- a/website/frontend3/src/templates/list.html
+++ b/website/frontend3/src/templates/list.html
@@ -95,7 +95,7 @@ Vulnerabilities
{{ if .Summary }}
{{ .Summary }}
{{ else }}
- {{ .Describe }}
+ See record for full details
{{ end }}
From eabca2a87f9dee24722803d5ad0660ef5efe645e Mon Sep 17 00:00:00 2001
From: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
Date: Mon, 28 Sep 2026 13:55:34 +1300
Subject: [PATCH 4/4] test: actually update specs
---
.../datastore/listed_vulnerability_test.go | 52 ++++++++++++-------
1 file changed, 33 insertions(+), 19 deletions(-)
diff --git a/go/internal/database/datastore/listed_vulnerability_test.go b/go/internal/database/datastore/listed_vulnerability_test.go
index 364e062f3e3..fefb5654de3 100644
--- a/go/internal/database/datastore/listed_vulnerability_test.go
+++ b/go/internal/database/datastore/listed_vulnerability_test.go
@@ -111,37 +111,51 @@ func TestNewListedVulnerabilityFromProto(t *testing.T) {
func TestNewListedVulnerabilityFromProto_Summary(t *testing.T) {
tests := []struct {
- name string
- text string
- want string
+ name string
+ summary string
+ details string
+ want string
}{
+ {want: ""},
{
- text: "",
- want: "See record for full details",
+ summary: "hello world",
+ details: "hello sunshine",
+ want: "hello world",
},
{
- text: " ",
- want: " ",
+ summary: "",
+ details: "hello sunshine",
+ want: "hello sunshine",
},
{
- text: " ",
- want: " ",
+ summary: " ",
+ details: "hello sunshine",
+ want: " ",
},
{
- text: "this is a short description",
- want: "this is a short description",
+ summary: " ",
+ details: "hello sunshine",
+ want: " ",
},
{
- text: strings.Repeat("!", 100),
- want: strings.Repeat("!", 80) + "...",
+ summary: strings.Repeat("!", 100),
+ details: "hello sunshine",
+ want: strings.Repeat("!", 100),
},
{
- text: strings.Repeat("\u754c", 100),
- want: strings.Repeat("\u754c", 80) + "...",
+ summary: "",
+ details: strings.Repeat("!", 100),
+ want: strings.Repeat("!", 80) + "...",
},
{
- text: strings.Repeat("\u754c", 30) + " " + strings.Repeat("\u8a9e", 60),
- want: strings.Repeat("\u754c", 30) + "...",
+ summary: "",
+ details: strings.Repeat("\u754c", 100),
+ want: strings.Repeat("\u754c", 80) + "...",
+ },
+ {
+ summary: "",
+ details: strings.Repeat("\u754c", 30) + " " + strings.Repeat("\u8a9e", 60),
+ want: strings.Repeat("\u754c", 30) + "...",
},
}
@@ -152,8 +166,8 @@ func TestNewListedVulnerabilityFromProto_Summary(t *testing.T) {
vuln := &osvschema.Vulnerability{
Id: "TEST-123",
Published: timestamppb.New(time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC)),
- Summary: "This is a vuln",
- Details: tt.text,
+ Summary: tt.summary,
+ Details: tt.details,
}
got := NewListedVulnerabilityFromProto(vuln).Summary