---
id: GHSA-6gcq-wc29-5xf2
title: >-
  Coraza JSON body processor: argument-limit truncation reopens an
  unbounded-depth gjson.Valid stack overflow (process crash)
summary: >-
  Coraza JSON body processor: argument-limit truncation reopens an
  unbounded-depth gjson.Valid stack overflow (process crash)
severity: high
cvss: 7.5
cwe:
  - CWE-674
vendor: corazawaf
product: github.com/corazawaf/coraza/v3
ecosystem: go
affected:
  - 'github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.1'
patched:
  - github.com/corazawaf/coraza/v3 3.8.1
published: '2026-10-08'
updated: '2026-10-08'
sourceUpdated: '2026-10-08T17:45:47Z'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-6gcq-wc29-5xf2'
references:
  - url: >-
      https://github.com/corazawaf/coraza/security/advisories/GHSA-6gcq-wc29-5xf2
  - url: >-
      https://github.com/corazawaf/coraza/commit/814e1898e083d2ff2ceb644382d0da17e930f93f
  - url: 'https://github.com/corazawaf/coraza/releases/tag/v3.8.1'
  - url: 'https://github.com/advisories/GHSA-6gcq-wc29-5xf2'
tags:
  - ghsa
  - go
ingestedAt: '2026-10-08T17:56:11.723Z'
---

## Overview

### 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 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.

## Affected packages

- `github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.1`

## Remediation

Upgrade to a patched release:

- `github.com/corazawaf/coraza/v3 3.8.1`
