diff --git a/agent/format/jsonformat/encoding.go b/agent/format/jsonformat/encoding.go index f601bbd3..5ba45a3f 100644 --- a/agent/format/jsonformat/encoding.go +++ b/agent/format/jsonformat/encoding.go @@ -3,8 +3,10 @@ package jsonformat import ( + "bytes" "encoding/json" "fmt" + "io" "github.com/google/jsonschema-go/jsonschema" ) @@ -69,9 +71,18 @@ func (f *Format) Normalize(v any) error { func applySchema(data json.RawMessage, resolved *jsonschema.Resolved) (json.RawMessage, error) { var v any if len(data) > 0 { - if err := json.Unmarshal(data, &v); err != nil { + // Decode with UseNumber so integers beyond 2^53 are not silently + // truncated by being decoded into float64 and re-marshalled. + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + if err := dec.Decode(&v); err != nil { return nil, fmt.Errorf("unmarshaling arguments: %w", err) } + // Unlike json.Unmarshal, json.Decoder tolerates trailing data after the + // first value; reject it so validation is not looser than before. + if _, err := dec.Token(); err != io.EOF { + return nil, fmt.Errorf("unmarshaling arguments: unexpected trailing data after JSON value") + } } if err := resolved.ApplyDefaults(&v); err != nil { return nil, fmt.Errorf("applying schema defaults: %w", err) diff --git a/agent/format/jsonformat/encoding_test.go b/agent/format/jsonformat/encoding_test.go index 3371a4d7..191dadab 100644 --- a/agent/format/jsonformat/encoding_test.go +++ b/agent/format/jsonformat/encoding_test.go @@ -133,3 +133,30 @@ func TestNormalizeEmptyStruct(t *testing.T) { t.Fatalf("Normalize: %v", err) } } + +// Integer arguments beyond 2^53 must survive Unmarshal: decoding through an +// interface{} as float64 would silently truncate them. +func TestFormat_Unmarshal_PreservesLargeIntegerPrecision(t *testing.T) { + type output struct { + N int64 `json:"n"` + } + format := requireFormat(t, jsonformat.MustFor[output]()) + var out output + if err := format.Unmarshal([]byte(`{"n":9007199254740993}`), &out); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if out.N != 9007199254740993 { + t.Errorf("N = %d, want 9007199254740993 (large-integer precision lost)", out.N) + } +} + +func TestFormat_Unmarshal_RejectsTrailingData(t *testing.T) { + type output struct { + N int `json:"n"` + } + format := requireFormat(t, jsonformat.MustFor[output]()) + var out output + if err := format.Unmarshal([]byte(`{"n":1} {"n":2}`), &out); err == nil { + t.Fatal("expected an error for trailing data after the JSON value, got nil") + } +}