CWE-436
Allowed-with-ReviewInterpretation Conflict
Abstraction: Class · Status: Incomplete
Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.
287 vulnerabilities reference this CWE, most recent first.
GHSA-W253-M66G-RX24
Vulnerability from github – Published: 2026-10-08 17:51 – Updated: 2026-10-08 17:51Summary
Coraza fails to inspect URL-encoded form bodies when their valid Content-Type contains a media-type parameter, for example:
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
An 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.
This 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
Details
The affected component is request-body processor selection in internal/corazawaf/transaction.go.
Transaction.AddRequestHeader compares the complete lowercased header value using exact equality:
case "content-type":
val := strings.ToLower(value)
if val == "application/x-www-form-urlencoded" {
tx.variables.reqbodyProcessor.Set("URLENCODED")
} else if strings.HasPrefix(val, "multipart/form-data") {
tx.variables.reqbodyProcessor.Set("MULTIPART")
}
Source: internal/corazawaf/transaction.go, lines 383-390.
The 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.
ProcessRequestBody treats the empty processor as success:
rbp = strings.ToLower(rbp)
if rbp == "" {
tx.WAF.Rules.Eval(types.PhaseRequestBody, tx)
return tx.interruption, nil
}
Source: internal/corazawaf/transaction.go, lines 1115-1120.
Coraza 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 is never invoked.
The bundled middleware buffers and reconstructs the full request body before invoking the backend. The backend consequently receives data that was absent from Coraza's body variables.
The 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.
PoC
Save this self-contained test as parameterized_form_bypass_test.go in the Coraza repository root:
package coraza_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
coraza "github.com/corazawaf/coraza/v3"
corazahttp "github.com/corazawaf/coraza/v3/http"
)
func TestParameterizedFormInspectionBypass(t *testing.T) {
cfg := coraza.NewWAFConfig().WithDirectives(`
SecRuleEngine On
SecRequestBodyAccess On
SecRule ARGS_POST:cmd "@streq evil" "id:910001,phase:2,deny,status:403,t:none"
`)
waf, err := coraza.NewWAF(cfg)
if err != nil {
t.Fatal(err)
}
backendSaw := ""
handler := corazahttp.WrapHandler(waf, http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
t.Fatal(err)
}
backendSaw = r.PostForm.Get("cmd")
w.WriteHeader(http.StatusNoContent)
},
))
req := httptest.NewRequest(
http.MethodPost,
"http://example.test/",
strings.NewReader("cmd=evil"),
)
req.Header.Set(
"Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8",
)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("expected request to reach backend, status=%d", rec.Code)
}
if backendSaw != "evil" {
t.Fatalf("backend did not receive malicious value: %q", backendSaw)
}
}
Run:
go test . -run TestParameterizedFormInspectionBypass -v
Observed:
=== RUN TestParameterizedFormInspectionBypass
--- PASS: TestParameterizedFormInspectionBypass (0.00s)
PASS
The test passes only when Coraza fails to return its configured 403 response and the backend parses cmd=evil.
As a control, change the header to:
Content-Type: application/x-www-form-urlencoded
Coraza then selects the URL-encoded processor, exposes cmd=evil as ARGS_POST:cmd, and returns 403 before the backend runs.
Impact
This is a parser differential and request-body inspection bypass affecting applications protected by custom Coraza rules.
An 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.
Rules relying on these variables are affected on this path:
ARGS_POSTARGSfor POST-derived valuesARGS_POST_NAMESARGS_NAMESREQUEST_BODYREQUEST_BODY_LENGTH
This 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.
Affected 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.
The 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.
Follow-up (2026-09-30): duplicate Content-Type headers desynchronize processor selection from the parsed body
The 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.
Root cause
Transaction.AddRequestHeader is called once per header by the integrator (documented contract). Its content-type case runs on every call:
case "content-type":
val := strings.ToLower(value)
if strings.HasPrefix(val, "application/x-www-form-urlencoded") {
tx.variables.reqbodyProcessor.Set("URLENCODED")
} else if strings.HasPrefix(val, "multipart/form-data") {
tx.variables.reqbodyProcessor.Set("MULTIPART")
}
A 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.
PoC
Content-Type: multipart/form-data; boundary=XyZ
Content-Type: application/x-www-form-urlencoded
with a genuine multipart body containing cmd=evil and a shell.php file upload. Against commit 19b86824:
reqbodyProcessorresolves to"URLENCODED"(the second, spoofed header wins).- The URL-encoded processor runs against the raw multipart body text, parses without error, and produces nothing.
ARGS_POST:cmd,FILES, andFILES_NAMESare all empty -- the entire payload is invisible to any rule inspecting those variables.- The backend (or any integrator using
Header.Get, which reads the first value) still seesContent-Type: multipart/form-dataand parsescmd=eviland the uploadedshell.phpnormally.
Current 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.
Fix
Only 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).
As 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.
AI involvement disclosure
- AI tools/models used: Claude Sonnet 5 (Anthropic), via Claude Code.
- What was generated/assisted: the vulnerability hypothesis and repro shape were supplied by the reporter as an existing written finding; Claude Sonnet 5 independently re-derived the root cause by reading the current source, traced the
mimeType/Header.Getfirst-value asymmetry, wrote and ran a fresh PoC against commit19b86824confirming the full bypass (ARGS_POST/FILES/FILES_NAMESall empty), verified the fix closes it, and drafted this addendum. - Review performed: reproduced by hand against a clean checkout of commit
19b86824before and after the fix using the realTransactionAPI (AddRequestHeaderx2,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 thetesting/corerulesetCRS regression suite, all green; reviewed by a human maintainer (fzipi) before this addendum was submitted.
Fix: https://github.com/corazawaf/coraza-ghsa-w253-m66g-rx24/pull/2
Patched in 3.8.1
The 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.
Severity (revised 2026-10-02)
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N (5.8, Medium).
Attack Complexity is Low: every form and multipart parser accepts these Content-Type values. The previous vector used Scope Unchanged (5.3).
Impact metrics follow the convention used across Coraza's WAF-bypass advisories: the vulnerable component is Coraza, but the impact lands on the protected application, so Scope is Changed. The bypass hides a payload from inspection; the application still has to be vulnerable to it, so Integrity is Low and Confidentiality is not scored separately.
AI involvement in this section: Claude Opus 5.5 (Anthropic), via Claude Code, re-derived the CVSS vector from the project's triage guidance (AGENTS.md, "CVSS preconditions get verified, not copied from the report") and drafted this text. A human maintainer (fzipi) chose the S:C/I:L impact convention and directed this update.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/corazawaf/coraza/v3"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.4"
},
{
"fixed": "3.8.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T17:51:53Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### 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\u0027s 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\u0027s 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\u0027s `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\u0027s `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`\u0027s 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 \u0026REQUEST_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\u0027s 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\u0027s 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._",
"id": "GHSA-w253-m66g-rx24",
"modified": "2026-10-08T17:51:53Z",
"published": "2026-10-08T17:51:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/security/advisories/GHSA-w253-m66g-rx24"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/3b6241ef895b089c66a59e51068a81cf40986e4d"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/a55950ffa161f33a29e14f87d926d7df3b85cc74"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/dd100261cf1d2e2b053803a50c9db28dcaaa4b7c"
},
{
"type": "PACKAGE",
"url": "https://github.com/corazawaf/coraza"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/releases/tag/v3.8.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Coraza: URL-encoded form Content-Type parameters bypass Coraza body inspection"
}
GHSA-W548-VV26-RFF6
Vulnerability from github – Published: 2026-05-08 00:31 – Updated: 2026-05-11 18:31A server-side request forgery (SSRF) vulnerability was identified in the GitHub Enterprise Server notebook viewer that allowed an attacker to access internal services by exploiting URL parser confusion between the validation layer and the HTTP request library. The hostname validation used a different URL parser than the request library, enabling a crafted URL to pass validation while directing the request to an unintended host. Exploitation required network access to the GitHub Enterprise Server instance. This vulnerability affected all versions of GitHub Enterprise Server prior to 3.21 and was fixed in versions 3.16.18, 3.17.15, 3.18.9, 3.19.6, and 3.20.2. This vulnerability was reported via the GitHub Bug Bounty program.
{
"affected": [],
"aliases": [
"CVE-2026-8034"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-05-07T22:16:37Z",
"severity": "HIGH"
},
"details": "A server-side request forgery (SSRF) vulnerability was identified in the GitHub Enterprise Server notebook viewer that allowed an attacker to access internal services by exploiting URL parser confusion between the validation layer and the HTTP request library. The hostname validation used a different URL parser than the request library, enabling a crafted URL to pass validation while directing the request to an unintended host. Exploitation required network access to the GitHub Enterprise Server instance. This vulnerability affected all versions of GitHub Enterprise Server prior to 3.21 and was fixed in versions 3.16.18, 3.17.15, 3.18.9, 3.19.6, and 3.20.2. This vulnerability was reported via the GitHub Bug Bounty program.",
"id": "GHSA-w548-vv26-rff6",
"modified": "2026-05-11T18:31:37Z",
"published": "2026-05-08T00:31:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-8034"
},
{
"type": "WEB",
"url": "https://docs.github.com/en/enterprise-server@3.16/admin/release-notes#3.16.18"
},
{
"type": "WEB",
"url": "https://docs.github.com/en/enterprise-server@3.17/admin/release-notes#3.17.15"
},
{
"type": "WEB",
"url": "https://docs.github.com/en/enterprise-server@3.18/admin/release-notes#3.18.9"
},
{
"type": "WEB",
"url": "https://docs.github.com/en/enterprise-server@3.19/admin/release-notes#3.19.6"
},
{
"type": "WEB",
"url": "https://docs.github.com/en/enterprise-server@3.20/admin/release-notes#3.20.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H/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",
"type": "CVSS_V4"
}
]
}
GHSA-W5XW-C46M-3695
Vulnerability from github – Published: 2026-09-08 18:33 – Updated: 2026-09-08 18:33Interpretation conflict in Visual Studio Code allows an unauthorized attacker to bypass a security feature over a network.
{
"affected": [],
"aliases": [
"CVE-2026-81378"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-08T18:20:54Z",
"severity": "HIGH"
},
"details": "Interpretation conflict in Visual Studio Code allows an unauthorized attacker to bypass a security feature over a network.",
"id": "GHSA-w5xw-c46m-3695",
"modified": "2026-09-08T18:33:30Z",
"published": "2026-09-08T18:33:30Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-81378"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-81378"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-W6F4-3V35-QJHJ
Vulnerability from github – Published: 2026-03-21 03:31 – Updated: 2026-03-24 19:05Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-6rcp-vxwf-3mfp. This link is maintained to preserve external references.
Original Description
OpenClaw versions prior to 2026.2.24 contain a command injection vulnerability in the system.run shell-wrapper that allows attackers to execute hidden commands by injecting positional argv carriers after inline shell payloads. Attackers can craft misleading approval text while executing arbitrary commands through trailing positional arguments that bypass display context validation.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "2026.2.23"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-436",
"CWE-77"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-24T19:05:12Z",
"nvd_published_at": "2026-03-21T01:17:08Z",
"severity": "MODERATE"
},
"details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-6rcp-vxwf-3mfp. This link is maintained to preserve external references.\n\n## Original Description\nOpenClaw versions prior to 2026.2.24 contain a command injection vulnerability in the system.run shell-wrapper that allows attackers to execute hidden commands by injecting positional argv carriers after inline shell payloads. Attackers can craft misleading approval text while executing arbitrary commands through trailing positional arguments that bypass display context validation.",
"id": "GHSA-w6f4-3v35-qjhj",
"modified": "2026-03-24T19:05:12Z",
"published": "2026-03-21T03:31:13Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-6rcp-vxwf-3mfp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32052"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/0f0a680d3df81739ea5088a2f88e65f938b7936b"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/55cf92578d266987e390c4bf688196af98eac748"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-hidden-command-execution-via-shell-wrapper-positional-argv-carriers"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:A/VC:N/VI:H/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",
"type": "CVSS_V4"
}
],
"summary": "Duplicate Advisory: OpenClaw\u0027s system.run shell-wrapper positional argv carriers could execute hidden commands under misleading approval text",
"withdrawn": "2026-03-24T19:05:12Z"
}
GHSA-W7MG-Q4HH-M9CQ
Vulnerability from github – Published: 2022-05-24 19:14 – Updated: 2025-10-22 00:32An issue was discovered in Aviatrix Controller 6.x before 6.5-1804.1922. Unrestricted upload of a file with a dangerous type is possible, which allows an unauthenticated user to execute arbitrary code via directory traversal.
{
"affected": [],
"aliases": [
"CVE-2021-40870"
],
"database_specific": {
"cwe_ids": [
"CWE-23",
"CWE-434",
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-09-13T08:15:00Z",
"severity": "CRITICAL"
},
"details": "An issue was discovered in Aviatrix Controller 6.x before 6.5-1804.1922. Unrestricted upload of a file with a dangerous type is possible, which allows an unauthenticated user to execute arbitrary code via directory traversal.",
"id": "GHSA-w7mg-q4hh-m9cq",
"modified": "2025-10-22T00:32:19Z",
"published": "2022-05-24T19:14:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-40870"
},
{
"type": "WEB",
"url": "https://docs.aviatrix.com/HowTos/UCC_Release_Notes.html#security-note-9-11-2021"
},
{
"type": "WEB",
"url": "https://wearetradecraft.com/advisories/tc-2021-0002"
},
{
"type": "WEB",
"url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-40870"
},
{
"type": "WEB",
"url": "http://packetstormsecurity.com/files/164461/Aviatrix-Controller-6.x-Path-Traversal-Code-Execution.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WFC6-R584-VFW7
Vulnerability from github – Published: 2026-05-11 15:54 – Updated: 2026-05-14 20:38Impact
Applications using React Server Components can be vulnerable to cache poisoning when shared caches do not correctly partition response variants. Under affected conditions, an attacker can cause an RSC response to be served from the original URL and poison shared cache entries so later visitors receive component payloads instead of the expected HTML.
Fix
We now validate and interpret RSC request headers consistently across request classification and rendering, and we enforce the intended cache-busting behavior so RSC payloads are not unexpectedly served from the original URL.
Workarounds
If you cannot upgrade immediately, ensure your CDN or reverse proxy keys on the relevant RSC request headers and honors Vary, or disable shared caching for affected App Router and RSC responses.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "next"
},
"ranges": [
{
"events": [
{
"introduced": "14.2.0"
},
{
"fixed": "15.5.16"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "next"
},
"ranges": [
{
"events": [
{
"introduced": "16.0.0"
},
{
"fixed": "16.2.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44576"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-11T15:54:46Z",
"nvd_published_at": "2026-05-13T17:16:23Z",
"severity": "MODERATE"
},
"details": "### Impact\n\nApplications using React Server Components can be vulnerable to cache poisoning when shared caches do not correctly partition response variants. Under affected conditions, an attacker can cause an RSC response to be served from the original URL and poison shared cache entries so later visitors receive component payloads instead of the expected HTML.\n\n### Fix\n\nWe now validate and interpret `RSC` request headers consistently across request classification and rendering, and we enforce the intended cache-busting behavior so RSC payloads are not unexpectedly served from the original URL.\n\n### Workarounds\n\nIf you cannot upgrade immediately, ensure your CDN or reverse proxy keys on the relevant RSC request headers and honors `Vary`, or disable shared caching for affected App Router and RSC responses.",
"id": "GHSA-wfc6-r584-vfw7",
"modified": "2026-05-14T20:38:11Z",
"published": "2026-05-11T15:54:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/vercel/next.js/security/advisories/GHSA-wfc6-r584-vfw7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44576"
},
{
"type": "PACKAGE",
"url": "https://github.com/vercel/next.js"
},
{
"type": "WEB",
"url": "https://github.com/vercel/next.js/releases/tag/v15.5.16"
},
{
"type": "WEB",
"url": "https://github.com/vercel/next.js/releases/tag/v16.2.5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "Next.js vulnerable to cache poisoning in React Server Component responses"
}
GHSA-WFQ4-F757-C65C
Vulnerability from github – Published: 2022-05-24 17:13 – Updated: 2023-05-16 21:30For ABB eSOMS versions 4.0 to 6.0.3, the X-Content-Type-Options Header is missing in the HTTP response, potentially causing the response body to be interpreted and displayed as different content type other than declared. A possible attack scenario would be unauthorized code execution via text interpreted as JavaScript.
{
"affected": [],
"aliases": [
"CVE-2019-19089"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-04-02T20:15:00Z",
"severity": "MODERATE"
},
"details": "For ABB eSOMS versions 4.0 to 6.0.3, the X-Content-Type-Options Header is missing in the HTTP response, potentially causing the response body to be interpreted and displayed as different content type other than declared. A possible attack scenario would be unauthorized code execution via text interpreted as JavaScript.",
"id": "GHSA-wfq4-f757-c65c",
"modified": "2023-05-16T21:30:17Z",
"published": "2022-05-24T17:13:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-19089"
},
{
"type": "WEB",
"url": "https://search.abb.com/library/Download.aspx?DocumentID=9AKK107492A9964\u0026LanguageCode=en\u0026DocumentPartId=\u0026Action=Launch"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-WJFC-PGFP-PV9C
Vulnerability from github – Published: 2023-04-21 20:27 – Updated: 2023-04-21 20:27Impact
Improper header parsing. An attacker could sneak in a newline (\n) into both the header names and values. While the specification states that \r\n\r\n is used to terminate the header list, many servers in the wild will also accept \n\n.
Patches
The issue is patched in 1.6.1.
Workarounds
There are no known workarounds.
References
- https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "nyholm/psr7"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2023-04-21T20:27:30Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Impact\n\nImproper header parsing. An attacker could sneak in a newline (`\\n`) into both the header names and values. While the specification states that `\\r\\n\\r\\n` is used to terminate the header list, many servers in the wild will also accept `\\n\\n`.\n\n### Patches\n\nThe issue is patched in 1.6.1.\n\n### Workarounds\n\nThere are no known workarounds.\n\n### References\n\n* https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4",
"id": "GHSA-wjfc-pgfp-pv9c",
"modified": "2023-04-21T20:27:30Z",
"published": "2023-04-21T20:27:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Nyholm/psr7/security/advisories/GHSA-wjfc-pgfp-pv9c"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/psr7/security/advisories/GHSA-wxmh-65f7-jcvw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-29197"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/nyholm/psr7/2023-04-17.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/Nyholm/psr7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Improper Input Validation in nyholm/psr7"
}
GHSA-WMMP-3585-3RMP
Vulnerability from github – Published: 2026-09-08 21:33 – Updated: 2026-09-08 21:33Summary
Nodemailer resolves an international (IDN / non-ASCII) recipient domain to a different Punycode xn-- label than every UTS‑46‑conformant parser (web browsers, the WHATWG URL Standard, Node's url.domainToASCII, Python's idna). Its address normalizer (_normalizeAddress in lib/mime-node/index.js) uses the bundled raw RFC‑3492 Punycode codec with no UTS‑46 mapping/normalization, so a domain that a standards‑compliant validator maps to a trusted domain is delivered by Nodemailer to a different, attacker‑registrable domain.
An application that applies a domain allow‑list / same‑domain check to a recipient using a normal IDN‑aware parser (or that shows the normalized recipient to a user for confirmation) and then relies on Nodemailer to deliver to that domain can be induced to send email to an unintended external domain. This is the same weakness class as CVE‑2025‑13033 (Interpretation Conflict, CWE‑436) but reached through IDN/Punycode rather than quoted local‑parts, and it is not addressed by the 7.0.7 fix.
Because the mismatch can be triggered with an invisible character (U+00AD SOFT HYPHEN) that UTS‑46 folds away to the exact trusted domain string, no visible look‑alike/homograph is required.
Details
lib/mime-node/index.js → _normalizeAddress(address) (around lines 1307–1346) splits the address at the last @ and normalizes the domain like this:
// lib/mime-node/index.js
try {
if (/[\x80-]/.test(user)) {
encodedDomain = punycode.toUnicode(domain.toLowerCase()); // line ~1338
} else {
encodedDomain = punycode.toASCII(domain.toLowerCase()); // line ~1340
}
} catch (_err) {
// keep domain as supplied
}
return `${this._normalizeLocalPart(user)}@${encodedDomain}`; // line ~1346
punycode here is the project’s bundled codec (lib/punycode/), which is a pure RFC 3492 (Punycode) implementation. The only normalization applied to the domain is .toLowerCase(). It performs none of the UTS‑46 “IDNA2008 + compatibility processing” steps that browsers and DNS‑facing resolvers apply before Punycode encoding, specifically:
- removing Ignored code points such as
U+00ADSOFT HYPHEN, - Mapping full‑width / compatibility characters to their canonical ASCII forms,
- Unicode NFC normalization,
- validity checks.
As a result, for any domain containing a UTS‑46‑mapped or ‑ignored character, Nodemailer’s punycode.toASCII(...) produces a different A‑label than url.domainToASCII(...) (Node ≥ 7 / WHATWG), new URL('http://'+domain), browsers, and Python’s idna (uts46=True). Nodemailer then uses its A‑label as:
- the SMTP envelope recipient written to the wire as
RCPT TO:<local@xn--…>(getEnvelope()→lib/smtp-connection/index.js_setEnvelope), and - the address emitted in the
To:/From:headers (_convertAddresses).
So the domain a standards‑compliant validator computes and the domain Nodemailer actually delivers to disagree, on a syntactically valid, validator‑accepted address. Concrete divergences (verified on 9.0.6):
| recipient (raw) | UTS‑46 parser (url.domainToASCII) |
Nodemailer delivers to |
|---|---|---|
victim@compa{U+00AD}ny.com (invisible soft hyphen) |
company.com |
xn--company-pka.com |
victim@company.com (full‑width) |
company.com |
xn--mi7cd4afch9d.com |
user@exámple.com (NFD a+U+0301) |
xn--exmple-qta.com |
xn--example-vge.com |
This is the “Punycode / IDN parser discrepancy” technique documented in PortSwigger’s Splitting the email atom research (which produced e.g. Joomla CVE‑2024‑21725 and fixes in the PHP idna_convert library). The fix for CVE‑2025‑13033 (nodemailer 7.0.7) hardened the quoted‑local‑part path only; this IDN path is independent and still present in 9.0.6 (latest) and, given the long‑standing use of the bundled RFC‑3492 codec, earlier releases.
Suggested remediation: perform UTS‑46 processing before/at domain encoding so Nodemailer’s resolution matches browsers, validators, and DNS — e.g. use the runtime’s url.domainToASCII() (available since Node 7) instead of the raw punycode.toASCII, and decode with the matching UTS‑46 domainToUnicode. At minimum, reject a domain whose value changes under UTS‑46 mapping (i.e. punycode.toASCII(d) ≠ url.domainToASCII(d)).
PoC
Environment: Node.js ≥ 18, the published nodemailer@9.0.6. No special configuration; the discrepancy is in domain normalization itself.
poc-idn.js:
'use strict';
const net = require('net');
const url = require('url');
const nodemailer = require('nodemailer'); // 9.0.6
const TRUSTED = 'company.com'; // the only domain the app will mail
const RECIPIENT = 'victim@compa\u00ADny.com'; // attacker input: invisible U+00AD inside "company"
// The app's domain allow-list check, done the standard (UTS-46 / browser / WHATWG) way:
const seen = url.domainToASCII(RECIPIENT.split('@').pop());
console.log('validator (url.domainToASCII) sees:', JSON.stringify(seen),
seen === TRUSTED ? '=> ALLOWED (equals trusted domain)' : '');
// A tiny SMTP sink that prints the literal RCPT TO Nodemailer transmits:
const server = net.createServer(sock => {
let buf = ''; sock.write('220 sink\r\n');
sock.on('data', d => { buf += d; let i;
while ((i = buf.indexOf('\r\n')) >= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 2);
const u = line.toUpperCase();
if (u.startsWith('EHLO')) sock.write('250-sink\r\n250 8BITMIME\r\n');
else if (u.startsWith('RCPT')) { console.log('nodemailer transmits :', line); sock.write('250 ok\r\n'); }
else if (u.startsWith('DATA')) sock.write('354 go\r\n');
else if (line === '.') sock.write('250 ok\r\n');
else if (u.startsWith('QUIT')) { sock.write('221 bye\r\n'); sock.end(); }
else sock.write('250 ok\r\n'); } });
});
server.listen(0, '127.0.0.1', async () => {
const t = nodemailer.createTransport({ host: '127.0.0.1', port: server.address().port, secure: false });
await t.sendMail({ from: 'app@company.com', to: RECIPIENT, subject: 'reset your password', text: 'secret link' });
t.close(); server.close();
});
Run:
npm init -y && npm install nodemailer@9.0.6
node poc-idn.js
Actual output (Nodemailer 9.0.6):
validator (url.domainToASCII) sees: "company.com" => ALLOWED (equals trusted domain)
nodemailer transmits : RCPT TO:<victim@xn--company-pka.com>
The application’s domain check approves company.com, but the message is sent to xn--company-pka.com — a different domain an attacker can register — carrying the To: header <victim@xn--company-pka.com> as well.
A containerized version that proves the same result against a real RFC 5321 SMTP server (aiosmtpd) is included alongside this report (docker compose up --build, cases R6/IDN); the receiving server accepts RCPT TO:<victim@xn--company-pka.com> and reports the recipient domain as xn--company-pka.com.
Impact
Any application that uses Nodemailer to send mail to a recipient whose domain is subjected to a security or trust decision made with a different (UTS‑46‑conformant) parser, and then trusts Nodemailer to deliver to that domain. This includes:
* recipient allow‑list / block‑list / “same corporate domain” checks implemented with new URL(), url.domainToASCII, a browser‑side check, or an IDN library;
* flows that display or log the normalized recipient domain for human confirmation (the shown company.com differs from the delivered xn--company-pka.com);
* any domain‑gated feature (employee‑only registration, “send only to our tenant”, notification routing).
Patched in 9.1.0
Domain encoding now applies UTS-46 (259c32d), so victim@company.com resolves to company.com, matching url.domainToASCII and browsers.
One caveat on the suggested remediation, hardened in b212ac4: url.domainToASCII is a WHATWG host parser, not a pure UTS-46 mapper. It terminates the host at /, \\, ? and # and percent-decodes. Used unguarded it introduces a worse version of the same weakness, since user@attacker.example/mail.corp.example encodes to the deliverable user@attacker.example where the bundled Punycode codec left it intact and unroutable. Those characters are now kept away from the mapper.
On severity, "attacker-registrable" is doing significant work in the report: xn--company-pka.com decodes to a label containing U+00AD and xn--mi7cd4afch9d.com to full-width Latin, neither of which Verisign's IDN tables permit for a .com registration. The misdelivery and the confirmation-UI mismatch stand regardless, which is why this is rated level with the comment issue rather than above it.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "nodemailer"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "9.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-08T21:33:32Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nNodemailer resolves an international (IDN / non-ASCII) recipient **domain** to a different Punycode `xn--` label than every UTS\u201146\u2011conformant parser (web browsers, the WHATWG URL Standard, Node\u0027s `url.domainToASCII`, Python\u0027s `idna`). Its address normalizer (`_normalizeAddress` in `lib/mime-node/index.js`) uses the bundled **raw RFC\u20113492 Punycode codec with no UTS\u201146 mapping/normalization**, so a domain that a standards\u2011compliant validator maps to a trusted domain is delivered by Nodemailer to a **different, attacker\u2011registrable domain**.\n\nAn application that applies a domain allow\u2011list / same\u2011domain check to a recipient using a normal IDN\u2011aware parser (or that shows the normalized recipient to a user for confirmation) and then relies on Nodemailer to deliver to that domain can be induced to send email to an **unintended external domain**. This is the same weakness class as CVE\u20112025\u201113033 (Interpretation Conflict, CWE\u2011436) but reached through IDN/Punycode rather than quoted local\u2011parts, and it is not addressed by the 7.0.7 fix.\n\nBecause the mismatch can be triggered with an **invisible** character (U+00AD SOFT HYPHEN) that UTS\u201146 folds away to the *exact* trusted domain string, no visible look\u2011alike/homograph is required.\n\n### Details\n\n`lib/mime-node/index.js` \u2192 `_normalizeAddress(address)` (around lines 1307\u20131346) splits the address at the last `@` and normalizes the domain like this:\n\n```js\n// lib/mime-node/index.js\ntry {\n if (/[\\x80-\uffff]/.test(user)) {\n encodedDomain = punycode.toUnicode(domain.toLowerCase()); // line ~1338\n } else {\n encodedDomain = punycode.toASCII(domain.toLowerCase()); // line ~1340\n }\n} catch (_err) {\n // keep domain as supplied\n}\nreturn `${this._normalizeLocalPart(user)}@${encodedDomain}`; // line ~1346\n```\n\n`punycode` here is the project\u2019s bundled codec (`lib/punycode/`), which is a **pure RFC 3492 (Punycode) implementation**. The only normalization applied to the domain is `.toLowerCase()`. It performs **none of the UTS\u201146 \u201cIDNA2008 + compatibility processing\u201d steps** that browsers and DNS\u2011facing resolvers apply before Punycode encoding, specifically:\n\n* removing **Ignored** code points such as `U+00AD` SOFT HYPHEN,\n* **Mapping** full\u2011width / compatibility characters to their canonical ASCII forms,\n* Unicode **NFC** normalization,\n* validity checks.\n\nAs a result, for any domain containing a UTS\u201146\u2011mapped or \u2011ignored character, Nodemailer\u2019s `punycode.toASCII(...)` produces a **different A\u2011label** than `url.domainToASCII(...)` (Node \u2265 7 / WHATWG), `new URL(\u0027http://\u0027+domain)`, browsers, and Python\u2019s `idna` (`uts46=True`). Nodemailer then uses its A\u2011label as:\n\n* the SMTP envelope recipient written to the wire as `RCPT TO:\u003clocal@xn--\u2026\u003e` (`getEnvelope()` \u2192 `lib/smtp-connection/index.js` `_setEnvelope`), **and**\n* the address emitted in the `To:` / `From:` headers (`_convertAddresses`).\n\nSo the domain a standards\u2011compliant validator computes and the domain Nodemailer actually delivers to **disagree**, on a syntactically valid, validator\u2011accepted address. Concrete divergences (verified on 9.0.6):\n\n| recipient (raw) | UTS\u201146 parser (`url.domainToASCII`) | Nodemailer delivers to |\n|---|---|---|\n| `victim@compa{U+00AD}ny.com` (invisible soft hyphen) | `company.com` | `xn--company-pka.com` |\n| `victim@\uff43\uff4f\uff4d\uff50\uff41\uff4e\uff59.com` (full\u2011width) | `company.com` | `xn--mi7cd4afch9d.com` |\n| `user@ex\u00e1mple.com` (NFD `a`+U+0301) | `xn--exmple-qta.com` | `xn--example-vge.com` |\n\nThis is the \u201cPunycode / IDN parser discrepancy\u201d technique documented in PortSwigger\u2019s *Splitting the email atom* research (which produced e.g. Joomla CVE\u20112024\u201121725 and fixes in the PHP `idna_convert` library). The fix for CVE\u20112025\u201113033 (nodemailer 7.0.7) hardened the *quoted\u2011local\u2011part* path only; this IDN path is independent and still present in **9.0.6 (latest)** and, given the long\u2011standing use of the bundled RFC\u20113492 codec, earlier releases.\n\n**Suggested remediation:** perform UTS\u201146 processing before/at domain encoding so Nodemailer\u2019s resolution matches browsers, validators, and DNS \u2014 e.g. use the runtime\u2019s `url.domainToASCII()` (available since Node 7) instead of the raw `punycode.toASCII`, and decode with the matching UTS\u201146 `domainToUnicode`. At minimum, reject a domain whose value changes under UTS\u201146 mapping (i.e. `punycode.toASCII(d)` \u2260 `url.domainToASCII(d)`).\n\n### PoC\n\nEnvironment: Node.js \u2265 18, the published `nodemailer@9.0.6`. No special configuration; the discrepancy is in domain normalization itself.\n\n`poc-idn.js`:\n\n```js\n\u0027use strict\u0027;\nconst net = require(\u0027net\u0027);\nconst url = require(\u0027url\u0027);\nconst nodemailer = require(\u0027nodemailer\u0027); // 9.0.6\n\nconst TRUSTED = \u0027company.com\u0027; // the only domain the app will mail\nconst RECIPIENT = \u0027victim@compa\\u00ADny.com\u0027; // attacker input: invisible U+00AD inside \"company\"\n\n// The app\u0027s domain allow-list check, done the standard (UTS-46 / browser / WHATWG) way:\nconst seen = url.domainToASCII(RECIPIENT.split(\u0027@\u0027).pop());\nconsole.log(\u0027validator (url.domainToASCII) sees:\u0027, JSON.stringify(seen),\n seen === TRUSTED ? \u0027=\u003e ALLOWED (equals trusted domain)\u0027 : \u0027\u0027);\n\n// A tiny SMTP sink that prints the literal RCPT TO Nodemailer transmits:\nconst server = net.createServer(sock =\u003e {\n let buf = \u0027\u0027; sock.write(\u0027220 sink\\r\\n\u0027);\n sock.on(\u0027data\u0027, d =\u003e { buf += d; let i;\n while ((i = buf.indexOf(\u0027\\r\\n\u0027)) \u003e= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 2);\n const u = line.toUpperCase();\n if (u.startsWith(\u0027EHLO\u0027)) sock.write(\u0027250-sink\\r\\n250 8BITMIME\\r\\n\u0027);\n else if (u.startsWith(\u0027RCPT\u0027)) { console.log(\u0027nodemailer transmits :\u0027, line); sock.write(\u0027250 ok\\r\\n\u0027); }\n else if (u.startsWith(\u0027DATA\u0027)) sock.write(\u0027354 go\\r\\n\u0027);\n else if (line === \u0027.\u0027) sock.write(\u0027250 ok\\r\\n\u0027);\n else if (u.startsWith(\u0027QUIT\u0027)) { sock.write(\u0027221 bye\\r\\n\u0027); sock.end(); }\n else sock.write(\u0027250 ok\\r\\n\u0027); } });\n});\nserver.listen(0, \u0027127.0.0.1\u0027, async () =\u003e {\n const t = nodemailer.createTransport({ host: \u0027127.0.0.1\u0027, port: server.address().port, secure: false });\n await t.sendMail({ from: \u0027app@company.com\u0027, to: RECIPIENT, subject: \u0027reset your password\u0027, text: \u0027secret link\u0027 });\n t.close(); server.close();\n});\n```\n\nRun:\n\n```\nnpm init -y \u0026\u0026 npm install nodemailer@9.0.6\nnode poc-idn.js\n```\n\nActual output (Nodemailer 9.0.6):\n\n```\nvalidator (url.domainToASCII) sees: \"company.com\" =\u003e ALLOWED (equals trusted domain)\nnodemailer transmits : RCPT TO:\u003cvictim@xn--company-pka.com\u003e\n```\n\nThe application\u2019s domain check approves `company.com`, but the message is sent to `xn--company-pka.com` \u2014 a **different domain an attacker can register** \u2014 carrying the `To:` header `\u003cvictim@xn--company-pka.com\u003e` as well.\n\nA containerized version that proves the same result against a **real RFC 5321 SMTP server** (`aiosmtpd`) is included alongside this report (`docker compose up --build`, cases `R6`/IDN); the receiving server accepts `RCPT TO:\u003cvictim@xn--company-pka.com\u003e` and reports the recipient domain as `xn--company-pka.com`.\n\n### Impact\n\nAny application that uses Nodemailer to send mail to a recipient whose domain is subjected to a security or trust decision made with a *different* (UTS\u201146\u2011conformant) parser, and then trusts Nodemailer to deliver to that domain. This includes:\n * recipient **allow\u2011list / block\u2011list / \u201csame corporate domain\u201d checks** implemented with `new URL()`, `url.domainToASCII`, a browser\u2011side check, or an IDN library;\n * flows that **display or log the normalized recipient domain** for human confirmation (the shown `company.com` differs from the delivered `xn--company-pka.com`);\n * any domain\u2011gated feature (employee\u2011only registration, \u201csend only to our tenant\u201d, notification routing).\n\n## Patched in 9.1.0\n\nDomain encoding now applies UTS-46 ([259c32d](https://github.com/nodemailer/nodemailer/commit/259c32d)), so `victim@compa\u00adny.com` resolves to `company.com`, matching `url.domainToASCII` and browsers.\n\nOne caveat on the suggested remediation, hardened in [b212ac4](https://github.com/nodemailer/nodemailer/commit/b212ac4): `url.domainToASCII` is a WHATWG **host parser**, not a pure UTS-46 mapper. It terminates the host at `/`, `\\\\`, `?` and `#` and percent-decodes. Used unguarded it introduces a worse version of the same weakness, since `user@attacker.example/mail.corp.example` encodes to the deliverable `user@attacker.example` where the bundled Punycode codec left it intact and unroutable. Those characters are now kept away from the mapper.\n\nOn severity, \"attacker-registrable\" is doing significant work in the report: `xn--company-pka.com` decodes to a label containing U+00AD and `xn--mi7cd4afch9d.com` to full-width Latin, neither of which Verisign\u0027s IDN tables permit for a .com registration. The misdelivery and the confirmation-UI mismatch stand regardless, which is why this is rated level with the comment issue rather than above it.",
"id": "GHSA-wmmp-3585-3rmp",
"modified": "2026-09-08T21:33:32Z",
"published": "2026-09-08T21:33:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/security/advisories/GHSA-wmmp-3585-3rmp"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/pull/1848"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/commit/259c32d7d266301e3377a212776c3fff993c0148"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/commit/b212ac4e27bce8182478044fcb8d1642ccdad46e"
},
{
"type": "PACKAGE",
"url": "https://github.com/nodemailer/nodemailer"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/releases/tag/v9.1.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Nodemailer: IDN/Punycode domain allow-list bypass leads to email delivery to an attacker-controlled domain"
}
GHSA-WVJ2-96WP-FQ3F
Vulnerability from github – Published: 2026-02-26 22:20 – Updated: 2026-02-26 22:20The Go MCP SDK used Go's standard encoding/json.Unmarshal for JSON-RPC and MCP protocol message parsing. Go's standard library performs case-insensitive matching of JSON keys to struct field tags — a field tagged json:"method" would also match "Method", "METHOD", etc. Additionally, Go's standard library folds the Unicode characters ſ (U+017F) and K (U+212A) to their ASCII equivalents s and k, meaning fields like "paramſ" would match "params". This violated the JSON-RPC 2.0 specification, which defines exact field names.
Impact:
A malicious MCP peer may have been able to send protocol messages with non-standard field casing (e.g., "Method" instead of "method") that the SDK would silently accept. This had the potential for: - Bypassing intermediary inspection: Proxies or policy layers that matched on exact field names may have failed to detect or filter these messages. - Cross-implementation inconsistency: Other MCP SDKs (TypeScript, Python) use case-sensitive parsing and would reject the same messages, creating potential security-boundary confusion.
Fix:
Go's standard JSON unmarshaling was replaced with a case-sensitive decoder (github.com/segmentio/encoding) in commit 7b8d81c. Users are advised to update to v1.3.1 to resolve this issue.
Credits:
MCP Go SDK thanks Francesco Lacerenza (Doyensec) for reporting this issue.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/modelcontextprotocol/go-sdk"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.3.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-27896"
],
"database_specific": {
"cwe_ids": [
"CWE-178",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-26T22:20:08Z",
"nvd_published_at": "2026-02-26T01:16:25Z",
"severity": "HIGH"
},
"details": "The Go MCP SDK used Go\u0027s standard encoding/json.Unmarshal for JSON-RPC and MCP protocol message parsing. Go\u0027s standard library performs case-insensitive matching of JSON keys to struct field tags \u2014 a field tagged json:\"method\" would also match \"Method\", \"METHOD\", etc. Additionally, Go\u0027s standard library folds the Unicode characters \u017f (U+017F) and K (U+212A) to their ASCII equivalents s and k, meaning fields like \"param\u017f\" would match \"params\". This violated the JSON-RPC 2.0 specification, which defines exact field names.\n\n#### Impact:\n\nA malicious MCP peer may have been able to send protocol messages with non-standard field casing (e.g., \"Method\" instead of \"method\") that the SDK would silently accept. This had the potential for:\n - **Bypassing intermediary inspection:** Proxies or policy layers that matched on exact field names may have failed to detect or filter these messages.\n - **Cross-implementation inconsistency:** Other MCP SDKs (TypeScript, Python) use case-sensitive parsing and would reject the same messages, creating potential security-boundary confusion.\n\n#### Fix:\n\nGo\u0027s standard JSON unmarshaling was replaced with a case-sensitive decoder (github.com/segmentio/encoding) in commit 7b8d81c. Users are advised to update to v1.3.1 to resolve this issue.\n\n#### Credits:\nMCP Go SDK thanks Francesco Lacerenza (Doyensec) for reporting this issue.",
"id": "GHSA-wvj2-96wp-fq3f",
"modified": "2026-02-26T22:20:08Z",
"published": "2026-02-26T22:20:08Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/modelcontextprotocol/go-sdk/security/advisories/GHSA-wvj2-96wp-fq3f"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27896"
},
{
"type": "WEB",
"url": "https://github.com/modelcontextprotocol/go-sdk/commit/7b8d81c264074404abdf5aa16e2cf0c2d9c64cc0"
},
{
"type": "PACKAGE",
"url": "https://github.com/modelcontextprotocol/go-sdk"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N",
"type": "CVSS_V4"
}
],
"summary": "MCP Go SDK Vulnerable to Improper Handling of Case Sensitivity"
}
No mitigation information available for this CWE.
CAPEC-105: HTTP Request Splitting
An adversary abuses the flexibility and discrepancies in the parsing and interpretation of HTTP Request messages by different intermediary HTTP agents (e.g., load balancer, reverse proxy, web caching proxies, application firewalls, etc.) to split a single HTTP request into multiple unauthorized and malicious HTTP requests to a back-end HTTP agent (e.g., web server).
See CanPrecede relationships for possible consequences.
CAPEC-273: HTTP Response Smuggling
An adversary manipulates and injects malicious content in the form of secret unauthorized HTTP responses, into a single HTTP response from a vulnerable or compromised back-end HTTP agent (e.g., server).
See CanPrecede relationships for possible consequences.
CAPEC-34: HTTP Response Splitting
An adversary manipulates and injects malicious content, in the form of secret unauthorized HTTP responses, into a single HTTP response from a vulnerable or compromised back-end HTTP agent (e.g., web server) or into an already spoofed HTTP response from an adversary controlled domain/site.
See CanPrecede relationships for possible consequences.