From 5304887ce9a37065015dd5a3ae60cc1855e1fc32 Mon Sep 17 00:00:00 2001 From: dev_agent Date: Sat, 3 Oct 2026 08:42:15 -0400 Subject: [PATCH] Reject non-canonical timestamps in Timestamp.Scan time.Parse accepts a comma before fractional seconds, so Scan took "2026-10-03T12:37:05,123Z" and Value then wrote it back with a period. Scan now requires the parsed instant to re-format byte-identically to its input, and the rejection test covers the comma form. Co-Authored-By: Claude Opus 5.5 --- internal/store/time.go | 3 +++ internal/store/time_test.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/store/time.go b/internal/store/time.go index 00be284..05a0401 100644 --- a/internal/store/time.go +++ b/internal/store/time.go @@ -47,6 +47,9 @@ func (t *Timestamp) Scan(src any) error { if err != nil { return fmt.Errorf("store: scan Timestamp %q: %w", s, err) } + if parsed.Format(timestampLayout) != s { + return fmt.Errorf("store: scan Timestamp %q: want %s", s, timestampLayout) + } t.Time = parsed return nil } diff --git a/internal/store/time_test.go b/internal/store/time_test.go index 5c77225..eb7d1cc 100644 --- a/internal/store/time_test.go +++ b/internal/store/time_test.go @@ -60,7 +60,10 @@ func TestTimestampRejects(t *testing.T) { if _, err := (Timestamp{}).Value(); err == nil { t.Error("zero Timestamp Value succeeded") } - for _, src := range []any{nil, int64(0), []byte("2026-10-03T12:37:05.123Z"), "2026-10-03T12:37:05Z", "2026-10-03T08:37:05.123-04:00", "garbage"} { + for _, src := range []any{ + nil, int64(0), []byte("2026-10-03T12:37:05.123Z"), "2026-10-03T12:37:05Z", + "2026-10-03T08:37:05.123-04:00", "2026-10-03T12:37:05,123Z", "garbage", + } { var ts Timestamp if err := ts.Scan(src); err == nil { t.Errorf("Scan(%#v) succeeded", src)