JuliusBrussee/caveman · error

cacheengine: JSON nesting limit exceeded

Error message

cacheengine: JSON nesting limit exceeded

What it means

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 '[[[[...'.

Source

Thrown at cacheengine/native.go:471

func validUniqueJSONObject(body []byte) bool {
	valid, _ := inspectUniqueJSONObject(body, "")
	return valid
}

func inspectUniqueJSONObject(body []byte, provider string) (bool, bool) {
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.UseNumber()
	found, err := inspectUniqueJSONValue(decoder, true, 0, provider, nil)
	if err != nil {
		return false, false
	}
	_, err = decoder.Token()
	return errors.Is(err, io.EOF), found
}

func inspectUniqueJSONValue(decoder *json.Decoder, root bool, depth int, provider string, path []string) (bool, error) {
	if depth > 512 {
		return false, errors.New("cacheengine: JSON nesting limit exceeded")
	}
	token, err := decoder.Token()
	if err != nil {
		return false, err
	}
	delim, composite := token.(json.Delim)
	if !composite {
		if root {
			return false, errors.New("cacheengine: request root must be object")
		}
		return false, nil
	}
	switch delim {
	case '{':
		seen := map[string]bool{}
		found := false
		for decoder.More() {
			keyToken, err := decoder.Token()

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Reject or bound nesting at your own ingress before the engine sees the body (most servers cap depth well below 512)
  2. Fix cyclic data structures that produce unbounded nesting when serialized
  3. Treat the engine's 'not found' result on such bodies as a reject, not a pass

Example fix

// before
body, _ := json.Marshal(payload) // payload contains a reference cycle

// after
// detect cycles before marshal or use a serializer with cycle detection (e.g. 'github.com/...json' with VisitCycleError), and cap nesting:
if nestingDepth(body) > 128 { return errors.New("reject: nesting too deep") }
Defensive patterns

Strategy: validation

Validate before calling

func maxNesting(b []byte, limit int) error {
    d := json.NewDecoder(bytes.NewReader(b))
    depth, max := 0, 0
    for {
        t, err := d.Token()
        if err != nil { return err }
        if t == nil { break }
        if dl, ok := t.(json.Delim); ok {
            if dl == '{' || dl == '[' { depth++; if depth > limit { return fmt.Errorf("nesting %d", depth) } } else { depth-- }
        }
    }
    _ = max
    return nil
}

Type guard

// n/a

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/e9e74242f199b390. Report an issue: GitHub.