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