Skip to main content

Coraza WAF CVE-2026-41510

| EUVDEUVD-2026-97824 HIGH
Protection Mechanism Failure (CWE-693)
2026-10-06 https://github.com/corazawaf/coraza GHSA-6r3q-mjv7-xr8m
7.2
CVSS 3.1 · Vendor: https://github.com/corazawaf/coraza
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Craft HTTP request carrying attack payload
  2. Delivery
    Pad request with 1000+ filler arguments
  3. Exploit
    Argument count exceeds SecArgumentsLimit default
  4. Execution
    Coraza silently drops inspected argument
  5. Impact
    Payload evades ARGS rules, reaches backend app

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A06:2025 - Insecure Design
ASVS 5.0
V15 - Secure Coding and Architecture Protection mechanism completeness
Exploitation Conditions

Exploitation requires a deployment of Coraza v3 (>=3.0.0, <3.8.1) using the SecArgumentsLimit directive with ARGS/ARGS_GET/ARGS_NAMES/ARGS_PATH-targeting rules (e.g. OWASP CRS) as the protective control, and - critically - the default coraza.conf-recommended, which shipped NO compensating ARGUMENTS_LIMIT_REACHED deny rule before 3.8.1, making it exploitable out-of-the-box. The attacker inflates the request's argument count past SecArgumentsLimit (default 1000), e.g. ~10,000 GET params for ~94% per-request bypass, or an urlencoded/JSON POST body (the POST urlencoded processor and JSON processor ignored the cap entirely, also enabling memory-exhaustion DoS). Limiting factors: the backend application must itself be vulnerable to the payload being hidden for the bypass to cause real harm; on pre-3.8.0 builds the GET bypass was probabilistic (depended on Go's randomized map iteration) requiring retries, whereas 3.8.0+ made it deterministic (tail-dropped); and a deployment that already ships the 3.8.1 compensating rules or a hand-rolled argument-limit check is not bypassable this way.

Impact

The vendor CVSS vector (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L, 7.2 High) describes a network-reachable, low-complexity, unauthenticated protection-mechanism bypass (CWE-693) with a scope change onto the protected application. These signals are internally consistent and credible: an attacker only needs to inflate a request's argument count past the default SecArgumentsLimit (1000), and since v3.8.0 the drop became deterministic, so AC:L is justified. However, this is a defense-in-depth/WAF-bypass issue, not a direct code-execution flaw in Coraza - its real-world impact is conditional on the backend application actually being vulnerable to the payload that slips past inspection, so the I:L rating (and A:L for the memory-DoS from unbounded ARGS_POST/JSON growth) reflects indirect impact. No EPSS score was provided in the intelligence, and the CVE is NOT listed in CISA KEV, so there is no evidence of widespread active exploitation; the GHSA advisory does publish a working proof-of-concept, meaning exploit code is public but exploitation is not confirmed in the wild. Net: a genuine priority for anyone relying on Coraza + OWASP CRS as a primary control (the default coraza.conf-recommended shipped no compensating rule before 3.8.1, making it exploitable out-of-the-box), but a moderate rather than critical issue where Coraza is one layer among several and the backend is independently hardened. Conflicting signal to verify: the network/unauthenticated vector is real, yet impact severity hinges on backend vulnerability state, which the CVE data cannot confirm.

Exploit ScenarioDerived

An attacker targeting a web application behind Coraza with OWASP CRS appends thousands of filler GET parameters alongside a single SQLi/XSS/RCE payload; because the total argument count exceeds the default SecArgumentsLimit of 1000, Coraza silently drops arguments before phase:2 evaluation, so the ARGS-targeting rule never fires and the payload reaches the backend. A working proof-of-concept is published in the GHSA advisory, demonstrating ~94% bypass success at 10,000 arguments using plain curl. Given AV:N/AC:L, exploitation is remote, unauthenticated, and requires only trivial request padding.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.5% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Oct 06, 2026

