{"id":"GHSA-3rxj-6jhw-qfv7","kind":"vulnerability","aliases":["GHSA-3rxj-6jhw-qfv7"],"related":[],"title":"mcp-server-fetch: IPv6 site-local fec0::/10 classified as public (SSRF guard classifier gap, CWE-918)","description":"### Summary\n\n_is_public_address in mcp-server-fetch omits fec0::/10 (deprecated IPv6 site-local) and ::ffff:0:0/96 from its non-public list and falls back to ip.is_global, which returns True for fec0::1. A fetch to http://[fec0::1]/ passes the SSRF guard. Low real-world reachability on default hosts; this is a classifier-correctness gap, not a proven metadata exploit.\n\n### Details\n\n**Server:** fetch (`mcp-server-fetch`, PyPI)\r\n**Affected versions:** 2026.11.4 and 2026.10.24 (reproduced on both)\r\n**CWE:** CWE-918 (SSRF — guard classifier gap)\r\n\r\n### Summary\r\n`_is_public_address` (`src/mcp_server_fetch/server.py`) unwraps IPv4-mapped and well-known NAT64 forms and lists many non-public ranges, but omits `fec0::/10` (deprecated IPv6 site-local, RFC 3879) and `::ffff:0:0/96` (IPv4-translated). It then falls back to `return ip.is_global and not ip.is_multicast`, and Python's `ipaddress` reports `fec0::1` as `is_global == True`. So a `fetch` to `http://[fec0::1]/…` passes the guard that is meant to refuse non-public destinations.\r\n\r\n### Scope / honest reachability note\r\nThis is a guard-classifier correctness issue — the guard's own threat model includes non-public destinations — **not** a proven metadata-service exploit on default hosts. On a stock modern kernel `fec0::1` usually has no route and the connection times out; the translated loopback form `[::ffff:0:127.0.0.1]` was correctly normalized and blocked. Every other loopback / RFC1918 / link-local / redirect form in a 36-case matrix (2-hop, protocol-relative, 307/308, userinfo, trailing-dot and uppercase-host redirects, `0.0.0.0`, `[::]`, full-octal IPv4, mapped / NAT64) was correctly refused.\r\n\r\n### Impact\r\nThe SSRF allowlist classifies a non-public (site-local) IPv6 destination as public. Severity is Low given the limited default-host reachability; it is a defense-in-depth / correctness gap in a guard whose purpose is to deny non-public destinations.\r\n\r\n### Suggested fix\r\nExplicitly block `fec0::/10` and `::ffff:0:0/96`; deny-by-default on `not ip.is_global` combined with reserved / site-local checks rather than relying on `is_global` alone.\r\n\r\n### Disclosure\r\nReported privately per SECURITY.md. Analysis and drafting were AI-assisted and reviewed by a human researcher at ShieldPi. Credit, if accepted: ShieldPi (support@shieldpi.io).\n\n### Proof of concept\n\n1. Classifier level (2026.11.4 and 2026.10.24):\r\n   python -c \"from mcp_server_fetch.server import _is_public_address as f; print(f('fec0::1'))\"\r\n   -> prints True (should be False).\r\n2. End-to-end: call fetch {\"url\":\"http://[fec0::1]/admin\"} -> the host passes the public-address guard. On a stock host with no route to fec0::/10 the TCP connection then times out; on a host that routes fec0::/10 the request proceeds to the non-public destination.\r\n3. Negative controls (correctly refused): http://[::ffff:0:127.0.0.1]/ , http://0.0.0.0/ , http://[::]/ , and 2-hop / 307 / 308 redirects into loopback or RFC1918.\r\nReproduced on mcp-server-fetch 2026.11.4 and 2026.10.24.\n\n### Impact\n\nThe SSRF guard classifies a non-public IPv6 site-local address (fec0::1) as a public destination, so a fetch to it bypasses the guard. Real-world reachability is low on default hosts (no route to fec0::/10); impact is a classifier-correctness / defense-in-depth gap, not a proven metadata-service read. Affects any deployment relying on mcp-server-fetch's address guard to refuse non-public destinations.","summary":"The fetch tool's address check treats the deprecated IPv6 site-local range fec0::/10 as public, so a request to a site-local address passes the guard. An attacker who can drive the fetch tool could reach a non-public IPv6 destination, though the advisory says default hosts usually have no route there.","severity":"low","cvss":3.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"mcp-server-fetch","fixed":["2026.11.11"],"affected":">= 2026.10.10, < 2026.11.11","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["ai-tooling","cwe-918","mcp","mcp-server-fetch","python"],"not_affected_if":["default-config","no-ipv6-route"],"how_to_check":"- Run `pip show mcp-server-fetch` and check whether the version is `>= 2026.10.10` and `< 2026.11.11`.\n- Run `python -c \"from mcp_server_fetch.server import _is_public_address as f; print(f('fec0::1'))\"` and a `True` result means the guard is affected.","fix":"- `mcp-server-fetch` `2026.11.11`","status":"open","published_at":"2026-11-11T07:25:00+00:00","first_seen_at":"2026-11-11T07:28:35+00:00","triaged_at":"2026-11-11T07:30:02+00:00","updated_at":"2026-11-11T07:30:02+00:00","category":"ai-tooling","confidence":0.8,"triage":{"at":"2026-11-11T07:30:02Z","model":"claude-haiku-5-5","output":{"fix":"- `mcp-server-fetch` `2026.11.11`","reach":"network","title":"mcp-server-fetch: IPv6 site-local addresses slip past the SSRF address guard","impact":"low","summary":"The fetch tool's address check treats the deprecated IPv6 site-local range fec0::/10 as public, so a request to a site-local address passes the guard. An attacker who can drive the fetch tool could reach a non-public IPv6 destination, though the advisory says default hosts usually have no route there.","audience":"developers","category":"ai-tooling","nicknames":[],"who_cares":["mcp","python","mcp-server-fetch"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"fetch tool reaches a non-public IPv6 site-local address","analyst_note":"","how_to_check":"- Run `pip show mcp-server-fetch` and check whether the version is `>= 2026.10.10` and `< 2026.11.11`.\n- Run `python -c \"from mcp_server_fetch.server import _is_public_address as f; print(f('fec0::1'))\"` and a `True` result means the guard is affected.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-config","no-ipv6-route"],"who_is_affected":"Teams that run mcp-server-fetch, the PyPI MCP server that fetches URLs for an AI agent, and rely on its address guard to refuse non-public destinations. Exposure depends on whether the host has a route to fec0::/10; the advisory says it usually does not on a stock modern kernel. Teams not running this package can skip it.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"no","severity_if_unknown":"low"},"cost_usd":0.000896,"input_tokens":5968,"output_tokens":599,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-918':19A,38B,187C '/10':10A,60B,112C,217C,425C,533C,546C,625C '/admin':515C '0':120C,227C,327C,428C,562C '0.0.0.0':363C,564C '0/96':121C,228C,429C '1':139C,144C,250C,260C,312C,480C,499C,514C,599C '127.0.0.1':328C,563C '2':348C,505C,566C '2026.10.24':181C,485C,583C '2026.11.4':179C,483C,581C '3':556C '307':568C '307/308':353C '308':569C '36':345C '3879':224C '3rxj':22A,27A '6jhw':23A,28A 'accept':474C 'address':49B,71B,104C,195C,493C,522C,597C,654C 'advisori':93B 'affect':177C,644C 'ai':35B,462C 'ai-assist':461C 'ai-tool':34B 'allowlist':376C 'alon':451C 'analysi':457C 'assist':463C 'attack':76B 'back':130C,235C 'block':333C,423C 'bypass':609C 'c':487C 'call':510C 'case':346C 'check':50B,444C 'classifi':11A,16A,161C,190C,282C,377C,481C,588C,630C 'classifier-correct':160C,629C 'combin':438C 'concept':479C 'connect':319C,536C 'control':558C 'correct':162C,283C,330C,371C,406C,559C,631C 'could':83B 'credit':472C 'cwe':18A,37B,185C,186C 'default':95B,155C,304C,396C,433C,619C 'default-host':395C 'defens':403C,633C 'defense-in-depth':402C,632C 'deni':415C,431C 'deny-by-default':430C 'deploy':646C 'deprec':53B,113C,218C 'depth':405C,635C 'destin':90B,272C,295C,386C,419C,555C,603C,661C 'detail':169C 'disclosur':452C 'dot':357C 'draft':459C 'drive':79B 'end':507C,509C 'end-to-end':506C 'everi':334C 'explicit':422C 'exploit':168C,302C 'f':495C,497C 'fall':129C,234C 'fals':504C 'fec0':9A,59B,111C,138C,143C,216C,249C,259C,311C,424C,498C,513C,532C,545C,598C,624C 'fetch':4A,33A,43B,46B,81B,109C,141C,171C,175C,257C,511C,580C,606C,652C 'ffff':119C,226C,326C,427C,561C 'fix':421C 'form':206C,325C,342C 'full':365C 'full-oct':364C 'gap':17A,163C,191C,407C,636C 'ghsa':21A,26A 'ghsa-3rxj-6jhw-qfv7':20A,25A 'given':392C 'global':133C,239C,253C,437C,450C 'guard':15A,74B,148C,189C,263C,281C,286C,410C,523C,587C,611C,655C 'guard-classifi':280C 'honest':274C 'hop':349C,567C 'host':96B,156C,305C,361C,397C,517C,527C,542C,620C 'human':468C 'impact':373C,584C,626C 'import':490C 'includ':291C 'ip.is':132C,238C,242C,436C 'ipaddress':247C 'ipv4':199C,230C,367C 'ipv4-mapped':198C 'ipv4-translated':229C 'ipv6':5A,54B,89B,114C,219C,385C,593C 'issu':284C 'kernel':310C 'known':204C 'level':482C 'limit':394C 'link':339C 'link-loc':338C 'list':127C,208C 'local':8A,57B,70B,117C,222C,340C,384C,443C,596C 'loopback':324C,336C,572C 'low':149C,391C,617C 'mani':209C 'map':200C,368C 'matrix':347C 'mcp':2A,31A,39B,41B,107C,173C,578C,650C 'mcp-server-fetch':1A,30A,40B,106C,172C,577C,649C 'mcp_server_fetch.server':489C 'meant':266C 'metadata':167C,300C,641C 'metadata-servic':299C,640C 'model':290C 'modern':309C 'multicast':243C 'nat64':205C,369C 'negat':557C 'non':87B,125C,211C,270C,293C,380C,417C,553C,591C,659C 'non-publ':86B,124C,210C,269C,292C,379C,416C,552C,590C,658C 'normal':331C 'note':276C 'octal':366C 'omit':110C,215C 'pass':72B,145C,261C,518C 'per':455C 'print':496C,500C 'privat':454C 'proceed':549C 'proof':477C 'protocol':351C 'protocol-rel':350C 'proven':166C,298C,639C 'public':13A,62B,88B,103C,126C,194C,212C,271C,294C,381C,388C,418C,492C,521C,554C,592C,602C,660C 'public-address':520C 'purpos':412C 'pypi':176C 'python':44B,245C,486C 'qfv7':24A,29A 'rang':58B,213C 'rather':445C 'reach':84B 'reachabl':153C,275C,398C,615C 'read':643C 'real':151C,613C 'real-world':150C,612C 'redirect':341C,362C,570C 'refus':268C,372C,560C,657C 'relat':352C 'reli':447C,647C 'report':248C,453C 'reproduc':182C,575C 'request':65B,548C 'research':469C 'reserv':440C 'return':135C,237C 'review':465C 'rfc':223C 'rfc1918':337C,574C 'rout':100B,316C,530C,544C,622C 'say':94B 'scope':273C 'security.md':456C 'server':3A,32A,42B,108C,170C,174C,579C,651C 'servic':301C,642C 'sever':389C 'shieldpi':471C,475C 'site':7A,56B,69B,116C,221C,383C,442C,595C 'site-loc':6A,55B,68B,115C,220C,382C,441C,594C 'src/mcp_server_fetch/server.py':196C 'ssrf':14A,147C,188C,375C,586C 'stock':308C,526C 'suggest':420C 'summari':101C,192C 'support@shieldpi.io':476C 'tcp':535C 'though':91B 'threat':289C 'time':320C,538C 'tool':36B,47B,82B 'trail':356C 'trailing-dot':355C 'translat':231C,323C 'treat':51B 'true':136C,254C,501C 'unwrap':197C 'uppercas':360C 'uppercase-host':359C 'url':512C 'userinfo':354C 'usual':97B,313C 'version':178C 'well':203C 'well-known':202C 'whose':411C 'world':152C,614C","nicknames":[],"mitigations":[],"who_affected":"Teams that run mcp-server-fetch, the PyPI MCP server that fetches URLs for an AI agent, and rely on its address guard to refuse non-public destinations. Exposure depends on whether the host has a route to fec0::/10; the advisory says it usually does not on a stock modern kernel. Teams not running this package can skip it.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["mcpserverfetch"],"ptokens":["fetch","mcp","server"],"labels":["aitooling","cwe918","low","mcp","mcpserverfetch","pypi","python"],"campaign_type":null,"search_a":"'-918':19A '/10':10A '3rxj':22A,27A '6jhw':23A,28A 'classifi':11A,16A 'cwe':18A 'fec0':9A 'fetch':4A,33A 'gap':17A 'ghsa':21A,26A 'ghsa-3rxj-6jhw-qfv7':20A,25A 'guard':15A 'ipv6':5A 'local':8A 'mcp':2A,31A 'mcp-server-fetch':1A,30A 'public':13A 'qfv7':24A,29A 'server':3A,32A 'site':7A 'site-loc':6A 'ssrf':14A","search_b":"'-918':38B '/10':60B 'address':49B,71B 'advisori':93B 'ai':35B 'ai-tool':34B 'attack':76B 'check':50B 'could':83B 'cwe':37B 'default':95B 'deprec':53B 'destin':90B 'drive':79B 'fec0':59B 'fetch':43B,46B,81B 'guard':74B 'host':96B 'ipv6':54B,89B 'local':57B,70B 'mcp':39B,41B 'mcp-server-fetch':40B 'non':87B 'non-publ':86B 'pass':72B 'public':62B,88B 'python':44B 'rang':58B 'reach':84B 'request':65B 'rout':100B 'say':94B 'server':42B 'site':56B,69B 'site-loc':55B,68B 'though':91B 'tool':36B,47B,82B 'treat':51B 'usual':97B","search_c":"'-918':187C '/10':112C,217C,425C,533C,546C,625C '/admin':515C '0':120C,227C,327C,428C,562C '0.0.0.0':363C,564C '0/96':121C,228C,429C '1':139C,144C,250C,260C,312C,480C,499C,514C,599C '127.0.0.1':328C,563C '2':348C,505C,566C '2026.10.24':181C,485C,583C '2026.11.4':179C,483C,581C '3':556C '307':568C '307/308':353C '308':569C '36':345C '3879':224C 'accept':474C 'address':104C,195C,493C,522C,597C,654C 'affect':177C,644C 'ai':462C 'ai-assist':461C 'allowlist':376C 'alon':451C 'analysi':457C 'assist':463C 'back':130C,235C 'block':333C,423C 'bypass':609C 'c':487C 'call':510C 'case':346C 'check':444C 'classifi':161C,190C,282C,377C,481C,588C,630C 'classifier-correct':160C,629C 'combin':438C 'concept':479C 'connect':319C,536C 'control':558C 'correct':162C,283C,330C,371C,406C,559C,631C 'credit':472C 'cwe':185C,186C 'default':155C,304C,396C,433C,619C 'default-host':395C 'defens':403C,633C 'defense-in-depth':402C,632C 'deni':415C,431C 'deny-by-default':430C 'deploy':646C 'deprec':113C,218C 'depth':405C,635C 'destin':272C,295C,386C,419C,555C,603C,661C 'detail':169C 'disclosur':452C 'dot':357C 'draft':459C 'end':507C,509C 'end-to-end':506C 'everi':334C 'explicit':422C 'exploit':168C,302C 'f':495C,497C 'fall':129C,234C 'fals':504C 'fec0':111C,138C,143C,216C,249C,259C,311C,424C,498C,513C,532C,545C,598C,624C 'fetch':109C,141C,171C,175C,257C,511C,580C,606C,652C 'ffff':119C,226C,326C,427C,561C 'fix':421C 'form':206C,325C,342C 'full':365C 'full-oct':364C 'gap':163C,191C,407C,636C 'given':392C 'global':133C,239C,253C,437C,450C 'guard':148C,189C,263C,281C,286C,410C,523C,587C,611C,655C 'guard-classifi':280C 'honest':274C 'hop':349C,567C 'host':156C,305C,361C,397C,517C,527C,542C,620C 'human':468C 'impact':373C,584C,626C 'import':490C 'includ':291C 'ip.is':132C,238C,242C,436C 'ipaddress':247C 'ipv4':199C,230C,367C 'ipv4-mapped':198C 'ipv4-translated':229C 'ipv6':114C,219C,385C,593C 'issu':284C 'kernel':310C 'known':204C 'level':482C 'limit':394C 'link':339C 'link-loc':338C 'list':127C,208C 'local':117C,222C,340C,384C,443C,596C 'loopback':324C,336C,572C 'low':149C,391C,617C 'mani':209C 'map':200C,368C 'matrix':347C 'mcp':107C,173C,578C,650C 'mcp-server-fetch':106C,172C,577C,649C 'mcp_server_fetch.server':489C 'meant':266C 'metadata':167C,300C,641C 'metadata-servic':299C,640C 'model':290C 'modern':309C 'multicast':243C 'nat64':205C,369C 'negat':557C 'non':125C,211C,270C,293C,380C,417C,553C,591C,659C 'non-publ':124C,210C,269C,292C,379C,416C,552C,590C,658C 'normal':331C 'note':276C 'octal':366C 'omit':110C,215C 'pass':145C,261C,518C 'per':455C 'print':496C,500C 'privat':454C 'proceed':549C 'proof':477C 'protocol':351C 'protocol-rel':350C 'proven':166C,298C,639C 'public':103C,126C,194C,212C,271C,294C,381C,388C,418C,492C,521C,554C,592C,602C,660C 'public-address':520C 'purpos':412C 'pypi':176C 'python':245C,486C 'rang':213C 'rather':445C 'reachabl':153C,275C,398C,615C 'read':643C 'real':151C,613C 'real-world':150C,612C 'redirect':341C,362C,570C 'refus':268C,372C,560C,657C 'relat':352C 'reli':447C,647C 'report':248C,453C 'reproduc':182C,575C 'request':548C 'research':469C 'reserv':440C 'return':135C,237C 'review':465C 'rfc':223C 'rfc1918':337C,574C 'rout':316C,530C,544C,622C 'scope':273C 'security.md':456C 'server':108C,170C,174C,579C,651C 'servic':301C,642C 'sever':389C 'shieldpi':471C,475C 'site':116C,221C,383C,442C,595C 'site-loc':115C,220C,382C,441C,594C 'src/mcp_server_fetch/server.py':196C 'ssrf':147C,188C,375C,586C 'stock':308C,526C 'suggest':420C 'summari':101C,192C 'support@shieldpi.io':476C 'tcp':535C 'threat':289C 'time':320C,538C 'trail':356C 'trailing-dot':355C 'translat':231C,323C 'true':136C,254C,501C 'unwrap':197C 'uppercas':360C 'uppercase-host':359C 'url':512C 'userinfo':354C 'usual':313C 'version':178C 'well':203C 'well-known':202C 'whose':411C 'world':152C,614C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-11T07:25:00+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-3rxj-6jhw-qfv7","url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-3rxj-6jhw-qfv7","severity":"low","published_at":"2026-11-11T07:25:00+00:00","modified_at":"2026-11-11T07:25:01+00:00","first_seen_at":"2026-11-11T07:28:35+00:00","fetched_at":"2026-11-11T07:28:35+00:00","note":null}],"documents":[{"id":123650,"url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-3rxj-6jhw-qfv7","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"}