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.
289 vulnerabilities reference this CWE, most recent first.
GHSA-36HH-X5P5-JGC8
Vulnerability from github – Published: 2026-05-27 00:37 – Updated: 2026-05-27 00:37Impact
The two parsers resolved duplicates inconsistently and silently:
- Content.disposition() retained the last occurrence of each parameter.
- Content.type() retained the first occurrence of charset and boundary.
Either behavior creates a parameter-smuggling primitive when another component in the request-processing chain (a WAF, reverse proxy, security filter, or alternate parser) resolves duplicates the opposite way. The primary attack vector is upload filename allowlist bypass:
Content-Disposition: form-data; name="file"; filename="safe.txt"; filename="shell.php"
Patches
The issue has been patched in 6.0.2.
Workarounds
Pre or post validate headers looking for duplicates.
Resources
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@hapi/content"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "6.0.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44974"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-27T00:37:20Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Impact\nThe two parsers resolved duplicates inconsistently and silently:\n- `Content.disposition()` retained the last occurrence of each parameter.\n- `Content.type()` retained the first occurrence of charset and boundary.\n\nEither behavior creates a parameter-smuggling primitive when another component in the request-processing chain (a WAF, reverse proxy, security filter, or alternate parser) resolves duplicates the opposite way. The primary attack vector is upload filename allowlist bypass:\n\n`Content-Disposition: form-data; name=\"file\"; filename=\"safe.txt\"; filename=\"shell.php\"`\n\n### Patches\nThe issue has been patched in 6.0.2.\n\n### Workarounds\nPre or post validate headers looking for duplicates.\n\n### Resources\n- [RFC 6266 \u00a74.1 \u2014 Content-Disposition syntax](https://www.rfc-editor.org/rfc/rfc6266#section-4.1)\n- [RFC 7231 \u00a73.1.1.1 \u2014 Content-Type syntax](https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1)\n- [RFC 7230 \u00a73.2.6 \u2014 token character set](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.6)",
"id": "GHSA-36hh-x5p5-jgc8",
"modified": "2026-05-27T00:37:20Z",
"published": "2026-05-27T00:37:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/hapijs/content/security/advisories/GHSA-36hh-x5p5-jgc8"
},
{
"type": "WEB",
"url": "https://github.com/hapijs/content/commit/3850079550c191d25e3643dc82a6d61144db8c2f"
},
{
"type": "PACKAGE",
"url": "https://github.com/hapijs/content"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:H/SA:N",
"type": "CVSS_V4"
}
],
"summary": "@hapi/content header parser has a parameter smuggling issue that allows upload-filter bypass via duplicate parameters"
}
GHSA-392F-GGF5-FP3C
Vulnerability from github – Published: 2026-03-02 21:49 – Updated: 2026-03-02 21:49Summary
A paired node could supply Unicode-confusable platform or deviceFamily metadata that passed metadata pinning but classified differently for command policy resolution, broadening default node command allowlists.
Impact
This is a policy-bypass issue within the paired-node trust boundary and can expand node command availability beyond intended defaults.
Fix
Node metadata canonicalization was hardened against confusables, and unknown platform defaults were made conservative (excluding system.run and system.which unless explicitly allowlisted).
Affected and Patched Versions
- Affected:
<= 2026.2.26 - Patched:
2026.3.1
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2026.3.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-176",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-02T21:49:33Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\nA paired node could supply Unicode-confusable `platform` or `deviceFamily` metadata that passed metadata pinning but classified differently for command policy resolution, broadening default node command allowlists.\n\n### Impact\nThis is a policy-bypass issue within the paired-node trust boundary and can expand node command availability beyond intended defaults.\n\n### Fix\nNode metadata canonicalization was hardened against confusables, and unknown platform defaults were made conservative (excluding `system.run` and `system.which` unless explicitly allowlisted).\n\n### Affected and Patched Versions\n- Affected: `\u003c= 2026.2.26`\n- Patched: `2026.3.1`",
"id": "GHSA-392f-ggf5-fp3c",
"modified": "2026-03-02T21:49:33Z",
"published": "2026-03-02T21:49:33Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-392f-ggf5-fp3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw: Unicode canonicalization drift in node metadata policy classification could broaden node allowlists"
}
GHSA-39WR-7Q6H-CF68
Vulnerability from github – Published: 2026-09-18 17:14 – Updated: 2026-09-18 17:14Summary
The URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.
Details
The current lmdeploy project uses _is_safe_url to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks.
However, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using
_is_safe_url for URL validation, and then using requests.Session().get to send the request.
The core issue:
urlparse() and requests disagree on which host a URL like http://127.0.0.1:6666\@1.1.1.1 points to:
urlparse()treats\as a regular character and@as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public)requeststreats\as a path character, connecting to127.0.0.1(internal)
Below is a test code I wrote following the code.
from urllib.parse import urlparse
import ipaddress
import socket
import requests
def _is_safe_url(url: str) -> tuple[bool, str]:
"""Check if the URL is safe to fetch (not internal/private)."""
try:
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return False, f"Unsupported scheme: {parsed.scheme}"
hostname = parsed.hostname
if not hostname:
return False, "Could not parse hostname from URL"
# check all IPs (IPv4 + IPv6) using getaddrinfo
try:
infos = socket.getaddrinfo(hostname, None)
except socket.gaierror:
return False, "Hostname resolution failed"
for info in infos:
ip = ipaddress.ip_address(info[4][0])
# block any IP that is not globally routable (covers private, loopback,
# link-local, multicast, reserved, unspecified, etc.)
if not ip.is_global:
return False, f"Blocked non-global IP detected: {ip}"
return True, "URL is safe"
except Exception as e:
return False, f"URL validation failed: {str(e)}"
# url = "http://127.0.0.1:6666"
url = "http://127.0.0.1:6666\@1.1.1.1"
is_safe, reason = _is_safe_url(url)
if not is_safe:
raise ValueError(f"URL is blocked for security reasons: {reason}")
fetch_timeout = 10
client = requests.Session()
client.max_redirects = 3
response = client.get(url, timeout=fetch_timeout, allow_redirects=True)
When an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it.
However, when an attacker uses
http://127.0.0.1:6666\@1.1.1.1, the detection logic resolves the host to 1.1.1.1, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to http://127.0.0.1:6666/, bypassing the detection and achieving an SSRF attack.
PoC
http://127.0.0.1:6666\@1.1.1.1
Impact
SSRF
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "lmdeploy"
},
"ranges": [
{
"events": [
{
"introduced": "0.12.3"
},
{
"fixed": "0.15.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-436",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-18T17:14:06Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\nThe URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.\n\n### Details\nThe current lmdeploy project uses `_is_safe_url` to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks.\n\u003cimg width=\"943\" height=\"836\" alt=\"QQ20260416-203956-16-1\" src=\"https://github.com/user-attachments/assets/042faad1-7458-444a-bbc9-525c772b0a4d\" /\u003e\nHowever, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using `_is_safe_url` for URL validation, and then using requests.Session().get to send the request.\n\u003cimg width=\"1086\" height=\"576\" alt=\"QQ20260416-204053-16-2\" src=\"https://github.com/user-attachments/assets/7ffb8a69-b155-483a-90be-016c53e6387a\" /\u003e\nThe core issue:\u00a0`urlparse()`\u00a0and\u00a0`requests`\u00a0disagree on which host a URL like\u00a0`http://127.0.0.1:6666\\@1.1.1.1`\u00a0points to:\n\n- `urlparse()`\u00a0treats\u00a0`\\`\u00a0as a regular character and\u00a0`@`\u00a0as the userinfo-host delimiter, so it extracts hostname as\u00a01.1.1.1\u00a0(public)\n- `requests`\u00a0treats\u00a0`\\`\u00a0as a path character, connecting to\u00a0`127.0.0.1`\u00a0(internal)\n\nBelow is a test code I wrote following the code.\n```\nfrom urllib.parse import urlparse\nimport ipaddress\nimport socket\nimport requests\n\n\ndef _is_safe_url(url: str) -\u003e tuple[bool, str]:\n \"\"\"Check if the URL is safe to fetch (not internal/private).\"\"\"\n try:\n parsed = urlparse(url)\n if parsed.scheme not in (\"http\", \"https\"):\n return False, f\"Unsupported scheme: {parsed.scheme}\"\n\n hostname = parsed.hostname\n if not hostname:\n return False, \"Could not parse hostname from URL\"\n\n # check all IPs (IPv4 + IPv6) using getaddrinfo\n try:\n infos = socket.getaddrinfo(hostname, None)\n except socket.gaierror:\n return False, \"Hostname resolution failed\"\n\n for info in infos:\n ip = ipaddress.ip_address(info[4][0])\n # block any IP that is not globally routable (covers private, loopback,\n # link-local, multicast, reserved, unspecified, etc.)\n if not ip.is_global:\n return False, f\"Blocked non-global IP detected: {ip}\"\n\n return True, \"URL is safe\"\n except Exception as e:\n return False, f\"URL validation failed: {str(e)}\"\n\n\n# url = \"http://127.0.0.1:6666\"\nurl = \"http://127.0.0.1:6666\\@1.1.1.1\"\nis_safe, reason = _is_safe_url(url)\nif not is_safe:\n raise ValueError(f\"URL is blocked for security reasons: {reason}\")\n\nfetch_timeout = 10\n\nclient = requests.Session()\nclient.max_redirects = 3\nresponse = client.get(url, timeout=fetch_timeout, allow_redirects=True)\n```\nWhen an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it.\n\u003cimg width=\"1286\" height=\"195\" alt=\"QQ20260416-204234-16-3\" src=\"https://github.com/user-attachments/assets/b921ff01-3b9f-49a5-a410-bd21fe42f9c9\" /\u003e\nHowever, when an attacker uses `http://127.0.0.1:6666\\@1.1.1.1`, the detection logic resolves the host to `1.1.1.1`, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to `http://127.0.0.1:6666/`, bypassing the detection and achieving an SSRF attack.\n\n\u003cimg width=\"2064\" height=\"154\" alt=\"QQ20260416-204319-16-4\" src=\"https://github.com/user-attachments/assets/5da18f35-f400-46e6-9bf3-1330ba424b02\" /\u003e\n\n### PoC\n```\nhttp://127.0.0.1:6666\\@1.1.1.1\n```\n\n### Impact\nSSRF",
"id": "GHSA-39wr-7q6h-cf68",
"modified": "2026-09-18T17:14:06Z",
"published": "2026-09-18T17:14:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/InternLM/lmdeploy/security/advisories/GHSA-39wr-7q6h-cf68"
},
{
"type": "PACKAGE",
"url": "https://github.com/InternLM/lmdeploy"
},
{
"type": "WEB",
"url": "https://github.com/InternLM/lmdeploy/releases/tag/v0.15.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "LMDeploy has an SSRF bypass"
}
GHSA-3F78-WQ4J-7VGR
Vulnerability from github – Published: 2023-01-17 21:30 – Updated: 2023-01-25 03:30Prior to Apache HTTP Server 2.4.55, a malicious backend can cause the response headers to be truncated early, resulting in some headers being incorporated into the response body. If the later headers have any security purpose, they will not be interpreted by the client.
{
"affected": [],
"aliases": [
"CVE-2022-37436"
],
"database_specific": {
"cwe_ids": [
"CWE-113",
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-01-17T20:15:00Z",
"severity": "MODERATE"
},
"details": "Prior to Apache HTTP Server 2.4.55, a malicious backend can cause the response headers to be truncated early, resulting in some headers being incorporated into the response body. If the later headers have any security purpose, they will not be interpreted by the client.",
"id": "GHSA-3f78-wq4j-7vgr",
"modified": "2023-01-25T03:30:32Z",
"published": "2023-01-17T21:30:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-37436"
},
{
"type": "WEB",
"url": "https://httpd.apache.org/security/vulnerabilities_24.html"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202309-01"
}
],
"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"
}
]
}
GHSA-3JGF-R68H-XFQM
Vulnerability from github – Published: 2024-05-05 03:30 – Updated: 2024-08-07 19:55btcd before 0.24.0 does not correctly implement the consensus rules outlined in BIP 68 and BIP 112, making it susceptible to consensus failures. Specifically, it uses the transaction version as a signed integer when it is supposed to be treated as unsigned. There can be a chain split and loss of funds.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/btcsuite/btcd"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.24.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-34478"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2024-05-06T14:28:56Z",
"nvd_published_at": "2024-05-05T01:15:06Z",
"severity": "MODERATE"
},
"details": "btcd before 0.24.0 does not correctly implement the consensus rules outlined in BIP 68 and BIP 112, making it susceptible to consensus failures. Specifically, it uses the transaction version as a signed integer when it is supposed to be treated as unsigned. There can be a chain split and loss of funds.",
"id": "GHSA-3jgf-r68h-xfqm",
"modified": "2024-08-07T19:55:49Z",
"published": "2024-05-05T03:30:47Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-34478"
},
{
"type": "WEB",
"url": "https://github.com/btcsuite/btcd/pull/1981"
},
{
"type": "WEB",
"url": "https://github.com/btcsuite/btcd/commit/253b688c68b89eca7eb75d4d5443dbdbc928db3c"
},
{
"type": "WEB",
"url": "https://delvingbitcoin.org/t/disclosure-btcd-consensus-bugs-due-to-usage-of-signed-transaction-version/455"
},
{
"type": "PACKAGE",
"url": "https://github.com/btcsuite/btcd"
},
{
"type": "WEB",
"url": "https://github.com/btcsuite/btcd/blob/e4c88c3a3ecb1813529bf3dddc7a865bd418a6b8/blockchain/chain.go#L383C1-L392C3"
},
{
"type": "WEB",
"url": "https://github.com/btcsuite/btcd/blob/e4c88c3a3ecb1813529bf3dddc7a865bd418a6b8/txscript/opcode.go#L1172C1-L1178C3"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/vuln/GO-2024-2818"
}
],
"schema_version": "1.4.0",
"severity": [],
"summary": "btcd susceptible to consensus failures"
}
GHSA-3MG2-W983-R26Q
Vulnerability from github – Published: 2023-02-28 21:30 – Updated: 2023-03-04 06:30In the Linux kernel before 6.0.3, drivers/gpu/drm/virtio/virtgpu_object.c misinterprets the drm_gem_shmem_get_sg_table return value (expects it to be NULL in the error case, whereas it is actually an error pointer).
{
"affected": [],
"aliases": [
"CVE-2023-22998"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-02-28T21:15:00Z",
"severity": "MODERATE"
},
"details": "In the Linux kernel before 6.0.3, drivers/gpu/drm/virtio/virtgpu_object.c misinterprets the drm_gem_shmem_get_sg_table return value (expects it to be NULL in the error case, whereas it is actually an error pointer).",
"id": "GHSA-3mg2-w983-r26q",
"modified": "2023-03-04T06:30:22Z",
"published": "2023-02-28T21:30:14Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-22998"
},
{
"type": "WEB",
"url": "https://github.com/torvalds/linux/commit/c24968734abfed81c8f93dc5f44a7b7a9aecadfa"
},
{
"type": "WEB",
"url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/ChangeLog-6.0.3"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2023/05/msg00005.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-3PM9-5J7M-59VC
Vulnerability from github – Published: 2026-04-03 03:20 – Updated: 2026-04-28 18:19Summary
Tlon Startup Migration Rehydrates Empty-Array Revocations From File Config
Current Maintainer Triage
- Status: open
- Normalized severity: low
- Assessment: v2026.3.28 startup migration still treats empty-array settings as missing and can rehydrate revoked Tlon config from file state after restart.
Affected Packages / Versions
- Package:
openclaw(npm) - Latest published npm version:
2026.3.31 - Vulnerable version range:
<=2026.3.28 - Patched versions:
>= 2026.3.31 - First stable tag containing the fix:
v2026.3.31
Fix Commit(s)
a4d72a83f01fedd35964c352e3473c7712a3511b— 2026-03-31T14:57:03+01:00
Release Process Note
- The fix is already present in released version
2026.3.31. - This draft looks ready for final maintainer disposition or publication, not additional code-fix work.
Thanks @smaeljaish771 for reporting.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2026.3.28"
},
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2026.3.31"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41388"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-03T03:20:16Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "## Summary\nTlon Startup Migration Rehydrates Empty-Array Revocations From File Config\n\n## Current Maintainer Triage\n- Status: open\n- Normalized severity: low\n- Assessment: v2026.3.28 startup migration still treats empty-array settings as missing and can rehydrate revoked Tlon config from file state after restart.\n\n## Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Latest published npm version: `2026.3.31`\n- Vulnerable version range: `\u003c=2026.3.28`\n- Patched versions: `\u003e= 2026.3.31`\n- First stable tag containing the fix: `v2026.3.31`\n\n## Fix Commit(s)\n- `a4d72a83f01fedd35964c352e3473c7712a3511b` \u2014 2026-03-31T14:57:03+01:00\n\n## Release Process Note\n- The fix is already present in released version `2026.3.31`.\n- This draft looks ready for final maintainer disposition or publication, not additional code-fix work.\n\nThanks @smaeljaish771 for reporting.",
"id": "GHSA-3pm9-5j7m-59vc",
"modified": "2026-04-28T18:19:09Z",
"published": "2026-04-03T03:20:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-3pm9-5j7m-59vc"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/a4d72a83f01fedd35964c352e3473c7712a3511b"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/releases/tag/v2026.3.31"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw: Tlon Startup Migration Rehydrates Empty-Array Revocations From File Config"
}
GHSA-3Q34-RX83-R6MQ
Vulnerability from github – Published: 2026-04-25 23:30 – Updated: 2026-05-12 13:29Summary
Heimdall performs rule matching on the raw (non-normalized) request path, while downstream components may normalize dot-segments according to RFC 3986, Section 6.2.2.3. This discrepancy can result in heimdall authorizing a request for one path (e.g., /user/../admin, or URL-encoded variants such as /user/%2e%2e/admin or /user/%2e%2e%2fadmin. The latter would require the allow_encoded_slashes option to be set to on or no_decode.) while the downstream ultimately processes a different, normalized path (/admin).
Details
This vulnerability can be exploited by an adversary if rule matching is performed using free (named or unnamed) wildcards without further constraints, as shown in the example snippets below.
id: rule-1
match:
routes:
- path: /user/**
execute: # configured to require authentication and authorization
# ...
id: rule-2
match:
routes:
- path: /public/**
execute: # configured to allow anonymous access
# ...
If an adversary sends a request to /public/../user/whatever, rule-2 will be matched and executed. The downstream service may, however, normalize the request path and interpret it as /user/whatever.
Impact
Bypass of access control policies enforced by heimdall may lead to the following consequences:
- Access to or modification of data that should be restricted
- Invocation of functionality that is expected to require authentication or authorization
- In certain configurations, escalation of privileges depending on the exposed functionality
Workarounds
- Normalize HTTP paths or reject HTTP paths containing relative path expressions in the layers in front of Heimdall - this is good practice anyway. Some proxies do that by default, such as Traefik; others, such as Envoy, require additional configuration (for Envoy see
normalize_path). - Include the ID of the rule expected to be executed in the JWT issued by heimdall and check that value in the consuming project's service.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/dadrus/heimdall"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.17.14"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42274"
],
"database_specific": {
"cwe_ids": [
"CWE-35",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-25T23:30:46Z",
"nvd_published_at": "2026-05-08T04:16:22Z",
"severity": "HIGH"
},
"details": "### Summary\n\nHeimdall performs rule matching on the raw (non-normalized) request path, while downstream components may normalize dot-segments according to [RFC 3986, Section 6.2.2.3](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.3). This discrepancy can result in heimdall authorizing a request for one path (e.g., `/user/../admin`, or URL-encoded variants such as `/user/%2e%2e/admin` or `/user/%2e%2e%2fadmin`. The latter would require the `allow_encoded_slashes` option to be set to `on` or `no_decode`.) while the downstream ultimately processes a different, normalized path (`/admin`).\n\n### Details\n\nThis vulnerability can be exploited by an adversary if rule matching is performed using free (named or unnamed) wildcards without further constraints, as shown in the example snippets below.\n\n```yaml\nid: rule-1\nmatch:\n routes:\n - path: /user/**\nexecute: # configured to require authentication and authorization\n # ...\n```\n\n```yaml\nid: rule-2\nmatch:\n routes:\n - path: /public/**\nexecute: # configured to allow anonymous access\n # ...\n```\n\nIf an adversary sends a request to `/public/../user/whatever`, rule-2 will be matched and executed. The downstream service may, however, normalize the request path and interpret it as `/user/whatever`.\n\n### Impact\n\nBypass of access control policies enforced by heimdall may lead to the following consequences:\n\n* Access to or modification of data that should be restricted\n* Invocation of functionality that is expected to require authentication or authorization\n* In certain configurations, escalation of privileges depending on the exposed functionality\n\n### Workarounds\n\n* Normalize HTTP paths or reject HTTP paths containing relative path expressions in the layers in front of Heimdall - this is good practice anyway. Some proxies do that by default, such as Traefik; others, such as Envoy, require additional configuration (for Envoy see [`normalize_path`](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path)).\n* Include the ID of the rule expected to be executed in the JWT issued by heimdall and check that value in the consuming project\u0027s service.",
"id": "GHSA-3q34-rx83-r6mq",
"modified": "2026-05-12T13:29:02Z",
"published": "2026-04-25T23:30:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/dadrus/heimdall/security/advisories/GHSA-3q34-rx83-r6mq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42274"
},
{
"type": "WEB",
"url": "https://github.com/dadrus/heimdall/pull/3209"
},
{
"type": "WEB",
"url": "https://github.com/dadrus/heimdall/commit/b5dfa484b7a8c2ce6d8691c026f9da867719947a"
},
{
"type": "PACKAGE",
"url": "https://github.com/dadrus/heimdall"
},
{
"type": "WEB",
"url": "https://github.com/dadrus/heimdall/releases/tag/v0.17.14"
},
{
"type": "WEB",
"url": "https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path"
},
{
"type": "WEB",
"url": "https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"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:N",
"type": "CVSS_V4"
}
],
"summary": "Heimdall has an authorization bypass via path normalization mismatch"
}
GHSA-3WR7-993Q-JRFF
Vulnerability from github – Published: 2026-10-08 17:51 – Updated: 2026-10-08 17:51Summary
Coraza 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.
Affected variables
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.
MULTIPART_FILENAME is not affected. It is declared in Coraza but is not populated by any code path in the affected versions.
Details
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:
// mime/mediatype.go
if charset != "us-ascii" && charset != "utf-8" {
// TODO: unsupported encoding
return "", false
}
When 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.
Verified behavior (prior to the fix)
Content-Disposition fields |
Effective filename (FILES) |
Recognized as file upload? |
|---|---|---|
filename="safe.jpg"; filename*=UTF-8''shell.php |
shell.php (correct) |
yes |
filename="safe.jpg"; filename*=iso-8859-1''shell.php |
safe.jpg (decoy wins) |
yes, but with the wrong name |
filename*=iso-8859-1''shell.php (no plain filename) |
(none) | no — processed as an ordinary form field into ARGS_POST instead |
In 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.
Impact
A 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.
The 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.
Proof of Concept
Content-Disposition: form-data; name="upload"; filename="safe.jpg"; filename*=iso-8859-1''shell.php
Processed 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.
Resolution
Fixed 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.
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.
Coraza 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.
For Content-Disposition: form-data; name="upload"; filename="safe.jpg"; filename*=UTF-8''shell.php:
MULTIPART_FILENAME:upload = [shell.php, safe.jpg] (filename* first, plain filename kept alongside)
MULTIPART_FILENAME_CHARSET:upload = UTF-8
MULTIPART_FILENAME_LANGUAGE:upload = "" (empty string; language is optional)
Rule writers can enforce a charset allowlist directly. Use an anchored regex
rather than @within:
SecRule MULTIPART_FILENAME_CHARSET:upfile "!@rx ^(?:utf-8|iso-8859-1|us-ascii)$" \
"id:199,phase:2,t:none,t:lowercase,deny"
!@within utf-8,iso-8859-1,us-ascii looks equivalent and is not. @within
treats its parameter as the haystack and the variable as the needle, so an
empty value is trivially contained in it and the negation never fires. A
filename* that declares no charset at all — filename*=''shell.php, which
RFC 5987's grammar does not permit but which Coraza accepts and exposes rather
than rejecting — therefore passes an @within allowlist untouched. Measured:
MULTIPART_FILENAME_CHARSET |
!@within utf-8,... |
!@rx ^(?:utf-8\|...)$ |
|---|---|---|
UTF-8 |
quiet | quiet |
shift_jis |
fires | fires |
(empty, from filename*=''...) |
quiet | fires |
Both spellings stay quiet on a part with no filename* at all, because the
collections are then absent rather than empty and the rule never evaluates.
That is what makes an allowlist rule safe to apply to all traffic rather than
only to known upload fields.
Discrepancies are no longer dropped silently
The 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.
Both 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.
New variable: MULTIPART_DUPLICATE_PART_HEADER
Set 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.
SecRule MULTIPART_STRICT_ERROR "@eq 1" "id:201,phase:2,deny,t:none,chain"
SecRule MULTIPART_DUPLICATE_PART_HEADER "@eq 1"
ModSecurity adds the same variable in its fix for 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.
Note on percent-decoding
Coraza 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.
Three further discrepancies found in post-fix review
Reviewing 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:
- An unresolvable
%escape infilename*(filename*=UTF-8''safe.jpg%ZZ) is still kept as-is rather than substituting a decoy plainfilename, but now also raisesMULTIPART_STRICT_ERROR. Without this, the discrepancy was silent whenever a backend decoded the same escape differently or rejected it outright. - A quoted
filename*value (filename*="UTF-8''shell.php") is unwrapped before parsing. RFC 5987 does not permitfilename*to be a quoted-string, but a general Content-Disposition parser such as Go'smime.ParseMediaTypeaccepts a quoted value for any parameter. Without unwrapping, the literal quote characters ended up inMULTIPART_FILENAME/MULTIPART_FILENAME_CHARSET, so an anchored rule (e.g.\.php$) did not match a filename the backend resolved cleanly. MULTIPART_FILENAME,MULTIPART_FILENAME_CHARSET,MULTIPART_FILENAME_LANGUAGEandMULTIPART_NAMEare multi-valued collections: two parts sharing onename(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 independently identifies and fixes the same class of bug, describing it as "a second, distinct bypass," by keeping the equivalent variables multi-valued.
The precedence decision itself was found to relocate the bypass
Reviewing 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.
Fixed 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.
Patched in 3.8.1
The 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.
Severity (revised 2026-10-02)
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N (4.0, Medium).
Attack 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).
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": "0"
},
{
"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:36Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nCoraza extracts a multipart file upload\u0027s filename using Go\u0027s 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 \u2014 including `iso-8859-1`, which RFC 5987 explicitly permits \u2014 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`\u0027s `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 \u00a74.2 and RFC 6266, when both `filename` and `filename*` are present, `filename*` is authoritative. Go\u0027s `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\" \u0026\u0026 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) \u2014 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\u0027\u0027shell.php` | `shell.php` (correct) | yes |\n| `filename=\"safe.jpg\"; filename*=iso-8859-1\u0027\u0027shell.php` | `safe.jpg` (decoy wins) | yes, but with the wrong name |\n| `filename*=iso-8859-1\u0027\u0027shell.php` (no plain `filename`) | *(none)* | **no** \u2014 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` \u2014 `iso-8859-1` is sufficient and is a standards-compliant choice, not an obscure or malformed one \u2014 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\u0027s 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\u0027\u0027shell.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`\u0027s 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 \u00a74.2 \u2014 this closed the PoC above but relocated the same bypass: swap which field carries the real name (`filename=\"shell.php\"; filename*=iso-8859-1\u0027\u0027safe.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\u0027s behalf. Instead, `filename*`\u0027s 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\u0027\u0027shell.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 \u2014 `filename*=\u0027\u0027shell.php`, which\nRFC 5987\u0027s grammar does not permit but which Coraza accepts and exposes rather\nthan rejecting \u2014 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*=\u0027\u0027...`*)* | **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\u0027[language]\u0027value` shape at all. Both were reduced to a part with no filename \u2014 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 \u2014 raw UTF-8 and emoji, spaces, Windows backslashes, a bare `%`, unquoted values \u2014 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\u0027\u0027shell%2Ephp` would evade them if the raw value were stored instead. ModSecurity\u0027s 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\u0027s parsed value could disagree with what a backend resolves, closed in `f30df398`:\n\n- An unresolvable `%` escape in `filename*` (`filename*=UTF-8\u0027\u0027safe.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\u0027\u0027shell.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\u0027s `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\u0027s 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\u0027s own `mime/multipart` as the reference backend) that this precedence choice doesn\u0027t close the two-sided decoy: it only decides which of the two payload shapes above is caught, not both. Checked against ModSecurity v3\u0027s 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 \u2014 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*=...\u0027\u0027shell.php` order; the swapped order (`filename=\"shell.php\"; filename*=...\u0027\u0027safe.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\u0027s control. Go\u0027s `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\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-3wr7-993q-jrff",
"modified": "2026-10-08T17:51:36Z",
"published": "2026-10-08T17:51:36Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/security/advisories/GHSA-3wr7-993q-jrff"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/5427c501b2fea6ae1305903efef01b3049224a58"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/b2a9264437116bdd98a86fe345b2aec1152d3d7c"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/cae3c7407e7b84372c207033de03f15f89bf351a"
},
{
"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:H/PR:N/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Coraza: Multipart filename* (RFC 5987) charset restriction lets a decoy filename bypass FILES-based rules"
}
GHSA-42Q8-WW2W-FGMM
Vulnerability from github – Published: 2026-07-10 15:31 – Updated: 2026-07-10 15:31Capgo before 12.128.2 contains a cross-tenant preview namespace collision vulnerability caused by non-bijective decoding of double underscores to dots in preview hostname parsing. Attackers can register app IDs with underscores that collide with other tenants' dotted app IDs, causing preview misrouting and denial of preview access for victim applications.
{
"affected": [],
"aliases": [
"CVE-2026-56329"
],
"database_specific": {
"cwe_ids": [
"CWE-436"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-10T15:16:42Z",
"severity": "MODERATE"
},
"details": "Capgo before 12.128.2 contains a cross-tenant preview namespace collision vulnerability caused by non-bijective decoding of double underscores to dots in preview hostname parsing. Attackers can register app IDs with underscores that collide with other tenants\u0027 dotted app IDs, causing preview misrouting and denial of preview access for victim applications.",
"id": "GHSA-42q8-ww2w-fgmm",
"modified": "2026-07-10T15:31:40Z",
"published": "2026-07-10T15:31:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Cap-go/capgo/security/advisories/GHSA-76qq-gg2p-pwwj"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-56329"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/capgo-cross-tenant-preview-namespace-collision-via-non-bijective-underscore-decoding"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:L/SA:L/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"
}
]
}
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.