{"record":{"id":"2ef146bbb3d529da","repo":"JuliusBrussee/caveman","slug":"json-splice-invalid-object-range","errorCode":null,"errorMessage":"json splice: invalid object range","messagePattern":"json splice: invalid object range","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/jsonsplice/jsonsplice.go","lineNumber":157,"sourceCode":"\t\treturn nil, fmt.Errorf(\"json splice: invalid range\")\n\t}\n\tif !json.Valid(replacement) {\n\t\treturn nil, fmt.Errorf(\"json splice: replacement is not valid JSON\")\n\t}\n\tout := make([]byte, 0, len(body)-(span.End-span.Start)+len(replacement))\n\tout = append(out, body[:span.Start]...)\n\tout = append(out, replacement...)\n\tout = append(out, body[span.End:]...)\n\treturn out, nil\n}\n\n// AppendObjectFields inserts fields immediately before an object's closing\n// brace. Existing bytes, whitespace, key order, and escapes remain untouched.\n// Callers must reject existing decoded keys before calling this function.\nfunc AppendObjectFields(body []byte, object Span, fields ...FieldInsertion) ([]byte, error) {\n\tif object.Start < 0 || object.End > len(body) || object.Start >= object.End ||\n\t\tbody[object.Start] != '{' || body[object.End-1] != '}' {\n\t\treturn nil, fmt.Errorf(\"json splice: invalid object range\")\n\t}\n\tif len(fields) == 0 {\n\t\treturn body, nil\n\t}\n\n\tinsertAt := object.End - 1\n\tfor insertAt > object.Start+1 && bytes.ContainsRune([]byte(\" \\n\\r\\t\"), rune(body[insertAt-1])) {\n\t\tinsertAt--\n\t}\n\thasFields := insertAt > object.Start+1\n\n\tvar addition bytes.Buffer\n\tif hasFields {\n\t\taddition.WriteByte(',')\n\t}\n\tfor i, field := range fields {\n\t\tif field.Name == \"\" || !json.Valid(field.Value) {\n\t\t\treturn nil, fmt.Errorf(\"json splice: invalid field insertion\")","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/jsonsplice/jsonsplice.go#L139-L175","documentation":"AppendObjectFields splices fields just before a JSON object's closing brace and validates that the span bounds the whole body, starts with '{', and ends with '}'. Any violation (bad bounds or the span is not actually an object) aborts to preserve byte integrity.","triggerScenarios":"inject*Raw helpers given a span pointing at a value that is not an object; span computed against a different body; End-1 landing on whitespace or ']' due to an off-by-one in the locator.","commonSituations":"Targeting a top-level 'system' that is a string instead of an array/object; span cached from a prior request shape; a locator that returns the last byte of the object's final value instead of the closing brace.","solutions":["Verify the located span actually brackets an object (first byte '{', last byte '}') before calling","Re-locate the object span on the exact body instance passed in","Confirm the field target exists in the current provider payload schema (e.g. system is an object here, array elsewhere)","Log body[span.Start] and body[span.End-1] on failure to spot the off-by-one"],"exampleFix":"// before: span points at a string value\nspan := findSpan(body, \"model\") // \"claude-3\" value span\nout, err := jsonsplice.AppendObjectFields(body, span, field)\n// after: span must bracket the whole object\nobjSpan, ok := findObjectSpan(body, \"system\")\nif !ok || body[objSpan.Start] != '{' { return nil, fmt.Errorf(\"system is not an object\") }\nout, err := jsonsplice.AppendObjectFields(body, objSpan, field)","handlingStrategy":"validation","validationCode":"func isObjectSpan(body []byte, s jsonsplice.Span) bool {\n    return s.Start >= 0 && s.End <= len(body) && s.Start < s.End && body[s.Start] == '{' && body[s.End-1] == '}'\n}","typeGuard":null,"tryCatchPattern":"if !isObjectSpan(body, span) { return nil, fmt.Errorf(\"target span is not a JSON object\") }\nout, err := jsonsplice.AppendObjectFields(body, span, fields...)","preventionTips":["Confirm the target field is an object in the current provider schema before appending fields","Re-locate the span on the live body, not a cached one","Verify locator output lands on '{' and '}' in tests"],"tags":["json","go","byte-splice","offsets"],"backgroundTag":"argument-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"}