Every CVE Search/ Get API key

CVE-2026-42260

Open-WebSearch: SSRF in `fetchWebContent` MCP tool: bracketed IPv6 literals and non-resolving hostname check bypass `isPrivateOrLocalHostname`

8.2 HighFix available

Open-WebSearch is a multi-engine MCP server, CLI, and local daemon for agent web search and content retrieval. Prior to 2.1.7, isPublicHttpUrl / assertPublicHttpUrl in src/utils/urlSafety.ts do not recognize bracketed IPv6 literals and do not resolve DNS, which combine to allow non-blind SSRF with the response body returned to the caller. This vulnerability is fixed in 2.1.7.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
Low
Availability
None

Exploit likelihood

0.26%Up 0.2 points since May 13, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.2

Open-WebSearch: SSRF in `fetchWebContent` MCP tool: bracketed IPv6 literals and non-resolving hostname check bypass `isPrivateOrLocalHostname`

Open-WebSearch is a multi-engine MCP server, CLI, and local daemon for agent web search and content retrieval. Prior to 2.1.7, isPublicHttpUrl / assertPublicHttpUrl in src/utils/urlSafety.ts do not recognize bracketed IPv6 literals and do not resolve DNS, which combine to allow non-blind SSRF with the response body returned to the caller. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-42260
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weakness
CWE-918
Updated
2026-11-02
EUVDEUVD-2026-29478 8.2

Open-WebSearch is a multi-engine MCP server, CLI, and local daemon for agent web search and content retrieval. Prior to 2.1.7, isPublicHttpUrl / assertPublicHttpUrl in src/utils/urlSafety.ts do not recognize bracketed IPv6 literals and do not resolve DNS, which combine to allow non-blind SSRF with the response body returned to the caller. This vulnerability is fixed in 2.1.7.

Record
EUVD-2026-29478
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-v228-72c7-fx8j 8.2

open-websearch has SSRF in `fetchWebContent` MCP tool: bracketed IPv6 literals and non-resolving hostname check bypass `isPrivateOrLocalHostname`

Summary

src/utils/urlSafety.ts exposes isPublicHttpUrl / assertPublicHttpUrl, used to gate the MCP fetchWebContent tool against private-network targets. The check has two defects that together allow non-blind SSRF with the response body returned to the caller:

  1. Bracketed IPv6 literals are never recognized. Node's WHATWG URL.hostname keeps the surrounding […] for IPv6 literals. isIP("[::1]") returns 0 (not 6), so neither isPrivateIpv4 nor isPrivateIpv6 is ever called on an IPv6 literal input — including [::1] itself, and including every IPv4-mapped form such as [::ffff:7f00:1] (= 127.0.0.1 via the IPv4 stack).
  2. No DNS resolution. isPrivateOrLocalHostname only inspects the literal hostname string. It never resolves the host to an IP. Any attacker-controlled hostname whose DNS record points at 127.0.0.1 (or any RFC1918 / link-local address) passes the check unchanged, and axios then performs its own resolution and connects to the private address.

The isPrivateIpv6 implementation also has the hex bypass (it would miss ::ffff:7f00:1 even if reached) but defect (1) makes every bracketed IPv6 literal slip past before that branch is even entered.

The fetchWebContent tool returns the response body (JSON.stringify(result)) to the MCP caller, so the SSRF is non-blind.

Details

<!-- obsidian --><p><strong>Vulnerable function</strong> — <code>src/utils/urlSafety.ts:95-119</code>:</p>

