{"record":{"id":"e9e74242f199b390","repo":"JuliusBrussee/caveman","slug":"cacheengine-json-nesting-limit-exceeded","errorCode":null,"errorMessage":"cacheengine: JSON nesting limit exceeded","messagePattern":"cacheengine: JSON nesting limit exceeded","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cacheengine/native.go","lineNumber":471,"sourceCode":"func validUniqueJSONObject(body []byte) bool {\n\tvalid, _ := inspectUniqueJSONObject(body, \"\")\n\treturn valid\n}\n\nfunc inspectUniqueJSONObject(body []byte, provider string) (bool, bool) {\n\tdecoder := json.NewDecoder(bytes.NewReader(body))\n\tdecoder.UseNumber()\n\tfound, err := inspectUniqueJSONValue(decoder, true, 0, provider, nil)\n\tif err != nil {\n\t\treturn false, false\n\t}\n\t_, err = decoder.Token()\n\treturn errors.Is(err, io.EOF), found\n}\n\nfunc inspectUniqueJSONValue(decoder *json.Decoder, root bool, depth int, provider string, path []string) (bool, error) {\n\tif depth > 512 {\n\t\treturn false, errors.New(\"cacheengine: JSON nesting limit exceeded\")\n\t}\n\ttoken, err := decoder.Token()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdelim, composite := token.(json.Delim)\n\tif !composite {\n\t\tif root {\n\t\t\treturn false, errors.New(\"cacheengine: request root must be object\")\n\t\t}\n\t\treturn false, nil\n\t}\n\tswitch delim {\n\tcase '{':\n\t\tseen := map[string]bool{}\n\t\tfound := false\n\t\tfor decoder.More() {\n\t\t\tkeyToken, err := decoder.Token()","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/cacheengine/native.go#L453-L489","documentation":"Thrown by inspectUniqueJSONValue when the streaming JSON decoder descends past depth 512 while walking the request body looking for cache markers. It protects the recursive walker (and the JSON codec) from stack exhaustion on adversarial deeply-nested payloads like '[[[[...'.","triggerScenarios":"A request body whose JSON nesting (objects/arrays) exceeds 512 levels — e.g. thousands of repeated '[' or '{' characters; note the error is returned from the recursive walker and some callers (like the unique-marker scan in native.go:471) discard it and simply treat the body as non-unique/not-found.","commonSituations":"Adversarial or fuzzed request bodies; accidentally serializing recursive data structures (a struct referencing itself) with a serializer that does not detect cycles; legitimately nested data is never 512 levels deep.","solutions":["Reject or bound nesting at your own ingress before the engine sees the body (most servers cap depth well below 512)","Fix cyclic data structures that produce unbounded nesting when serialized","Treat the engine's 'not found' result on such bodies as a reject, not a pass"],"exampleFix":"// before\nbody, _ := json.Marshal(payload) // payload contains a reference cycle\n\n// after\n// detect cycles before marshal or use a serializer with cycle detection (e.g. 'github.com/...json' with VisitCycleError), and cap nesting:\nif nestingDepth(body) > 128 { return errors.New(\"reject: nesting too deep\") }","handlingStrategy":"validation","validationCode":"func maxNesting(b []byte, limit int) error {\n    d := json.NewDecoder(bytes.NewReader(b))\n    depth, max := 0, 0\n    for {\n        t, err := d.Token()\n        if err != nil { return err }\n        if t == nil { break }\n        if dl, ok := t.(json.Delim); ok {\n            if dl == '{' || dl == '[' { depth++; if depth > limit { return fmt.Errorf(\"nesting %d\", depth) } } else { depth-- }\n        }\n    }\n    _ = max\n    return nil\n}","typeGuard":"// n/a","tryCatchPattern":null,"preventionTips":["Reject nesting > ~128 at your ingress","Use cycle-detecting serialization for recursive structures"],"tags":["cacheengine","json","nesting","security","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}