Silent argument dropping in Coraza WAF v3 (>= 3.0.0, < 3.8.1) lets unauthenticated remote attackers evade every SecRule that inspects ARGS, ARGS_GET, ARGS_NAMES, ARGS_GET_NAMES or ARGS_PATH - including the OWASP CRS SQLi, XSS, RCE and LFI families - by padding a request past SecArgumentsLimit (default 1000) so the malicious parameter is discarded before phase 2 evaluation; roughly 10,000 GET parameters yields about a 94% per-request bypass, and the failure is completely silent (no audit-log entry, no transaction flag). The same advisory covers unbounded ARGS_POST growth because Coraza's urlencoded and JSON body processors ignored the argument cap entirely, which is also a per-request memory-exhaustion DoS surface. No public exploit identified at time of analysis and no confirmed active exploitation; vendor-released patch: 3.8.1 (the 3.8.0 fix was incomplete and 3.8.0 remains listed as affected), and the advisory/report is credited to the corazawaf project with AI-assisted analysis disclosed. The assessed vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L (7.2 High), a CWE-693 protection-mechanism failure whose impact lands on the protected application rather than Coraza itself.

RemediationAI

Upgrade to Coraza v3.8.1 or later - the authoritative fix covers all four original mitigations (compensating rules, the ARGUMENTS_LIMIT_REACHED transaction variable, urlencoded body-processor enforcement, and order-deterministic GET parsing) plus three additional gaps (JSON body-processor enforcement for both ARGS_POST and RESPONSE_ARGS, parse-time limiting in ParseQuery/ParseQueryOrdered, and TotalValues()-based counting that closes the repeated-key bypass); note that 3.8.0 is itself listed as affected because its fix was incomplete, and the vendor patch commits are 146c2f79f39ad16f13787e7d67ad400f8d8cb9a3 and 814e1898e083d2ff2ceb644382d0da17e930f93f, with the later byte-budget completion in PR #2. Confirm the shipped coraza.conf-recommended now contains rules id:200004 (phase:1, GET/PATH) and id:200005 (phase:2, POST), which deny with status 400 when ARGUMENTS_LIMIT_REACHED is 1. If an upgrade cannot be scheduled immediately, add the compensating rules by hand - mirroring ModSecurity's id:200007 but keyed per collection, e.g. SecRule &ARGS_GET "@ge 1000" (id:200007), SecRule &ARGS_POST "@ge 1000" (id:200008) and SecRule &ARGS_PATH "@ge 1000" (id:200009) with phase:2, log, deny and status 400 - accepting that these thresholds must be kept in sync with any custom SecArgumentsLimit and that they can false-positive when an operator tunes the limit, whereas the engine-backed SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" form (id:200006) does not. Do not use a single aggregate &ARGS @ge 1000 check, since Coraza caps ARGS_GET, ARGS_POST and ARGS_PATH independently and an aggregate would deny legitimate GET=500 plus POST=500 traffic. Additional compensating controls with trade-offs: lower SecArgumentsLimit below 1000 to shrink the attack window at the cost of rejecting legitimate parameter-heavy applications; enforce a maximum parameter count at the reverse proxy or load balancer in front of the application, which is effective but can desynchronize from Coraza's own parsing and is bypassed by body-splitting; and cap SecRequestBodyLimit and SecResponseBodyLimit plus monitor process memory to blunt the urlencoded/JSON memory-exhaustion DoS, at the cost of rejecting large legitimate uploads. Where CRS is in use, verify the deployment is actually running an ARGS-dependent rule set - a bypass is only harmful if the protected application is itself vulnerable to the hidden payload. Vendor advisory and patch references: https://github.com/corazawaf/coraza/security/advisories/GHSA-6r3q-mjv7-xr8m, https://github.com/advisories/GHSA-6r3q-mjv7-xr8m and the related https://github.com/corazawaf/coraza/security/advisories/GHSA-3ww9-vw83-9w5x (JSON/urlencoded body processors ignoring SecArgumentsLimit).

Affected VersionsENISA

coraza 3.0.0, < 3.8.1

Affected Packages

go github.com/corazawaf/coraza no fix
go github.com/corazawaf/coraza/v2 no fix
go github.com/corazawaf/coraza/v3 fix: 3.8.1
euvd coraza_3_0_0 fix: 3.8.1

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 4 packages in go, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

