Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash)
Description
### Summary The JSON body processor (`internal/bodyprocessors/json.go`) can be made to crash the whole process with an unrecoverable `fatal error: stack overflow`, using a request body that is well under the recommended `SecRequestBodyLimit` and the default `SecArgumentsLimit`. ### Root cause `readJSON` (json.go:113-143) runs a bounded, best-effort flattening walk (`readItems`) and *afterwards* calls `gjson.Valid(s)` on the raw body if `readItems` returned no error: ```go json := gjson.Parse(s) ... truncated, err = readItems(json, key, maxRecursion, argumentLimit, byteBudget, &usedBytes, &argCount, res) if err != nil { return res, truncated, err } if !gjson.Valid(s) { return res, truncated, errors.New("invalid JSON") } ``` `gjson.Valid` (gjson v1.18.0, `validany` -> `validarray`/`validobject`) recurses once per nesting level with **no depth bound**. `readItems` does have a depth bound (`maxRecursion`), enforced here (json.go:163-182): ```go func readItems(json gjson.Result, objKey []byte, maxRecursion int, argumentLimit int, byteBudget int, usedBytes *int, argCount *int, res map[string][]string) (truncated bool, err error) { if byteBudget > 0 && *usedBytes >= byteBudget { return true, nil // <-- checked first } if argumentLimit > 0 && *argCount >= argumentLimit { return true, nil // <-- checked second } ... if maxRecursion <= 0 { return false, errors.New("max recursion reached while reading json object") } ``` The byte-budget and argument-limit checks run *before* the recursion-depth check, and they short-circuit the walk with `truncated=true, err=nil` instead of recursing further. If the configured `SecArgumentsLimit` (`ArgumentLimit`, default 1000, `internal/corazawaf/waf.go:359`) is reached by earlier, shallow values in the document, `readItems` stops walking *before it ever reaches* a deeply nested tail later in the same document — so the `maxRecursion` error is never produced, `err` comes back `nil`, and `readJSON` falls through to the unconditional `gjson.Valid(s)` call on the complete raw body, including the part `readItems` never visited. This is not a new interaction with the recursion limit itself: at v3.7.0, `gjson.Valid` ran unconditionally before any recursion check at all, so a plain deeply-nested body crashed the process directly. A later fix added a depth check that returns an error before `Valid` runs for the *straightforward* case (nesting reached before any other guard fires). The argument-limit / byte-budget guards added since then (GHSA-6r3q-mjv7-xr8m, GHSA-3ww9-vw83-9w5x) reopened the same crash for the case above, because they short-circuit the walk (and therefore the recursion counter) ahead of the depth check, on both the request and response body path (`ProcessResponse` calls the same `readJSON`, json.go:57-88). Because this is `fatal error: stack overflow`, not a `panic`, it is **not** recoverable by any `recover()` in the calling goroutine — the process terminates unconditionally. ### PoC ```go package bodyprocessors import ( "strings" "testing" ) func TestStackOverflowRepro(t *testing.T) { body := "[" + strings.Repeat("1,", 1000) + strings.Repeat("[", 13_000_000) // 13,002,001 bytes total: under the recommended SecRequestBodyLimit // (13107200, coraza.conf-recommended:78) and default ArgumentLimit (1000, // internal/corazawaf/waf.go:359). _, _, _ = readJSON(body, 20, 1000) } ``` ``` $ go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v runtime: goroutine stack exceeds 1000000000-byte limit fatal error: stack overflow ... github.com/tidwall/gjson.validarray(...) .../[email protected]/gjson.go:2584 github.com/tidwall/gjson.validany(...) .../[email protected]/gjson.go:2499 github.com/tidwall/gjson.validarray(...) .../[email protected]/gjson.go:2589 ... (repeats until the goroutine stack limit is hit) ``` Reproduced against commit `19b86824` (tag `v3.8.0`), both by calling `readJSON` directly and end-to-end through the recommended `coraza.conf-recommended` configuration (JSON `Content-Type`, default `SecArgumentsLimit`, recommended `SecRequestBodyLimit`). ### Impact An unauthenticated attacker who can send an HTTP request body (any endpoint protected by Coraza with the JSON body processor enabled, which is the default for `application/json`) can crash the entire host process with a single request, using a payload well within default and recommended body size and argument-count limits. There is no privilege or interaction requirement, and the crash cannot be caught or mitigated by the integrator (no `recover()` stops a stack-overflow fatal error). This is strictly worse than a CPU-exhaustion or slow-request DoS: the process must be restarted, and every in-flight request/transaction on that process is lost. ### Suggested fix Run an iterative, explicitly-bounded-depth pre-scan (or reuse `readItems`'s own recursion accounting) before calling `gjson.Valid`, and never call `gjson.Valid` on input whose nesting exceeds `maxRecursion`. The response path (`ProcessResponse`) needs the same treatment since it shares `readJSON`. ### AI involvement disclosure - **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code. - **What was generated/assisted:** the initial vulnerability hypothesis and repro shape were supplied by the reporter as an existing written finding; Claude Sonnet 5 independently re-derived the root cause by reading the current source, wrote and ran a fresh PoC test against commit `19b86824` (tag `v3.8.0`), confirmed the crash and stack trace shown above, verified the default configuration values cited (`ArgumentLimit` default, `SecRequestBodyLimit` recommended value) against the current source, and drafted this advisory text. - **Review performed:** reproduced by hand by running the PoC test above with `go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v` against a clean checkout of commit `19b86824`; observed the `fatal error: stack overflow` and stack trace through `gjson.validarray`/`validany`; traced `readJSON`/`readItems` line by line to confirm the guard ordering described above; the PoC was reviewed by a human maintainer (fzipi) before submission of this advisory.
CVSS v3.1 base metrics
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HHigh severity
Band computed from the CVSS base score, not the source's own label — so it means the same thing across every feed.
AV
Network
Attack Vector
AC
Low
Attack Complexity
PR
None
Privileges Required
UI
None
User Interaction
S
Unchanged
Scope
C
None
Confidentiality
I
None
Integrity
A
High
Availability
Affected
- Vendor
- Go
- Product
- github.com/corazawaf/coraza/v3
Versions
- pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.1
Stated as the source expressed them.
References
- advisoryOSV GHSA-6gcq-wc29-5xf2https://osv.dev/vulnerability/GHSA-6gcq-wc29-5xf2
- otherOSV webhttps://github.com/corazawaf/coraza/security/advisories/GHSA-6gcq-wc29-5xf2
- otherOSV webhttps://github.com/corazawaf/coraza/commit/814e1898e083d2ff2ceb644382d0da17e930f93f
- vendorOSV packagehttps://github.com/corazawaf/coraza
- otherOSV webhttps://github.com/corazawaf/coraza/releases/tag/v3.8.1
Related threats
same CWE or vendorCVE-2026-19498 — IBM Security Verify Access 10.0 through 10.0.9.2 and IBM Verify Identity Access 11.0 through 11.0.3 could allow a remote attacker to cause a denial…
CVE-2026-19498 · 3h ago
CVE-2026-107386 — amqp091-go is a Go AMQP 0.9.1 client.
CVE-2026-107386 · 5h ago
CVE-2026-107376 — webonyx graphql-php is a PHP implementation of the GraphQL specification.
CVE-2026-107376 · 6h ago
Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor
OSV · 7h ago
Coraza: URL-encoded form Content-Type parameters bypass Coraza body inspection
OSV · 7h ago
Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion
OSV · 7h ago