{"record":{"id":"5ae5eada8a41111f","repo":"JuliusBrussee/caveman","slug":"number-exceeds-the-supported-jsonb-numeric-size","errorCode":null,"errorMessage":"number exceeds the supported JSONB numeric size","messagePattern":"number exceeds the supported JSONB numeric size","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/proposalrun/proposalrun.go","lineNumber":173,"sourceCode":"\t}\n\tif value, err := number.Float64(); err == nil && !math.IsInf(value, 0) && !math.IsNaN(value) {\n\t\tlegacy, err := json.Marshal(value)\n\t\tif err == nil {\n\t\t\tlegacyExact, legacyErr := normalizeJSONNumber(string(legacy))\n\t\t\tif legacyErr == nil && legacyExact == exact {\n\t\t\t\treturn canonicalJSONNumber(string(legacy)), nil\n\t\t\t}\n\t\t}\n\t}\n\treturn canonicalJSONNumber(exact), nil\n}\n\n// normalizeJSONNumber converts one JSON number literal to the shortest plain\n// decimal that has exactly the same base-10 value. It deliberately avoids\n// floating-point conversion; all arithmetic is over digit counts and strings.\nfunc normalizeJSONNumber(literal string) (string, error) {\n\tif len(literal) == 0 || len(literal) > maxJSONBNumberLexeme {\n\t\treturn \"\", fmt.Errorf(\"number exceeds the supported JSONB numeric size\")\n\t}\n\tstart := 0\n\tnegative := false\n\tif literal[0] == '-' {\n\t\tnegative = true\n\t\tstart = 1\n\t}\n\tif start == len(literal) {\n\t\treturn \"\", fmt.Errorf(\"invalid JSON number\")\n\t}\n\n\tmantissaEnd := len(literal)\n\texponent := 0\n\tfor i := start; i < len(literal); i++ {\n\t\tif literal[i] != 'e' && literal[i] != 'E' {\n\t\t\tcontinue\n\t\t}\n\t\tif mantissaEnd != len(literal) {","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/proposalrun/proposalrun.go#L155-L191","documentation":"normalizeJSONNumber rejects a JSON number literal whose lexeme is empty or longer than maxJSONBNumberLexeme (147456 characters: 131072 integer digits + 16383 fraction digits + 1 + 16 slack). This is a pre-allocation guard: PostgreSQL jsonb stores numbers as numeric with documented bounds, so a literal that huge either cannot be persisted or would drive unbounded intermediate string arithmetic. The error is wrapped by 'proposalrun: canonicalize detail: %w' from CanonicalDetail.","triggerScenarios":"CanonicalDetail/RowHash on detail JSON containing a number literal longer than 147456 characters (e.g. a 500000-digit integer, or a number padded with hundreds of thousands of fraction digits), or a degenerate empty number token reaching the normalizer.","commonSituations":"Hostile or fuzzed payloads forwarded into tool-call details; a data pipeline accumulating digits (e.g. concatenated precision from a bignum library); accidental string-to-number conversion of a huge identifier; DOS-style inputs attempting to blow up hash computation.","solutions":["Measure the offending literal's length and trim it to within PostgreSQL numeric bounds (<=131072 integer digits, <=16383 fraction digits).","If the value is genuinely a big number, serialize it as a JSON string in the detail blob instead of a number — strings are not subject to numeric bounds.","Validate detail payloads at ingestion (reject numbers over the bound before RowHash) so hostile inputs fail fast with your own error.","If a producer library is emitting absurd precision, configure it to round (e.g. cap decimal places) before embedding values into detail."],"exampleFix":"// before\ndetail := []byte(`{\"x\":` + hugeBigFloatString + `}`)\ncanon, err := proposalrun.CanonicalDetail(detail) // exceeds JSONB numeric size\n\n// after\ndetail := []byte(`{\"x\":\"` + hugeBigFloatString + `\"}`) // carry as string\ncanon, err := proposalrun.CanonicalDetail(detail)","handlingStrategy":"validation","validationCode":"func numberLexemeWithinBounds(detail json.RawMessage) bool {\n    var v any\n    dec := json.NewDecoder(bytes.NewReader(detail))\n    dec.UseNumber()\n    if err := dec.Decode(&v); err != nil {\n        return false\n    }\n    ok := true\n    jsonWalkNumbers(v, func(n json.Number) error {\n        if len(n.String()) > 147456 {\n            ok = false\n        }\n        return nil\n    })\n    return ok\n}","typeGuard":null,"tryCatchPattern":"canon, err := proposalrun.CanonicalDetail(detail)\nif err != nil && strings.Contains(err.Error(), \"JSONB numeric size\") {\n    return fmt.Errorf(\"detail number exceeds storage bounds; encode as string: %w\", err)\n}","preventionTips":["Cap numeric precision at the producer (e.g. round floats to a fixed number of digits).","Represent bignum/decimal values as JSON strings in audit details.","Reject oversized numeric literals at the API boundary before hashing.","Fuzz-test CanonicalDetail with long numeric lexemes."],"tags":["go","json","numeric-limits","postgresql","input-validation"],"backgroundTag":"value-out-of-range","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}