The root cause is a protection-mechanism failure (CWE-693) inside Coraza's transaction argument pipeline. AddGetRequestArgument, AddPostRequestArgument and AddPathRequestArgument in internal/corazawaf/transaction.go return silently once checkArgumentLimit() sees the per-collection count at or above WAF.ArgumentLimit (default 1000, internal/corazawaf/waf.go:346); no error is propagated, no flag variable is set, and no rule can observe the drop. ExtractGetArguments compounds this by ranging over the map[string][]string returned by urlutil.ParseQuery, and because Go randomizes map iteration order the surviving arguments - and therefore which payload is visible - are non-deterministic on pre-3.8.0 builds. Two secondary defects broadened the impact: internal/bodyprocessors/urlencoded.go wrote directly into ARGS_POST via argsCol.Set without ever calling checkArgumentLimit, making the POST cap dead code for real urlencoded bodies, and the JSON body processor (readJSON/readItems) performed no argument-limit enforcement at all, so a small body decoding to a wide flat array or a deeply nested structure with long shared parent keys expanded into millions of ARGS_POST/RESPONSE_ARGS entries. The counting logic itself was also wrong: checkArgumentLimit compared against NamedCollection.Len(), which counts distinct keys, so repeated keys such as a=1&a=1&a=1... grew the collection unboundedly (1,000,000 repeats produced ~60MB of heap growth despite SecArgumentsLimit 1000); the fix adds Map.TotalValues() and switches both checks to it. The remediation introduces an ARGUMENTS_LIMIT_REACHED transaction variable (analogous to MULTIPART_STRICT_ERROR/URLENCODED_ERROR), threads ArgumentLimit through BodyProcessorOptions, makes GET parsing ordered via ParseQueryOrdered so truncation is deterministic (tail-dropped), applies a flattenBytesFactor-based byte budget to JSON flattening, and caps ParseQuery/ParseQueryOrdered at parse time so memory is not spent before any downstream limit check runs. ModSecurity v3 has the same engine-level silent drop (src/transaction.cc:282-291) but ships compensating rule id:200007 that denies when &ARGS reaches SecArgumentsLimit, which is precisely the guard coraza.conf-recommended lacked before 3.8.1. CVSS scoring follows Coraza's WAF-bypass convention: Scope is Changed because the vulnerable component is Coraza while the impact lands on the protected application, Integrity is Low because a payload is hidden but the application must still be exploitable by it, and Availability is Low because of the unbounded argument-collection growth.

Know when Go gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/corazawaf/coraza) PRIMARY 7.2 HIGH
AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L
vuln.today AI 7.2 HIGH

Remote unauthenticated HTTP request (AV:N/PR:N/UI:N) with trivial, now-deterministic filler padding (AC:L); the WAF is the vulnerable component but impact lands on the protected app (S:C), hiding a payload (I:L) and consuming memory (A:L).

3.1 AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:L/SA:N

