internal/database: set modification timestamp from git history

Set the "modified" field in OSV reports to the timestamp of the last
change in the git history of the appropriate .yaml.

Update code comments for setDates: Setting the publication date
no longer speeds up generation (since we always pull the git history
for the repo), and this function intentionally does not set the
LastModified field (to avoid potential confusion if a report has
a hardcoded, out-of-date LastModified field.)

Fixes golang/vulndb#50434.

Change-Id: Id5f8009d84f450fc9dc87889fb227be7ea17d575
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/392539
Trust: Damien Neil <dneil@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/vulnreport/main.go b/cmd/vulnreport/main.go
index afd004f..0470668 100644
--- a/cmd/vulnreport/main.go
+++ b/cmd/vulnreport/main.go
@@ -330,9 +330,14 @@
 //
 // It isn't crucial to run this for every report, because the same logic exists
 // in gendb, ensuring that every report has a PublishedDate before being
-// transformed into a DB entry. The advantages of using this command are that it
-// speeds up gendb, and the dates become permanent (if you create and submit a
-// CL after running it).
+// transformed into a DB entry. The advantage of using this command is that
+// the dates become permanent (if you create and submit a CL after running it).
+//
+// This intentionally does not set the LastModified of the report: While the
+// publication date of a report may be expected not to change, the modification
+// date can. Always using the git history as the source of truth for the
+// last-modified date avoids confusion if the report YAML and the git history
+// disagree.
 func setDates(filename string, dates map[string]gitrepo.Dates) (err error) {
 	defer derrors.Wrap(&err, "setDates(%q)", filename)
 
diff --git a/internal/database/generate.go b/internal/database/generate.go
index b844f1a..f4670df 100644
--- a/internal/database/generate.go
+++ b/internal/database/generate.go
@@ -62,14 +62,25 @@
 		if err != nil {
 			return err
 		}
+
+		yamlPath := filepath.Join(yamlDir, f.Name())
+		dates, ok := commitDates[yamlPath]
+		if !ok {
+			return fmt.Errorf("can't find git repo commit dates for %q", yamlPath)
+		}
+		// If a report contains a published field, consider it
+		// the authoritative source of truth. Otherwise, set
+		// the published field from the git history.
 		if r.Published.IsZero() {
-			yamlPath := filepath.Join(yamlDir, f.Name())
-			dates, ok := commitDates[yamlPath]
-			if !ok {
-				return fmt.Errorf("can't find git repo commit dates for %q", yamlPath)
-			}
 			r.Published = dates.Oldest
 		}
+		// Always set the last_modified field based on git history.
+		// The alternative is to possibly miss modifications to any
+		// report with a checked-in last_modified field.
+		if newest := dates.Newest; !dates.Oldest.Equal(newest) {
+			r.LastModified = &newest
+		}
+
 		if lints := r.Lint(); len(lints) > 0 {
 			return fmt.Errorf("vuln.Lint: %v", lints)
 		}