{"record":{"id":"50bb481e7fbf4c33","repo":"JuliusBrussee/caveman","slug":"cacheengine-invalid-object-close","errorCode":null,"errorMessage":"cacheengine: invalid object close","messagePattern":"cacheengine: invalid object close","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cacheengine/native.go","lineNumber":509,"sourceCode":"\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tkey, ok := keyToken.(string)\n\t\t\tif !ok || seen[key] {\n\t\t\t\treturn false, errors.New(\"cacheengine: duplicate or invalid object key\")\n\t\t\t}\n\t\t\tseen[key] = true\n\t\t\tmatched := cacheMarkerAt(provider, path, key)\n\t\t\tpath = append(path, key)\n\t\t\tchildFound, err := inspectUniqueJSONValue(decoder, false, depth+1, provider, path)\n\t\t\tpath = path[:len(path)-1]\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfound = found || matched || childFound\n\t\t}\n\t\tclosing, err := decoder.Token()\n\t\tif err != nil || closing != json.Delim('}') {\n\t\t\treturn false, errors.New(\"cacheengine: invalid object close\")\n\t\t}\n\t\treturn found, nil\n\tcase '[':\n\t\tif root {\n\t\t\treturn false, errors.New(\"cacheengine: request root must be object\")\n\t\t}\n\t\tfound := false\n\t\tpath = append(path, \"*\")\n\t\tfor decoder.More() {\n\t\t\tchildFound, err := inspectUniqueJSONValue(decoder, false, depth+1, provider, path)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfound = found || childFound\n\t\t}\n\t\tclosing, err := decoder.Token()\n\t\tif err != nil || closing != json.Delim(']') {\n\t\t\treturn false, errors.New(\"cacheengine: invalid array close\")","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/cacheengine/native.go#L491-L527","documentation":"Thrown by inspectUniqueJSONValue when, after consuming all members of an object (decoder.More() returned false), the next token is not '}'. In a well-formed stream this cannot happen; it indicates truncated or malformed JSON that the streaming decoder walked past, or corrupted decoder state.","triggerScenarios":"A body whose object is not properly closed, e.g. '{\"a\":1' or '{\"a\":1]' — combined with the walker's structure; in practice this surfaces with truncated bodies or hand-assembled JSON where the closing brace is missing/replaced.","commonSituations":"Truncated request bodies (a size cap cutting the tail, or a partial write); hand-built JSON in tests missing the closing brace; a body-editing middleware that drops trailing bytes (e.g. when stripping a field by slicing).","solutions":["Validate the body with a strict full-document json.Unmarshal/Valid check before sending to the engine","Fix body-rewriting middleware to re-marshal rather than byte-slice editing","Ensure no upstream truncates the body (content-length mismatches, log-redaction that cuts tails)"],"exampleFix":"// before\nbody := strings.Replace(string(raw), \"long_field_to_strip\", \"\", 1) // can leave broken/truncated JSON\n\n// after\nvar m map[string]any\njson.Unmarshal(raw, &m)\ndelete(m, \"long_field_to_strip\")\nbody, _ := json.Marshal(m)","handlingStrategy":"validation","validationCode":"if !json.Valid(body) { return errors.New(\"invalid JSON body\") }","typeGuard":"// n/a","tryCatchPattern":null,"preventionTips":["Run json.Valid at the trust boundary","Never byte-slice-edit serialized JSON; re-marshal"],"tags":["cacheengine","json","malformed","protocol","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}