{"record":{"id":"010a9a832a6eb2a7","repo":"JuliusBrussee/caveman","slug":"json-splice-invalid-range","errorCode":null,"errorMessage":"json splice: invalid range","messagePattern":"json splice: invalid range","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/jsonsplice/jsonsplice.go","lineNumber":111,"sourceCode":"}\n\nfunc StringField(body []byte, object Span, name string) (string, bool) {\n\tspan, ok := Field(body, object, name)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn String(body, span)\n}\n\nfunc Replace(body []byte, candidates []Candidate, replacements [][]byte) ([]byte, error) {\n\tif len(candidates) != len(replacements) {\n\t\treturn nil, fmt.Errorf(\"json splice: %d replacements for %d candidates\", len(replacements), len(candidates))\n\t}\n\tvar out []byte\n\tlast := 0\n\tfor i, candidate := range candidates {\n\t\tif candidate.Start < last || candidate.End > len(body) || candidate.Start >= candidate.End {\n\t\t\treturn nil, fmt.Errorf(\"json splice: invalid range\")\n\t\t}\n\t\treplacement := replacements[i]\n\t\tif replacement == nil || bytes.Equal(replacement, candidate.Original) {\n\t\t\tcontinue\n\t\t}\n\t\tquoted, err := quote(string(replacement))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif out == nil {\n\t\t\tout = make([]byte, 0, len(body)-len(candidate.Original)+len(replacement))\n\t\t}\n\t\tout = append(out, body[last:candidate.Start]...)\n\t\tout = append(out, quoted...)\n\t\tlast = candidate.End\n\t}\n\tif out == nil {\n\t\treturn body, nil","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/jsonsplice/jsonsplice.go#L93-L129","documentation":"jsonsplice.Replace validates each candidate span before splicing: Start must not regress past the previous candidate's end, End must be within body, and Start must be strictly less than End. Any violation means the candidate offsets do not describe ordered, in-bounds, non-empty spans of the given body.","triggerScenarios":"Candidates computed against a different (older/transformed) body than the one passed; unsorted candidate spans; zero-length or inverted spans from a failed string scan; candidates from nested overlapping matches.","commonSituations":"Reusing cached offsets after the body changed upstream; sort order broken by map iteration when collecting candidates; byte-offset vs rune-offset confusion when locating strings in multibyte UTF-8 payloads.","solutions":["Ensure candidates come from the exact same body bytes passed to Replace and are recomputed after any modification","Sort candidates by Start and merge/drop overlaps before calling Replace","Skip zero-length or inverted spans at collection time","Switch to byte-offset (not rune-index) searches to compute spans"],"exampleFix":"// before: unsorted/inconsistent offsets\nout, err := jsonsplice.Replace(body, cands, reps)\n// after: normalize first\nsort.Slice(cands, func(i, j int) bool { return cands[i].Start < cands[j].Start })\nvalid := cands[:0]\nlast := 0\nfor _, c := range cands {\n    if c.Start >= last && c.End <= len(body) && c.Start < c.End {\n        valid = append(valid, c); last = c.End\n    }\n}\nout, err := jsonsplice.Replace(body, valid, matchingReps(valid))","handlingStrategy":"validation","validationCode":"func validCandidates(body []byte, cands []jsonsplice.Candidate) bool {\n    last := 0\n    for _, c := range cands {\n        if c.Start < last || c.End > len(body) || c.Start >= c.End { return false }\n        last = c.End\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if !validCandidates(body, cands) { return nil, fmt.Errorf(\"refusing splice: invalid candidate offsets\") }\nout, err := jsonsplice.Replace(body, cands, reps)","preventionTips":["Recompute candidate offsets from the same body instance passed to Replace","Sort by Start and drop overlaps/zero-length spans before calling","Use byte offsets, not rune indices, when scanning UTF-8 payloads"],"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"}