<pre><code class="language-ts">export function isPrivateOrLocalHostname(hostname: string): boolean { const host = hostname.trim().toLowerCase(); if (!host) return true; if (host === 'localhost' || host.endsWith('.localhost')) return true; if (host === 'metadata.google.internal' || host === 'metadata.azure.internal') return true; const integerIp = parseIntegerIpv4Literal(host); if (integerIp &#x26;&#x26; isPrivateIpv4(integerIp)) return true; if (isPrivateOrLocalIp(host)) return true; // only runs if isIP(host) ∈ {4, 6} return false; } </code></pre>

<p><code>isPrivateOrLocalIp</code> — <code>src/utils/urlSafety.ts:84-93</code>:</p> <pre><code class="language-ts">function isPrivateOrLocalIp(ip: string): boolean { const version = isIP(ip); // returns 0 for "[::1]", "[::ffff:7f00:1]", any bracketed literal if (version === 4) return isPrivateIpv4(ip); if (version === 6) return isPrivateIpv6(ip); return false; } </code></pre> <p>Caller — <code>src/tools/setupTools.ts:252-286</code> (<code>fetchWebContent</code> tool):</p> <pre><code class="language-ts">server.tool( fetchWebToolName, // default: "fetchWebContent" "Fetch content from a public HTTP(S) URL ...", { url: z.string().url().refine( (url) => validatePublicWebUrl(url), // → isPublicHttpUrl → isPrivateOrLocalHostname "URL must be a public HTTP(S) address ..." ), /* … */ }, async ({url, maxChars}) => { const result = await runtime.services.fetchWeb.execute({ url, maxChars, /*…*/ }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); </code></pre> <p>Service — <code>src/engines/web/fetchWebContent.ts:313-375</code>: re-validates via <code>assertPublicHttpUrl</code> (same broken check), then calls <code>axios.head</code> + <code>axios.get</code> on the raw URL and returns <code>response.data</code> and <code>response.headers</code> to the caller.</p> <p>Transport — <code>src/index.ts:85-253</code>: when <code>config.enableHttpServer</code> is true (documented configuration; enabled by the Docker image), the MCP server binds on <code>0.0.0.0:${PORT}</code> (default <code>3000</code>) with CORS <code>origin: '*'</code> and <strong>no authentication</strong> on <code>/mcp</code> (Streamable HTTP) or <code>/sse</code> (legacy SSE). Anyone who can reach the port can invoke any tool.</p> <h3 data-heading="Verification of the validator (run against current &#x60;HEAD&#x60;)">Verification of the validator (run against current…

Record
GHSA-v228-72c7-fx8j
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weakness
CWE-20, CWE-693, CWE-918
Updated
2026-11-02
NVD 8.2

Open-WebSearch is a multi-engine MCP server, CLI, and local daemon for agent web search and content retrieval. Prior to 2.1.7, isPublicHttpUrl / assertPublicHttpUrl in src/utils/urlSafety.ts do not recognize bracketed IPv6 literals and do not resolve DNS, which combine to allow non-blind SSRF with the response body returned to the caller. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-42260
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weakness
CWE-918
Updated
2026-11-02
OSV 8.2

Open-WebSearch: SSRF in `fetchWebContent` MCP tool: bracketed IPv6 literals and non-resolving hostname check bypass `isPrivateOrLocalHostname`

Open-WebSearch is a multi-engine MCP server, CLI, and local daemon for agent web search and content retrieval. Prior to 2.1.7, isPublicHttpUrl / assertPublicHttpUrl in src/utils/urlSafety.ts do not recognize bracketed IPv6 literals and do not resolve DNS, which combine to allow non-blind SSRF with the response body returned to the caller. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-42260
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weakness
CWE-918
Updated
2026-11-02
OSVGHSA-v228-72c7-fx8j 8.2

open-websearch has SSRF in `fetchWebContent` MCP tool: bracketed IPv6 literals and non-resolving hostname check bypass `isPrivateOrLocalHostname`

Summary

src/utils/urlSafety.ts exposes isPublicHttpUrl / assertPublicHttpUrl, used to gate the MCP fetchWebContent tool against private-network targets. The check has two defects that together allow non-blind SSRF with the response body returned to the caller:

  1. Bracketed IPv6 literals are never recognized. Node's WHATWG URL.hostname keeps the surrounding […] for IPv6 literals. isIP("[::1]") returns 0 (not 6), so neither isPrivateIpv4 nor isPrivateIpv6 is ever called on an IPv6 literal input — including [::1] itself, and including every IPv4-mapped form such as [::ffff:7f00:1] (= 127.0.0.1 via the IPv4 stack).
  2. No DNS resolution. isPrivateOrLocalHostname only inspects the literal hostname string. It never resolves the host to an IP. Any attacker-controlled hostname whose DNS record points at 127.0.0.1 (or any RFC1918 / link-local address) passes the check unchanged, and axios then performs its own resolution and connects to the private address.

The isPrivateIpv6 implementation also has the hex bypass (it would miss ::ffff:7f00:1 even if reached) but defect (1) makes every bracketed IPv6 literal slip past before that branch is even entered.

The fetchWebContent tool returns the response body (JSON.stringify(result)) to the MCP caller, so the SSRF is non-blind.

Details

<!-- obsidian --><p><strong>Vulnerable function</strong> — <code>src/utils/urlSafety.ts:95-119</code>:</p>

<pre><code class="language-ts">export function isPrivateOrLocalHostname(hostname: string): boolean { const host = hostname.trim().toLowerCase(); if (!host) return true; if (host === 'localhost' || host.endsWith('.localhost')) return true; if (host === 'metadata.google.internal' || host === 'metadata.azure.internal') return true; const integerIp = parseIntegerIpv4Literal(host); if (integerIp &#x26;&#x26; isPrivateIpv4(integerIp)) return true; if (isPrivateOrLocalIp(host)) return true; // only runs if isIP(host) ∈ {4, 6} return false; } </code></pre>

<p><code>isPrivateOrLocalIp</code> — <code>src/utils/urlSafety.ts:84-93</code>:</p> <pre><code class="language-ts">function isPrivateOrLocalIp(ip: string): boolean { const version = isIP(ip); // returns 0 for "[::1]", "[::ffff:7f00:1]", any bracketed literal if (version === 4) return isPrivateIpv4(ip); if (version === 6) return isPrivateIpv6(ip); return false; } </code></pre> <p>Caller — <code>src/tools/setupTools.ts:252-286</code> (<code>fetchWebContent</code> tool):</p> <pre><code class="language-ts">server.tool( fetchWebToolName, // default: "fetchWebContent" "Fetch content from a public HTTP(S) URL ...", { url: z.string().url().refine( (url) => validatePublicWebUrl(url), // → isPublicHttpUrl → isPrivateOrLocalHostname "URL must be a public HTTP(S) address ..." ), /* … */ }, async ({url, maxChars}) => { const result = await runtime.services.fetchWeb.execute({ url, maxChars, /*…*/ }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); </code></pre> <p>Service — <code>src/engines/web/fetchWebContent.ts:313-375</code>: re-validates via <code>assertPublicHttpUrl</code> (same broken check), then calls <code>axios.head</code> + <code>axios.get</code> on the raw URL and returns <code>response.data</code> and <code>response.headers</code> to the caller.</p> <p>Transport — <code>src/index.ts:85-253</code>: when <code>config.enableHttpServer</code> is true (documented configuration; enabled by the Docker image), the MCP server binds on <code>0.0.0.0:${PORT}</code> (default <code>3000</code>) with CORS <code>origin: '*'</code> and <strong>no authentication</strong> on <code>/mcp</code> (Streamable HTTP) or <code>/sse</code> (legacy SSE). Anyone who can reach the port can invoke any tool.</p> <h3 data-heading="Verification of the validator (run against current &#x60;HEAD&#x60;)">Verification of the validator (run against current…

Record
GHSA-v228-72c7-fx8j
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weakness
CWE-20, CWE-693, CWE-918
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
open-websearch
npm
through 2.1.6
2.1.7
open-websearch
npm
before 2.1.7
2.1.7

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-42260 is a high vulnerability (CVSS 8.2) in Aas-ee Open-websearch, published May 5, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.26%, the 18th percentile. Fixed in open-websearch 2.1.7.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open