Primary rating from Vendor (https://github.com/corazawaf/coraza).

CVSS VectorVendor: https://github.com/corazawaf/coraza

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
None
Integrity
Low
Availability
Low

DescriptionCVE.org

Root Cause

File: internal/corazawaf/transaction.go, lines 770-808 (since commit 2fd87b89, PR #812, 2023-06-14)

go
func (tx *Transaction) AddGetRequestArgument(key string, value string) {
    if tx.checkArgumentLimit(tx.variables.argsGet) {
        tx.debugLogger.Warn().Msg("skipping get request argument, over limit")
        return
    }
    tx.variables.argsGet.Add(key, value)
}

func (tx *Transaction) checkArgumentLimit(c *collections.NamedCollection) bool {
    return c.Len() >= tx.WAF.ArgumentLimit
}

AddGetRequestArgument, AddPostRequestArgument, and AddPathRequestArgument silently return once the per-collection argument count reaches WAF.ArgumentLimit (default 1000, see internal/corazawaf/waf.go:346). No error variable is set, no transaction flag is raised, and no rule can observe that a drop occurred.

Worse, ExtractGetArguments (transaction.go:761) iterates the map[string][]string returned by urlutil.ParseQuery:

go
func (tx *Transaction) ExtractGetArguments(uri string) {
    data := urlutil.ParseQuery(uri, '&')
    for k, vs := range data {        // Go map iteration order is randomized
        for _, v := range vs {
            tx.AddGetRequestArgument(k, v)
        }
    }
}

Because Go randomizes map iteration order, which of the caller-supplied arguments survive the limit is non-deterministic. An attacker can pad the URI with filler arguments; any one of them - including the malicious payload - may be the one silently discarded, and therefore invisible to every SecRule targeting ARGS, ARGS_GET, or ARGS_NAMES.

Secondary finding - POST urlencoded processor bypasses the cap entirely

internal/bodyprocessors/urlencoded.go:29 populates ARGS_POST without invoking checkArgumentLimit:

go
values := urlutil.ParseQuery(b, '&')
argsCol := v.ArgsPost()
for k, vs := range values {
    argsCol.Set(k, vs)            // direct write, no limit check
}

So AddPostRequestArgument's cap is effectively dead code for real urlencoded bodies. ARGS_POST grows unbounded - both a bypass surface and a memory-DoS surface.

Impact

Any SecRule or CRS rule that inspects ARGS, ARGS_GET, ARGS_NAMES, ARGS_GET_NAMES, or ARGS_PATH can be evaded by inflating the request's argument count past SecArgumentsLimit (default 1000). The bypass probability per request scales with overflow:

Total args in requestObserved bypass rate of ARGS rule
1000 (at limit)0 / 50 (0.0%)
1001 (1 over)0 / 2000 (< 0.1%)
1100 (100 over)45 / 500 (9.0%)
2000 (1000 over)106 / 200 (53.0%)
10000 (10× limit)47 / 50 (94.0%)

The rate matches the theoretical model (N − limit) / N. An attacker flooding with 10000 arguments lands a bypass on ~94% of requests; one failed attempt costs them nothing, so a handful of retries yields a near-certain evasion against any ARGS-targeted rule, including the OWASP CRS SQLi, XSS, RCE, and LFI detection families.

The issue is silent - operators see no audit-log entry, no error, and no MULTIPART_STRICT_ERROR-style flag variable, because none exists.

Proof of Concept

Start a Coraza-wrapped HTTP server with a trivial ARGS rule:

conf
SecRuleEngine On
SecRule ARGS "@contains ATTACK_HERE_XYZ" "id:9001,phase:2,deny,status:403,msg:'Attack detected'"

Baseline sanity checks pass:

$ curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8090/?evil=ATTACK_HERE_XYZ'
403

Now pad the URI with 9999 filler parameters and one malicious parameter placed at a random offset. Running 50 such trials against a real HTTP listener with real curl:

=== 10000 args (attacker adds 9999 filler parameters) ===
total_args=10000 trials=50 BLOCKED=3 BYPASS=47 (94.0%)

47 of 50 attack requests were served HTTP 200 despite the payload being present in the URI. The defending rule never fired because Coraza discarded the argument before phase:2 evaluation.

Comparison with ModSecurity v3

The engine-level bug is present in ModSecurity v3 as well - src/transaction.cc:282-291 has the equivalent silent-drop:

cpp
bool Transaction::addArgument(...) {
    if (m_rules->m_argumentsLimit.m_set
            && m_variableArgs.size() >= m_rules->m_argumentsLimit.m_value) {
        ms_dbg(4, "Skipping request argument, over limit (...)")
        return false;                    // return value is ignored at the GET callsite
    }
    ...
}

ModSecurity is in fact *more deterministic* than Coraza - its query-string parser (extractArguments, transaction.cc:254) splits with an ordered ssplit, so it is the *tail* of the query that silently drops. An attacker places the payload first and pads the tail; no retry loop needed.

However, ModSecurity's default configuration papers over the engine bug. modsecurity.conf-recommended ships:

conf
SecArgumentsLimit 1000
# If SecArgumentsLimit has been set, you probably want to reject any
# request body that has only been partly parsed. The value used in this
# rule should match what was used with SecArgumentsLimit
SecRule &ARGS "@ge 1000" \
    "id:'200007',phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2"

Because addArgument caps the single m_variableArgs collection at exactly the limit, &ARGS == limit iff the limit was hit - rule 200007 converts silent-drop into explicit HTTP 400. ModSecurity also has a complementary REQBODY_ERROR path: its JSON processor cancels parsing on addArgument failure, and rule 200002 denies on REQBODY_ERROR (verified by test/test-cases/regression/secargumentslimit.json, test 2/2).

Coraza's coraza.conf-recommended ships no equivalent rule. That is what makes the bug exploitable out-of-the-box in Coraza and not in ModSecurity.

Silent-drop at engineCompensating default ruleExploitable out-of-the-box
ModSecurity v3yesyes (id:200007, &ARGS @ge 1000)no - denies at limit
Coraza v3yesnoyes

Mitigation

Recommended fixes, in the order they should be applied. Config-layer (#1) closes the default-install exposure quickly; engine-layer (#2, #3) is the durable fix.

1. Ship compensating rules in coraza.conf-recommended (config-layer, immediate)

Port the ModSecurity guard, but keyed per-collection - Coraza caps ARGS_GET, ARGS_POST, and ARGS_PATH independently, unlike ModSecurity's unified m_variableArgs. A single &ARGS @ge 1000 check on the concatenated collection would false-positive at e.g. GET=500 + POST=500 (no drops occurred but aggregate == 1000):

conf
SecRule &ARGS_GET  "@ge 1000" \
    "id:200007,phase:2,t:none,log,deny,status:400,msg:'ARGS_GET over SecArgumentsLimit; request partially parsed'"
SecRule &ARGS_POST "@ge 1000" \
    "id:200008,phase:2,t:none,log,deny,status:400,msg:'ARGS_POST over SecArgumentsLimit; request partially parsed'"
SecRule &ARGS_PATH "@ge 1000" \
    "id:200009,phase:2,t:none,log,deny,status:400,msg:'ARGS_PATH over SecArgumentsLimit; request partially parsed'"

Both &VAR (variable count, internal/seclang/rule_parser.go:40) and @ge (internal/operators/testdata/ge.json) are supported. Thresholds must track SecArgumentsLimit if the operator overrides it.

2. Expose a transaction-visible flag (engine-layer, durable)

Introduce an ARGUMENTS_LIMIT_REACHED collection variable, analogous to MULTIPART_STRICT_ERROR and URLENCODED_ERROR, set to 1 by AddGetRequestArgument / AddPostRequestArgument / AddPathRequestArgument whenever they drop. Replace the config rules above with a single engine-backed check:

conf
SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" \
    "id:200006,phase:1,t:none,log,deny,status:413,msg:'Argument limit reached; request rejected'"

This protects operators with hand-rolled configurations, not just those who use the recommended file.

3. Close the POST urlencoded body-processor gap

internal/bodyprocessors/urlencoded.go should route through AddPostRequestArgument (or invoke checkArgumentLimit explicitly) so the cap is actually enforced for urlencoded request bodies. Currently a 10000-arg POST body populates ARGS_POST in full, regardless of SecArgumentsLimit.

4. Make ExtractGetArguments order-deterministic

Replace the urlutil.ParseQuery → map → range pattern with an ordered slice-based parse. Combined with #2, this means when the limit is hit the outcome is at least deterministic (fail-closed via the flag) rather than a probabilistic game.

Affected versions

All releases since v3.0.0 that ship the SecArgumentsLimit directive (introduced in PR #812, commit 2fd87b89, June 2023). Confirmed reproducible on main at commit 599ae64a with default configuration.

References

  • internal/corazawaf/transaction.go lines 770-808
  • internal/corazawaf/waf.go line 346 (ArgumentLimit: 1000)
  • internal/bodyprocessors/urlencoded.go line 29 (POST-side gap)
  • internal/seclang/rule_parser.go:40 (&VAR count syntax)
  • internal/collections/concat_test.go:20 (ARGS as ConcatCollection of ARGS_GET/ARGS_POST/ARGS_PATH)
  • PR #812 - introduction of SecArgumentsLimit
  • ModSecurity v3 src/transaction.cc:282-291 (same silent-drop)
  • ModSecurity v3 modsecurity.conf-recommended rule id:200007 (compensating config-layer deny)

Resolution (2026-07-28)

Fixed in https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/1, implementing all four mitigation steps above, plus additional gaps found while verifying the fix (see below):

  1. Compensating coraza.conf-recommended rules - shipped as ARGUMENTS_LIMIT_REACHED-based rules (id:200004/200005, phase:1 for GET/PATH and phase:2 for POST), per-flag rather than the originally-sketched per-collection &ARGS_GET/&ARGS_POST/&ARGS_PATH counts, since the flag (below) already distinguishes GET/PATH-time drops from POST-time drops without needing separate threshold rules per collection.
  2. ARGUMENTS_LIMIT_REACHED transaction variable - added, set by every argument-adding path that can drop: AddGetRequestArgument, AddPostRequestArgument, AddPathRequestArgument, AddResponseArgument, the urlencoded body processor, and (see below) the JSON body processor and the query-string/urlencoded parser itself.
  3. internal/bodyprocessors/urlencoded.go now enforces the limit - threads ArgumentLimit through BodyProcessorOptions into the body processor, closing the POST-side gap.
  4. ExtractGetArguments is now order-deterministic - via ParseQueryOrdered, so when the limit is hit the tail is dropped predictably instead of a randomized subset.

Additional gaps found while verifying the fix (folded into the same PR)

While confirming this fix actually closed the class of bug, three more instances of the same underlying "argument limit isn't really enforced" problem turned up, overlapping with an independently-reported advisory, GHSA-3ww9-vw83-9w5x (JSON/urlencoded body processors ignore SecArgumentsLimit, enabling memory-exhaustion DoS):

  • The JSON body processor had zero enforcement at all (GHSA-3ww9's actual reported bug, with a working PoC: a small body decoding to a wide flat JSON array like [1,1,1,...] expanded into millions of ARGS_POST entries, exhausting memory on a single request). readJSON/readItems now stop flattening once ArgumentLimit entries are collected, for both request (ARGS_POST) and response (RESPONSE_ARGS) bodies - response previously received an empty BodyProcessorOptions{} with no limit at all.
  • ParseQuery/ParseQueryOrdered built their entire result before any caller-side cap ran. Even after fixing (3)/(4) above, a query string or urlencoded body with millions of pairs still spent the memory during parsing itself, before any limit check downstream ever got a chance to run. Both now accept a limit and stop parsing immediately once reached.
  • checkArgumentLimit (and AddResponseArgument's equivalent) compared against Len(), which counts distinct keys, not total values. Map.Add appends repeated-key values into the same map entry without growing it, so a=1&a=1&a=1... never tripped the limit no matter how large it grew - confirmed empirically: 1,000,000 repeats of a=1& via the already-"protected" GET-argument path produced ~60MB of unbounded heap growth despite SecArgumentsLimit 1000. Added Map.TotalValues() (cheap even under this attack - it sums len(slice) per key, so cost is bounded by distinct keys present, not by how many values piled up under any single one of them) and switched both checks to use it.

Verified before/after with heap measurements and direct parser unit tests (exactly limit entries returned regardless of a 1,000,000-entry adversarial input, for both repeated-key and distinct-key shapes). Full repo go test ./... -race, go vet, gofmt, golangci-lint all clean. BenchmarkReadJSONArgumentLimit shows the fix also cuts CPU time ~17x on a 100k-element flat array (741µs vs 12.5ms), since capped iteration stops early instead of walking the whole structure.

GHSA-3ww9-vw83-9w5x's own description has been updated to point here rather than duplicating this fix in a second PR.

Follow-up (2026-09-30): byte-budget bypass in the array-length write path

The "Resolution" section above states that readJSON/readItems "stop flattening once ArgumentLimit entries are collected". That is true for every per-leaf write, but not for the array-length summary entry written after each gjson.ForEach call returns (internal/bodyprocessors/json.go, the if arrayLen > 0 block). That write checked argumentLimit but never byteBudget:

go
if arrayLen > 0 {
    if argumentLimit > 0 && *argCount >= argumentLimit {
        iterationTruncated = true
    } else {
        k := string(objKey)
        lenStr := strconv.Itoa(arrayLen)
        res[k] = append(res[k], lenStr)
        *usedBytes += len(objKey) + len(lenStr)   // accounted for, but never checked against byteBudget first
        *argCount++
    }
}

objKey (the full flattened path) grows by roughly a fixed amount per nesting level, while argCount grows by only one per level. That is exactly the amplification byteBudget exists to bound (see flattenBytesFactor), but only the per-leaf write inside the ForEach callback checks it before writing; this post-ForEach write does not. A long property name nested under many single-element arrays inflates memory far past the configured byte budget while argumentLimit alone never trips, because each nesting level contributes only one argument, however long its path.

PoC

go
const keyLen = 20000
const depth = 200
body := `{"` + strings.Repeat("a", keyLen) + `":` + strings.Repeat("[", depth) + strings.Repeat("]", depth) + `}`
res, truncated, err := readJSON(body, 1024, 1000)

Against main at commit 19b86824: a 20,405-byte body produces 199 entries totalling 4,020,596 bytes of flattened keys (~197x the body size) and truncated=false, err=nil -- the byte budget for a body this size is len(body) * flattenBytesFactor (~163 KB), so this is roughly 25x over budget with no signal to the caller. A reviewer measured +961 MB heap growth end-to-end for a 1 MB body with a long property name. The recommended SecRequestBodyLimit (12.5 MiB) admits proportionally larger amplification. Because every ARGS_NAMES-targeted regex in CRS scans these flattened keys, this is also a CPU cost, not just memory. ProcessResponse shares the same readJSON/readItems code path, so RESPONSE_ARGS is affected identically.

Fix

Move the byte-budget check into the same else branch as the argument-limit check, computing lenStr first so its length is known before the check (mirroring the per-leaf write's own check three lines above it):

go
if argumentLimit > 0 && *argCount >= argumentLimit {
    iterationTruncated = true
} else {
    lenStr := strconv.Itoa(arrayLen)
    if byteBudget > 0 && *usedBytes+len(objKey)+len(lenStr) > byteBudget {
        iterationTruncated = true
    } else {
        k := string(objKey)
        res[k] = append(res[k], lenStr)
        *usedBytes += len(objKey) + len(lenStr)
        *argCount++
    }
}

Verified: the PoC above now returns truncated=true, with total flattened bytes bounded by the byte budget (163,160 bytes measured, vs. 4,020,596 before the fix).

AI involvement disclosure

  • AI tools/models used: Claude Sonnet 5 (Anthropic), via Claude Code.
  • What was generated/assisted: the 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 and heap measurement against commit 19b86824, confirmed the amplification and lack of truncation, verified the fix closes the gap, and drafted this addendum.

  • Review performed: reproduced by hand by running the PoC above against

a clean checkout of commit 19b86824 before and after the fix, comparing entry count, total flattened bytes, and the truncated flag; added and ran TestReadJSONArrayLengthWriteRespectsByteBudget, confirmed it fails against the pre-fix code (4,020,596 bytes, truncated=false) and passes against the fix; ran the full test suite, the build-tag matrix (coraza.no_memoize, coraza.rule.multiphase_evaluation, coraza.rule.no_regex_multiline), and the testing/coreruleset CRS regression suite, all green; reviewed by a human maintainer (fzipi) before this addendum was submitted.

Fix: https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/2

Patched in 3.8.1

The 3.8.0 fix was incomplete. 3.8.1 completes it: array-length entries produced while flattening JSON bodies are now held to the flattening byte budget (previously they could amplify a small body into a large memory allocation), a body over that budget now sets REQBODY_ERROR, and array-length entries no longer count toward SecArgumentsLimit. Upgrade to 3.8.1; 3.8.0 is listed as affected.

Severity (revised 2026-10-02)

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L (7.2, High).

Attack Complexity is Low: filler arguments alone trigger the drop, on any deployment, and since 3.8.0 the drop is deterministic. Availability is Low because this advisory also covers unbounded ARGS_POST growth from urlencoded bodies and, in 3.8.1, JSON flattening amplification, both of which consume memory per request. The previous vector scored Confidentiality Low and Availability None.

Impact metrics follow the convention used across Coraza's WAF-bypass advisories: the vulnerable component is Coraza, but the impact lands on the protected application, so Scope is Changed. The bypass hides a payload from inspection; the application still has to be vulnerable to it, so Integrity is Low and Confidentiality is not scored separately.

_AI involvement in this section: Claude Opus 5.5 (Anthropic), via Claude Code, re-derived the CVSS vector from the project's triage guidance (AGENTS.md, "CVSS preconditions get verified, not copied from the report") and drafted this text. A human maintainer (fzipi) chose the S:C/I:L impact convention and directed this update._

Technical POC Analysis

Bug class: CWE-693 (Protection Mechanism Failure), with a secondary CWE-770 (Allocation of Resources Without Limits or Throttling) facet.

Full technical POC analysis - root cause, step-by-step exploitation and patch breakdown - is part of the Pro and Business plans.

Executive ViewAI

Why It Matters

Coraza WAF v3 releases from 3.0.0 up to but not including 3.8.1 can silently drop request arguments before rule evaluation, allowing attackers to bypass critical CRS protections for SQL injection, XSS, RCE, and LFI and potentially reach backend applications unfiltered; the same defect can cause memory-exhaustion denial of service through unbounded POST argument growth. Because this weakens a core security control that many organizations rely on for internet-facing applications, leadership should prioritize urgent remediation and validate that no compensating layers are the only remaining defense. No public exploit identified at time of analysis.

Business Impact: High
Compensating Controls

Apply vendor patch.

Threat Intelligence Overview

EPSS Score
0.5%
Top 59% exploitable
Prevalence
Medium
open-source Go WAF engine, moderate production adoption (Caddy/Traefik, CRS users)
Threat Intel
1
1 source
Exploit
POC
Public exploit code exists
Data Quality
5 sources
Confidence: Medium

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.5%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation· derived
Poc
System Exposure· derived
Open
Automatable· derived
Yes
Human Impact
High

Priority Score

56
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +36
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Cross-Site Scripting occurs when an application accepts untrusted data and sends it to a web browser without proper validation or encoding.

More in Go

View all
CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2016-5386 HIGH
8.1 Jul 19

The net/http package in Go through 1.6 does not attempt to address RFC 3875 section 4.1.18 namespace conflicts and there

CVE-2025-68121 CRITICAL POC
10.0 Feb 05

Critical certificate validation bypass in Go crypto/tls during session resumption. If ClientCAs or RootCAs fields are mu

CVE-2019-14809 CRITICAL POC
9.8 Aug 13

net/url in Go before 1.11.13 and 1.12.x before 1.12.8 mishandles malformed hosts in URLs, leading to an authorization by

CVE-2026-90942 CRITICAL POC
9.3 Sep 14

Casdoor through 4.4.0 exposes its instance-wide built-in JWT signing private key unmasked through the /api/get-certs and

CVE-2026-101090 CRITICAL POC
9.3 Sep 27

Host header injection in Nezha 2.2.3's OAuth2 redirect endpoint (/api/v1/oauth2/{provider}) allows an unauthenticated re

CVE-2026-78428 HIGH POC
8.8 Sep 17

Session cross-wiring in SUSE NeuVector before 5.6.1 allows an unauthenticated remote attacker to obtain another user's a

CVE-2026-92791 HIGH POC
8.7 Sep 16

Unauthenticated path traversal in Uber Kraken through 0.1.29 allows remote attackers to read files outside the configure

CVE-2018-7187 HIGH POC
8.8 Feb 16

The "go get" implementation in Go 1.9.4, when the -insecure command-line option is used, does not validate the import pa

CVE-2026-16651 HIGH POC
8.7 Sep 21

Denial of service in Temporal Server allows an authenticated caller holding at least namespace read permission to crash

CVE-2026-82405 HIGH POC
8.7 Sep 23

Account takeover in Klever-Go's KleverUpdateAccountPermission built-in (all versions prior to 1.7.20) allows an attacker

CVE-2026-82409 HIGH POC
8.4 Sep 23

Maliciously crafted on-chain account names processed by Klever-Go prior to 1.7.20 are spliced into Elasticsearch _bulk r

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy