{"id":"GHSA-66qx-2266-pmj6","kind":"vulnerability","aliases":["GHSA-66qx-2266-pmj6"],"related":[],"title":"fetch server: SSRF guard treats the well-known NAT64 prefix 64:ff9b::/96 as public, so a metadata address is reachable on NAT64/DNS64 egress","description":"### Summary\r\n\r\nIn mcp-server-fetch, `_is_public_address()` (src/fetch/src/mcp_server_fetch/server.py) decides whether a resolved address is a public destination. Its `_NON_PUBLIC_NETWORKS` list refuses the local-use NAT64 prefix `64:ff9b:1::/48` (RFC 8215) and the 6to4 prefix `2002::/16`, but it does not list the well-known NAT64 prefix `64:ff9b::/96` (RFC 6052) and does not decode the IPv4 embedded in its low 32 bits. An address `64:ff9b::<embedded-IPv4>` is therefore classified as public even when the embedded IPv4 is non-public, e.g. the cloud metadata endpoint 169.254.169.254. The `_refuse_private_destination` hook is correctly applied on every request and redirect hop; the gap is in the classifier itself.\r\n\r\n### Details\r\n\r\nAffected code: src/fetch/src/mcp_server_fetch/server.py, function `_is_public_address` and the module-level `_NON_PUBLIC_NETWORKS` tuple.\r\n\r\nThe tuple lists `64:ff9b:1::/48` (local-use NAT64) and `2002::/16` (6to4), and handles `ipv4_mapped`, but it omits the well-known NAT64 prefix `64:ff9b::/96` (RFC 6052). For an address in that prefix the function does not decode the embedded IPv4, and because the address is globally-scoped it falls through to `ip.is_global and not ip.is_multicast`, returning True.\r\n\r\nImpact (CWE-918): on a host whose egress uses NAT64/DNS64 (RFC 6146/6147) — common on IPv6-only networks, including some cloud environments — `64:ff9b::a9fe:a9fe` routes to 169.254.169.254, so a fetch the server believes is public reaches the metadata endpoint the guard is documented to refuse. Where the server is exposed to attacker-influenced URLs (its purpose), this is an SSRF-guard bypass to internal/metadata addresses on such networks. On a host without NAT64 egress the address is not routable, so, like the DNS-rebinding caveat the README already states, exploitability depends on the deployment.\r\n\r\nThis is the same class as several advisories published in the last week (IPv6-transition / embedded-IPv4 blocklist gaps), so it is a known, recurring pattern.\r\n\r\nSuggested fix: before the network checks, decode embedded-IPv4 forms and classify the embedded address (for `64:ff9b::/96` take the low 32 bits, as the code already does for `ipv4_mapped`); or simply add `64:ff9b::/96` to `_NON_PUBLIC_NETWORKS`, since a NAT64-wrapped public IPv4 is reachable directly anyway. I'm happy to open a PR with tests once this is acknowledged, following CONTRIBUTING.\r\n\r\nAI disclosure (per your CONTRIBUTING's AI-assisted workflow): this report was prepared with AI assistance (Claude Code) — a source review plus a unit-level evaluation of the installed classifier. The behaviour is confirmed by that evaluation, not assumed; I reviewed it.\r\n\r\n### Proof of concept\r\n\r\nUnit-level reproduction against the project's own classifier (no network, no third-party system). Import the real function from the installed package and classify addresses that each encode 169.254.169.254 through different IPv6 representations:\r\n\r\n    import mcp_server_fetch.server as s\r\n    s._is_public_address(\"169.254.169.254\")        # -> False  (plain link-local, correct)\r\n    s._is_public_address(\"::ffff:169.254.169.254\") # -> False  (IPv4-mapped, correct)\r\n    s._is_public_address(\"64:ff9b:1::a9fe:a9fe\")    # -> False  (local-use NAT64, correct)\r\n    s._is_public_address(\"2002:a9fe:a9fe::\")        # -> False  (6to4, correct)\r\n    s._is_public_address(\"64:ff9b::a9fe:a9fe\")      # -> True   (well-known NAT64 -> treated as PUBLIC)\r\n    s._is_public_address(\"8.8.8.8\")                 # -> True   (real public, control)\r\n\r\nThe 64:ff9b::a9fe:a9fe case embeds 169.254.169.254 in the low 32 bits of the well-known NAT64 prefix and is classified as public. No request was sent and no cloud or third-party system was touched; this evaluates the same surface the project's tests cover. On a NAT64/DNS64 network that address resolves/routes to the metadata endpoint.\r\n\r\n### Impact\r\n\r\nCWE-918 (SSRF). On a host whose egress uses NAT64/DNS64 (common on IPv6-only networks, including some cloud environments), an attacker who can influence a fetched URL can reach internal-only services and the cloud metadata endpoint 169.254.169.254 by using an address in the well-known NAT64 prefix 64:ff9b::/96 that embeds the target IPv4, because _is_public_address classifies it as public. This defeats the SSRF guard the server documents as refusing metadata/private addresses, and the response is returned to the caller. On hosts without NAT64 egress the address is not routable, so exploitability depends on the deployment, like the DNS-rebinding caveat already noted in the README.","summary":"The mcp-server-fetch SSRF guard treats addresses in the well-known NAT64 prefix 64:ff9b::/96 as public, without checking the embedded IPv4. An attacker who controls a fetched URL can reach internal or cloud metadata addresses such as 169.254.169.254 on hosts whose network uses NAT64/DNS64 egress.","severity":"low","cvss":3.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"mcp-server-fetch","fixed":["2026.10.24"],"affected":">= 2026.10.10, <= 2026.10.19","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cloud","cwe-918","mcp","package","python","self-hosted"],"not_affected_if":["default-config","no-nat64-egress"],"how_to_check":"- Check the installed version: `pip show mcp-server-fetch` and confirm it is `2026.10.10` through `2026.10.19`.\n- Check whether the host has IPv6-only or NAT64/DNS64 egress, since the bypass needs that routing.","fix":"- `mcp-server-fetch` fixed in `2026.10.24`","status":"open","published_at":"2026-10-25T00:18:52+00:00","first_seen_at":"2026-11-09T22:53:25+00:00","triaged_at":"2026-11-09T22:58:22+00:00","updated_at":"2026-11-09T22:58:22+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-09T22:58:22Z","model":"claude-haiku-5-5","output":{"fix":"- `mcp-server-fetch` fixed in `2026.10.24`","reach":"internet","title":"mcp-server-fetch: SSRF guard misses NAT64 prefix, reaching cloud metadata addresses","impact":"medium","summary":"The mcp-server-fetch SSRF guard treats addresses in the well-known NAT64 prefix 64:ff9b::/96 as public, without checking the embedded IPv4. An attacker who controls a fetched URL can reach internal or cloud metadata addresses such as 169.254.169.254 on hosts whose network uses NAT64/DNS64 egress.","audience":"businesses","category":"package","nicknames":[],"who_cares":["mcp","python","self-hosted","cloud"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"Server fetches cloud metadata endpoint and returns its contents to the caller.","analyst_note":"","how_to_check":"- Check the installed version: `pip show mcp-server-fetch` and confirm it is `2026.10.10` through `2026.10.19`.\n- Check whether the host has IPv6-only or NAT64/DNS64 egress, since the bypass needs that routing.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-config","no-nat64-egress"],"who_is_affected":"Teams running the mcp-server-fetch server where attacker-influenced URLs can be fetched, on networks with NAT64/DNS64 egress such as some IPv6-only cloud environments. On hosts without NAT64 egress the embedded address is not routable, so exploitability depends on the deployment. Teams that do not run this server can skip it.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"low"},"cost_usd":0.000945,"input_tokens":6364,"output_tokens":618,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-918':42B,301C,698C '/16':141C,245C '/48':133C,238C '/96':14A,67B,155C,262C,445C,464C '1':132C,237C,601C '169.254.169.254':91B,193C,327C,573C,583C,592C,643C '2002':140C,244C,611C '2266':29A,34A '32':168C,449C,647C '6052':157C,264C '6146/6147':310C '64':12A,65B,130C,153C,172C,235C,260C,321C,443C,462C,599C,618C,637C '66qx':28A,33A '6to4':138C,246C,615C '8.8.8.8':631C '8215':135C 'a9fe':323C,324C,602C,603C,612C,613C,620C,621C,639C,640C 'acknowledg':492C 'add':461C 'address':20A,57B,88B,107C,113C,171C,222C,267C,282C,367C,378C,441C,569C,690C 'advisori':405C 'affect':216C 'ai':495C,502C,510C 'ai-assist':501C 'alreadi':391C,454C 'anyway':479C 'appli':201C 'assist':503C,511C 'assum':535C 'attack':76B,353C 'attacker-influenc':352C 'behaviour':528C 'believ':333C 'bit':169C,450C,648C 'blocklist':417C 'bypass':364C 'case':641C 'caveat':388C 'check':71B,431C 'class':402C 'classifi':176C,213C,438C,526C,551C,568C,658C 'claud':512C 'cloud':40B,86B,190C,319C,667C 'code':217C,453C,513C 'common':311C,707C 'concept':541C 'confirm':530C 'contribut':494C,499C 'control':78B,635C 'correct':200C,589C,597C,609C,616C 'cover':684C 'cwe':41B,300C,697C 'decid':109C 'decod':161C,275C,432C 'depend':394C 'deploy':397C 'destin':117C,197C 'detail':215C 'differ':575C 'direct':478C 'disclosur':496C 'dns':386C 'dns-rebind':385C 'document':343C 'e.g':188C 'egress':25A,98B,306C,376C,704C 'emb':642C 'embed':73B,164C,182C,277C,415C,434C,440C 'embedded-ipv4':414C,433C 'encod':572C 'endpoint':192C,339C,695C 'environ':320C 'evalu':522C,533C,676C 'even':179C 'everi':203C 'exploit':393C 'expos':350C 'fall':288C 'fals':584C,593C,604C,614C 'fetch':1A,39A,53B,80B,104C,330C 'ff9b':13A,66B,131C,154C,173C,236C,261C,322C,444C,463C,600C,619C,638C 'ffff':591C 'fix':427C 'follow':493C 'form':436C 'function':219C,272C,562C 'gap':209C,418C 'ghsa':27A,32A 'ghsa-66qx':26A,31A 'global':285C,292C 'globally-scop':284C 'guard':4A,55B,341C,363C 'handl':248C 'happi':482C 'hook':198C 'hop':207C 'host':48B,93B,304C,373C,702C 'impact':299C,696C 'import':559C,578C 'incl':713C 'includ':317C 'influenc':354C 'instal':525C,565C 'intern':84B 'internal/metadata':366C 'ip.is':291C,295C 'ipv4':74B,163C,183C,249C,278C,416C,435C,457C,475C,595C 'ipv4-mapped':594C 'ipv6':314C,412C,576C,710C 'ipv6-only':313C,709C 'ipv6-transition':411C 'known':9A,62B,150C,257C,423C,625C,653C 'last':409C 'level':227C,521C,544C 'like':383C 'link':587C 'link-loc':586C 'list':122C,146C,234C 'local':126C,240C,588C,606C 'local-us':125C,239C,605C 'low':167C,448C,646C 'm':481C 'map':250C,458C,596C 'mcp':37A,43B,51B,102C 'mcp-server-fetch':36A,50B,101C 'mcp_server_fetch.server':579C 'metadata':19A,87B,191C,338C,694C 'modul':226C 'module-level':225C 'multicast':296C 'nat64':10A,63B,128C,151C,242C,258C,375C,472C,608C,626C,654C 'nat64-wrapped':471C 'nat64/dns64':24A,97B,308C,687C,706C 'network':95B,121C,230C,316C,370C,430C,468C,553C,688C,712C 'non':119C,186C,228C,466C 'non-publ':185C 'omit':253C 'open':484C 'packag':44B,566C 'parti':557C,671C 'pattern':425C 'per':497C 'plain':585C 'plus':517C 'pmj6':30A,35A 'pr':486C 'prefix':11A,64B,129C,139C,152C,259C,270C,655C 'prepar':508C 'privat':196C 'project':548C,681C 'proof':539C 'public':16A,69B,106C,116C,120C,178C,187C,221C,229C,335C,467C,474C,629C,634C,660C 'publish':406C 'purpos':357C 'python':45B 'reach':83B,336C 'reachabl':22A,477C 'readm':390C 'real':561C,633C 'rebind':387C 'recur':424C 'redirect':206C 'refus':123C,195C,345C 'report':506C 'represent':577C 'reproduct':545C 'request':204C,662C 'resolv':112C 'resolves/routes':691C 'return':297C 'review':516C,537C 'rfc':134C,156C,263C,309C 'rout':325C 'routabl':381C 's._is_public_address':582C,590C,598C,610C,617C,630C 'scope':286C 'self':47B 'self-host':46B 'sent':664C 'server':2A,38A,52B,103C,332C,348C 'sever':404C 'simpli':460C 'sinc':469C 'sourc':515C 'src/fetch/src/mcp_server_fetch/server.py':108C,218C 'ssrf':3A,54B,362C,699C 'ssrf-guard':361C 'state':392C 'suggest':426C 'summari':99C 'surfac':679C 'system':558C,672C 'take':446C 'test':488C,683C 'therefor':175C 'third':556C,670C 'third-parti':555C,669C 'touch':674C 'transit':413C 'treat':5A,56B,627C 'true':298C,622C,632C 'tupl':231C,233C 'unit':520C,543C 'unit-level':519C,542C 'url':81B,355C 'use':96B,127C,241C,307C,607C,705C 'week':410C 'well':8A,61B,149C,256C,624C,652C 'well-known':7A,60B,148C,255C,623C,651C 'whether':110C 'whose':94B,305C,703C 'without':70B,374C 'workflow':504C 'wrap':473C","nicknames":[],"mitigations":[],"who_affected":"Teams running the mcp-server-fetch server where attacker-influenced URLs can be fetched, on networks with NAT64/DNS64 egress such as some IPv6-only cloud environments. On hosts without NAT64 egress the embedded address is not routable, so exploitability depends on the deployment. Teams that do not run this server can skip it.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["mcpserverfetch"],"ptokens":["fetch","mcp","server"],"labels":["cloud","cwe918","low","mcp","package","pypi","python","selfhosted"],"campaign_type":null,"search_a":"'/96':14A '2266':29A,34A '64':12A '66qx':28A,33A 'address':20A 'egress':25A 'fetch':1A,39A 'ff9b':13A 'ghsa':27A,32A 'ghsa-66qx':26A,31A 'guard':4A 'known':9A 'mcp':37A 'mcp-server-fetch':36A 'metadata':19A 'nat64':10A 'nat64/dns64':24A 'pmj6':30A,35A 'prefix':11A 'public':16A 'reachabl':22A 'server':2A,38A 'ssrf':3A 'treat':5A 'well':8A 'well-known':7A","search_b":"'-918':42B '/96':67B '169.254.169.254':91B '64':65B 'address':57B,88B 'attack':76B 'check':71B 'cloud':40B,86B 'control':78B 'cwe':41B 'egress':98B 'embed':73B 'fetch':53B,80B 'ff9b':66B 'guard':55B 'host':48B,93B 'intern':84B 'ipv4':74B 'known':62B 'mcp':43B,51B 'mcp-server-fetch':50B 'metadata':87B 'nat64':63B 'nat64/dns64':97B 'network':95B 'packag':44B 'prefix':64B 'public':69B 'python':45B 'reach':83B 'self':47B 'self-host':46B 'server':52B 'ssrf':54B 'treat':56B 'url':81B 'use':96B 'well':61B 'well-known':60B 'whose':94B 'without':70B","search_c":"'-918':301C,698C '/16':141C,245C '/48':133C,238C '/96':155C,262C,445C,464C '1':132C,237C,601C '169.254.169.254':193C,327C,573C,583C,592C,643C '2002':140C,244C,611C '32':168C,449C,647C '6052':157C,264C '6146/6147':310C '64':130C,153C,172C,235C,260C,321C,443C,462C,599C,618C,637C '6to4':138C,246C,615C '8.8.8.8':631C '8215':135C 'a9fe':323C,324C,602C,603C,612C,613C,620C,621C,639C,640C 'acknowledg':492C 'add':461C 'address':107C,113C,171C,222C,267C,282C,367C,378C,441C,569C,690C 'advisori':405C 'affect':216C 'ai':495C,502C,510C 'ai-assist':501C 'alreadi':391C,454C 'anyway':479C 'appli':201C 'assist':503C,511C 'assum':535C 'attack':353C 'attacker-influenc':352C 'behaviour':528C 'believ':333C 'bit':169C,450C,648C 'blocklist':417C 'bypass':364C 'case':641C 'caveat':388C 'check':431C 'class':402C 'classifi':176C,213C,438C,526C,551C,568C,658C 'claud':512C 'cloud':190C,319C,667C 'code':217C,453C,513C 'common':311C,707C 'concept':541C 'confirm':530C 'contribut':494C,499C 'control':635C 'correct':200C,589C,597C,609C,616C 'cover':684C 'cwe':300C,697C 'decid':109C 'decod':161C,275C,432C 'depend':394C 'deploy':397C 'destin':117C,197C 'detail':215C 'differ':575C 'direct':478C 'disclosur':496C 'dns':386C 'dns-rebind':385C 'document':343C 'e.g':188C 'egress':306C,376C,704C 'emb':642C 'embed':164C,182C,277C,415C,434C,440C 'embedded-ipv4':414C,433C 'encod':572C 'endpoint':192C,339C,695C 'environ':320C 'evalu':522C,533C,676C 'even':179C 'everi':203C 'exploit':393C 'expos':350C 'fall':288C 'fals':584C,593C,604C,614C 'fetch':104C,330C 'ff9b':131C,154C,173C,236C,261C,322C,444C,463C,600C,619C,638C 'ffff':591C 'fix':427C 'follow':493C 'form':436C 'function':219C,272C,562C 'gap':209C,418C 'global':285C,292C 'globally-scop':284C 'guard':341C,363C 'handl':248C 'happi':482C 'hook':198C 'hop':207C 'host':304C,373C,702C 'impact':299C,696C 'import':559C,578C 'incl':713C 'includ':317C 'influenc':354C 'instal':525C,565C 'internal/metadata':366C 'ip.is':291C,295C 'ipv4':163C,183C,249C,278C,416C,435C,457C,475C,595C 'ipv4-mapped':594C 'ipv6':314C,412C,576C,710C 'ipv6-only':313C,709C 'ipv6-transition':411C 'known':150C,257C,423C,625C,653C 'last':409C 'level':227C,521C,544C 'like':383C 'link':587C 'link-loc':586C 'list':122C,146C,234C 'local':126C,240C,588C,606C 'local-us':125C,239C,605C 'low':167C,448C,646C 'm':481C 'map':250C,458C,596C 'mcp':102C 'mcp-server-fetch':101C 'mcp_server_fetch.server':579C 'metadata':191C,338C,694C 'modul':226C 'module-level':225C 'multicast':296C 'nat64':128C,151C,242C,258C,375C,472C,608C,626C,654C 'nat64-wrapped':471C 'nat64/dns64':308C,687C,706C 'network':121C,230C,316C,370C,430C,468C,553C,688C,712C 'non':119C,186C,228C,466C 'non-publ':185C 'omit':253C 'open':484C 'packag':566C 'parti':557C,671C 'pattern':425C 'per':497C 'plain':585C 'plus':517C 'pr':486C 'prefix':129C,139C,152C,259C,270C,655C 'prepar':508C 'privat':196C 'project':548C,681C 'proof':539C 'public':106C,116C,120C,178C,187C,221C,229C,335C,467C,474C,629C,634C,660C 'publish':406C 'purpos':357C 'reach':336C 'reachabl':477C 'readm':390C 'real':561C,633C 'rebind':387C 'recur':424C 'redirect':206C 'refus':123C,195C,345C 'report':506C 'represent':577C 'reproduct':545C 'request':204C,662C 'resolv':112C 'resolves/routes':691C 'return':297C 'review':516C,537C 'rfc':134C,156C,263C,309C 'rout':325C 'routabl':381C 's._is_public_address':582C,590C,598C,610C,617C,630C 'scope':286C 'sent':664C 'server':103C,332C,348C 'sever':404C 'simpli':460C 'sinc':469C 'sourc':515C 'src/fetch/src/mcp_server_fetch/server.py':108C,218C 'ssrf':362C,699C 'ssrf-guard':361C 'state':392C 'suggest':426C 'summari':99C 'surfac':679C 'system':558C,672C 'take':446C 'test':488C,683C 'therefor':175C 'third':556C,670C 'third-parti':555C,669C 'touch':674C 'transit':413C 'treat':627C 'true':298C,622C,632C 'tupl':231C,233C 'unit':520C,543C 'unit-level':519C,542C 'url':355C 'use':127C,241C,307C,607C,705C 'week':410C 'well':149C,256C,624C,652C 'well-known':148C,255C,623C,651C 'whether':110C 'whose':305C,703C 'without':374C 'workflow':504C 'wrap':473C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-10-25T00:18:52+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-66qx-2266-pmj6","url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-66qx-2266-pmj6","severity":"low","published_at":"2026-10-25T00:18:52+00:00","modified_at":"2026-10-25T00:18:52+00:00","first_seen_at":"2026-11-09T22:53:25+00:00","fetched_at":"2026-11-09T22:53:25+00:00","note":null}],"documents":[{"id":110620,"url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-66qx-2266-pmj6","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}