{"id":"GHSA-hhwq-wxfx-cj7g","kind":"vulnerability","aliases":["GHSA-hhwq-wxfx-cj7g"],"related":[],"title":"@modelcontextprotocol/server-filesystem list_directory_with_sizes follows symlinks and leaks outside-directory file metadata","description":"## Description\r\n\r\n`list_directory_with_sizes` validates only the requested directory path. It then iterates over entries and calls `fs.stat(entryPath)` on each child without validating the child path. `fs.stat()` follows symlinks, so a symlink inside an allowed directory that points to a file outside the allowed directory causes the tool to disclose the outside target's file size.\r\n\r\nDirect reads through the same symlink are correctly denied by `read_text_file`, so this is a residual metadata bypass in `list_directory_with_sizes` on the current npm release rather than a report of the already-published 2025 symlink or prefix CVEs.\r\n\r\n## Non-Duplicate Rationale\r\n\r\nKnown repository advisories GHSA-vjqx-cfc4-9h6v, GHSA-5cgr-j3jf-jw3v, GHSA-9xwc-hfwc-8w59, and GHSA-j22h-9j4x-23w5 affect `mcp-server-git`, not `@modelcontextprotocol/server-filesystem`.\r\n\r\nThe two relevant filesystem advisories are GHSA-q66q-fx2p-7w4m and GHSA-hc55-p739-j48w. They affect versions before `2025.7.1`. This report is reproduced against `@modelcontextprotocol/server-filesystem@2026.1.14`, after those fixes. It is also narrower: direct file-content access remains denied, but `list_directory_with_sizes` still leaks outside-target file size by following symlinks during child metadata collection.\r\n\r\n## Source Locations\r\n\r\nRepository source:\r\n- `servers/src/filesystem/index.ts:465-473`\r\n\r\nPublished npm package:\r\n- `touchstone-poc/mcp-modelcontextprotocol-server-filesystem/published-package/package/dist/index.js:353-360`\r\n\r\n## Reproduction\r\n\r\n1. Install or unpack `@modelcontextprotocol/server-filesystem@2026.1.14`.\r\n2. Create an allowed directory and a separate outside directory.\r\n3. Create a 1536-byte file in the outside directory.\r\n4. Create a symlink inside the allowed directory pointing to that outside file.\r\n5. Start the published server over stdio with the allowed directory.\r\n6. Call `read_text_file` on the outside file. It is denied.\r\n7. Call `read_text_file` on the symlink inside the allowed directory. It is denied.\r\n8. Call `list_directory_with_sizes` on the allowed directory. It returns `1.50 KB` for the symlink, leaking the outside target's size.\r\n\r\nPoC command:\r\n\r\n```powershell\r\nnode touchstone-poc\\mcp-modelcontextprotocol-server-filesystem\\poc-list-directory-symlink-metadata.js\r\n```\r\n\r\nObserved output:\r\n\r\n```json\r\n{\r\n  \"directOutsideReadDenied\": true,\r\n  \"symlinkReadDenied\": true,\r\n  \"leakedSizeInListing\": true,\r\n  \"outsideSecretSize\": 1536,\r\n  \"listingText\": \"[FILE] link-to-outside-secret.bin        1.50 KB\\n\\nTotal: 1 files, 0 directories\\nCombined size: 1.50 KB\"\r\n}\r\n```\r\n\r\nFull transcript:\r\n- `touchstone-poc/mcp-modelcontextprotocol-server-filesystem/poc-list-directory-symlink-metadata-output.json`\r\n\r\n## Impact\r\n\r\nAn attacker who can place or preserve a symlink inside an allowed directory can cause an MCP client or agent to call `list_directory_with_sizes` and disclose metadata about files outside the allowed directory. The confirmed leaked field is file size. This can reveal the existence and approximate size of sensitive files such as private keys, local databases, credentials, or other user files when a symlink to those files is present in the allowed tree.\r\n\r\n## Preconditions\r\n\r\n- The victim runs `@modelcontextprotocol/server-filesystem@2026.1.14` with an allowed directory.\r\n- A symlink exists inside that allowed directory and points to a file outside the allowed directory.\r\n- The attacker controls the MCP tool input directly, or indirectly through prompt injection or repository content that persuades an agent to call `list_directory_with_sizes`.\r\n\r\n## Remediation\r\n\r\nValidate each child path before following it:\r\n\r\n```ts\r\nconst entryPath = path.join(validPath, entry.name);\r\nconst validEntryPath = await validatePath(entryPath);\r\nconst stats = await fs.stat(validEntryPath);\r\n```\r\n\r\nAlternatively, use `fs.lstat()` and report symlink metadata without following the target. If target metadata is intentionally shown, the target must be resolved and checked against the allowed-directory policy before calling `fs.stat()`.\r\n\r\n## Reporter Credit\r\n\r\nCredit: @cybercraftsolutionsllc\n\n## Upgrading from a date-versioned release\n\n`@modelcontextprotocol/server-filesystem` moved from date versions (2025.x, 2026.x) to semantic versions in 1.0.0. Package managers rank 2.0.1 below every 2025.x and 2026.x version, so they will not offer it as an upgrade and Dependabot will not open one. If you are on a date version, install the fix explicitly: `npm install @modelcontextprotocol/server-filesystem@2.0.1` (or `@latest`).","summary":"The @modelcontextprotocol/server-filesystem tool list_directory_with_sizes calls fs.stat on symlinked children without checking their targets, so symlinks inside an allowed directory reveal the size of files outside it. An attacker who can place a symlink in the allowed tree, or steer an agent into calling the tool, learns the existence and approximate size of outside files, but not their contents.","severity":"low","cvss":3.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"@modelcontextprotocol/server-filesystem","fixed":[],"affected":">= 2025.7.1, <= 2026.8.31","ecosystem":"npm"},{"name":"@modelcontextprotocol/server-filesystem","fixed":["2.0.1"],"affected":">= 1.0.0, < 2.0.1","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["ai-tooling","claude-code","cwe-59","filesystem","mcp","node","npm"],"not_affected_if":["default-config","no-symlinks-in-allowed-dir"],"how_to_check":"- Check the installed version with `npm ls @modelcontextprotocol/server-filesystem` and look for versions `>= 1.0.0` and `< 2.0.1`, or `>= 2025.7.1` and `<= 2026.8.31`.\n- Check whether the server is configured with an allowed directory that could contain symlinks pointing outside it.","fix":"- `@modelcontextprotocol/server-filesystem`: move to `2.0.1` (npm will not offer it automatically because the 2025.x and 2026.x date versions rank higher; install it explicitly).","status":"open","published_at":"2026-10-25T00:19:37+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":"ai-tooling","confidence":0.8,"triage":{"at":"2026-11-09T22:58:22Z","model":"claude-haiku-5-5","output":{"fix":"- `@modelcontextprotocol/server-filesystem`: move to `2.0.1` (npm will not offer it automatically because the 2025.x and 2026.x date versions rank higher; install it explicitly).","reach":"local","title":"MCP filesystem server: directory listing leaks sizes of files outside allowed folder","impact":"low","summary":"The @modelcontextprotocol/server-filesystem tool list_directory_with_sizes calls fs.stat on symlinked children without checking their targets, so symlinks inside an allowed directory reveal the size of files outside it. An attacker who can place a symlink in the allowed tree, or steer an agent into calling the tool, learns the existence and approximate size of outside files, but not their contents.","audience":"everyone","category":"ai-tooling","nicknames":[],"who_cares":["mcp","npm","node","filesystem","claude-code"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"file sizes of outside files disclosed to an MCP client","analyst_note":"","how_to_check":"- Check the installed version with `npm ls @modelcontextprotocol/server-filesystem` and look for versions `>= 1.0.0` and `< 2.0.1`, or `>= 2025.7.1` and `<= 2026.8.31`.\n- Check whether the server is configured with an allowed directory that could contain symlinks pointing outside it.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-config","no-symlinks-in-allowed-dir"],"who_is_affected":"Teams that run the filesystem MCP server with an allowed directory that may contain symlinks to files outside it. Exposure requires a symlink in the allowed tree and a client or agent that calls list_directory_with_sizes; direct reads through the same symlink remain denied.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"low"},"cost_usd":0.000968,"input_tokens":6329,"output_tokens":670,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-360':314C '-473':305C '-59':34B '/mcp-modelcontextprotocol-server-filesystem/poc-list-directory-symlink-metadata-output.json':460C '/mcp-modelcontextprotocol-server-filesystem/published-package/package/dist/index.js':312C '0':449C '1':316C,447C '1.50':405C,443C,453C '1536':335C,439C '2':322C '2025':197C '2025.7.1':258C '2026.1.14':265C,321C,543C '23w5':229C '3':332C '353':313C '4':342C '465':304C '5':355C '5cgr':216C '6':366C '7':378C '7w4m':247C '8':393C '8w59':223C '9h6v':213C '9j4x':228C '9xwc':221C 'access':277C 'advisori':208C,241C 'affect':230C,255C 'agent':82B,481C,583C 'ai':28B 'ai-tool':27B 'allow':59B,77B,136C,145C,325C,348C,364C,388C,401C,473C,495C,536C,546C,553C,562C 'alreadi':195C 'already-publish':194C 'also':271C 'altern':614C 'approxim':91B,510C 'attack':69B,463C,565C 'await':606C,611C 'bypass':177C 'byte':336C 'call':46B,84B,117C,367C,379C,394C,483C,585C 'caus':147C,476C 'cfc4':212C 'check':52B 'child':122C,126C,296C,593C 'children':50B 'cj7g':19A,24A 'claud':31B 'claude-cod':30B 'client':479C 'code':32B 'collect':298C 'command':417C 'confirm':498C 'const':599C,604C,609C 'content':99B,276C,579C 'control':566C 'correct':165C 'creat':323C,333C,343C 'credenti':521C 'current':185C 'cves':201C 'cwe':33B 'databas':520C 'deni':166C,279C,377C,392C 'descript':100C 'direct':158C,273C,571C 'directori':3A,12A,43B,60B,102C,109C,137C,146C,180C,282C,326C,331C,341C,349C,365C,389C,396C,402C,450C,474C,485C,496C,547C,554C,563C,587C 'directoutsidereaddeni':432C 'disclos':151C,489C 'duplic':204C 'entri':115C 'entry.name':603C 'entrypath':119C,600C,608C 'exist':89B,508C,550C 'field':500C 'file':13A,65B,95B,142C,156C,170C,275C,290C,337C,354C,370C,374C,382C,441C,448C,492C,502C,514C,525C,531C,559C 'file-cont':274C 'filesystem':35B,240C,427C 'fix':268C 'follow':6A,129C,293C,596C,622C 'fs.lstat':616C 'fs.stat':47B,118C,128C,612C 'full':455C 'fx2p':246C 'ghsa':16A,21A,210C,215C,220C,226C,244C,250C 'ghsa-5cgr-j3jf-jw3v':214C 'ghsa-9xwc-hfwc-8w59':219C 'ghsa-hc55-p739-j48w':249C 'ghsa-hhwq-wxfx-cj7g':15A,20A 'ghsa-j22h-9j4x-23w5':225C 'ghsa-q66q-fx2p-7w4m':243C 'ghsa-vjqx-cfc4-9h6v':209C 'git':234C 'hc55':251C 'hfwc':222C 'hhwq':17A,22A 'impact':461C 'indirect':573C 'inject':576C 'input':570C 'insid':57B,134C,346C,386C,471C,551C 'instal':317C 'intent':629C 'iter':113C 'j22h':227C 'j3jf':217C 'j48w':253C 'json':431C 'jw3v':218C 'kb':406C,444C,454C 'key':518C 'known':206C 'leak':9A,286C,410C,499C 'leakedsizeinlist':436C 'learn':87B 'link-to-outside-secret.bin':442C 'list':2A,42B,101C,179C,281C,395C,484C,586C 'listingtext':440C 'local':519C 'locat':300C 'mcp':36B,232C,424C,478C,568C 'mcp-modelcontextprotocol-server-filesystem':423C 'mcp-server-git':231C 'metadata':14A,176C,297C,490C,620C,627C 'modelcontextprotocol':425C 'modelcontextprotocol/server-filesystem':1A,25A,26A,40B,236C,264C,320C,542C 'must':633C 'n':445C 'narrow':272C 'ncombin':451C 'node':37B,419C 'non':203C 'non-dupl':202C 'npm':38B,186C,307C 'ntotal':446C 'observ':429C 'output':430C 'outsid':11A,66B,94B,143C,153C,288C,330C,340C,353C,373C,412C,493C,560C 'outside-directori':10A 'outside-target':287C 'outsidesecrets':438C 'p739':252C 'packag':308C 'path':110C,127C,594C 'path.join':601C 'persuad':581C 'place':72B,466C 'poc':311C,416C,422C,459C 'poc-list-directory-symlink-metadata.js':428C 'point':139C,350C,556C 'powershel':418C 'precondit':538C 'prefix':200C 'present':533C 'preserv':468C 'privat':517C 'prompt':575C 'publish':196C,306C,358C 'q66q':245C 'rather':188C 'rational':205C 'read':159C,168C,368C,380C 'releas':187C 'relev':239C 'remain':278C 'remedi':590C 'report':191C,260C,618C 'repositori':207C,301C,578C 'reproduc':262C 'reproduct':315C 'request':108C 'residu':175C 'resolv':635C 'return':404C 'reveal':61B,506C 'run':541C 'sensit':513C 'separ':329C 'server':233C,359C,426C 'servers/src/filesystem/index.ts':303C 'shown':630C 'size':5A,45B,63B,92B,104C,157C,182C,284C,291C,398C,415C,452C,487C,503C,511C,589C 'sourc':299C,302C 'start':356C 'stat':610C 'stdio':361C 'steer':80B 'still':285C 'symlink':7A,49B,56B,74B,130C,133C,163C,198C,294C,345C,385C,409C,470C,528C,549C,619C 'symlinkreaddeni':434C 'target':54B,154C,289C,413C,624C,626C,632C 'text':169C,369C,381C 'tool':29B,41B,86B,149C,569C 'touchston':310C,421C,458C 'touchstone-poc':309C,420C,457C 'transcript':456C 'tree':78B,537C 'true':433C,435C,437C 'ts':598C 'two':238C 'unpack':319C 'use':615C 'user':524C 'valid':105C,124C,591C 'validatepath':607C 'validentrypath':605C,613C 'validpath':602C 'version':256C 'victim':540C 'vjqx':211C 'without':51B,123C,621C 'wxfx':18A,23A","nicknames":[],"mitigations":[],"who_affected":"Teams that run the filesystem MCP server with an allowed directory that may contain symlinks to files outside it. Exposure requires a symlink in the allowed tree and a client or agent that calls list_directory_with_sizes; direct reads through the same symlink remain denied.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"everyone","plain_action":null,"pkeys":["serverfilesystem"],"ptokens":["filesystem","modelcontextprotocol","server"],"labels":["aitooling","claudecode","cwe59","filesystem","low","mcp","node","npm"],"campaign_type":null,"search_a":"'cj7g':19A,24A 'directori':3A,12A 'file':13A 'follow':6A 'ghsa':16A,21A 'ghsa-hhwq-wxfx-cj7g':15A,20A 'hhwq':17A,22A 'leak':9A 'list':2A 'metadata':14A 'modelcontextprotocol/server-filesystem':1A,25A,26A 'outsid':11A 'outside-directori':10A 'size':5A 'symlink':7A 'wxfx':18A,23A","search_b":"'-59':34B 'agent':82B 'ai':28B 'ai-tool':27B 'allow':59B,77B 'approxim':91B 'attack':69B 'call':46B,84B 'check':52B 'children':50B 'claud':31B 'claude-cod':30B 'code':32B 'content':99B 'cwe':33B 'directori':43B,60B 'exist':89B 'file':65B,95B 'filesystem':35B 'fs.stat':47B 'insid':57B 'learn':87B 'list':42B 'mcp':36B 'modelcontextprotocol/server-filesystem':40B 'node':37B 'npm':38B 'outsid':66B,94B 'place':72B 'reveal':61B 'size':45B,63B,92B 'steer':80B 'symlink':49B,56B,74B 'target':54B 'tool':29B,41B,86B 'tree':78B 'without':51B","search_c":"'-360':314C '-473':305C '/mcp-modelcontextprotocol-server-filesystem/poc-list-directory-symlink-metadata-output.json':460C '/mcp-modelcontextprotocol-server-filesystem/published-package/package/dist/index.js':312C '0':449C '1':316C,447C '1.50':405C,443C,453C '1536':335C,439C '2':322C '2025':197C '2025.7.1':258C '2026.1.14':265C,321C,543C '23w5':229C '3':332C '353':313C '4':342C '465':304C '5':355C '5cgr':216C '6':366C '7':378C '7w4m':247C '8':393C '8w59':223C '9h6v':213C '9j4x':228C '9xwc':221C 'access':277C 'advisori':208C,241C 'affect':230C,255C 'agent':481C,583C 'allow':136C,145C,325C,348C,364C,388C,401C,473C,495C,536C,546C,553C,562C 'alreadi':195C 'already-publish':194C 'also':271C 'altern':614C 'approxim':510C 'attack':463C,565C 'await':606C,611C 'bypass':177C 'byte':336C 'call':117C,367C,379C,394C,483C,585C 'caus':147C,476C 'cfc4':212C 'child':122C,126C,296C,593C 'client':479C 'collect':298C 'command':417C 'confirm':498C 'const':599C,604C,609C 'content':276C,579C 'control':566C 'correct':165C 'creat':323C,333C,343C 'credenti':521C 'current':185C 'cves':201C 'databas':520C 'deni':166C,279C,377C,392C 'descript':100C 'direct':158C,273C,571C 'directori':102C,109C,137C,146C,180C,282C,326C,331C,341C,349C,365C,389C,396C,402C,450C,474C,485C,496C,547C,554C,563C,587C 'directoutsidereaddeni':432C 'disclos':151C,489C 'duplic':204C 'entri':115C 'entry.name':603C 'entrypath':119C,600C,608C 'exist':508C,550C 'field':500C 'file':142C,156C,170C,275C,290C,337C,354C,370C,374C,382C,441C,448C,492C,502C,514C,525C,531C,559C 'file-cont':274C 'filesystem':240C,427C 'fix':268C 'follow':129C,293C,596C,622C 'fs.lstat':616C 'fs.stat':118C,128C,612C 'full':455C 'fx2p':246C 'ghsa':210C,215C,220C,226C,244C,250C 'ghsa-5cgr-j3jf-jw3v':214C 'ghsa-9xwc-hfwc-8w59':219C 'ghsa-hc55-p739-j48w':249C 'ghsa-j22h-9j4x-23w5':225C 'ghsa-q66q-fx2p-7w4m':243C 'ghsa-vjqx-cfc4-9h6v':209C 'git':234C 'hc55':251C 'hfwc':222C 'impact':461C 'indirect':573C 'inject':576C 'input':570C 'insid':134C,346C,386C,471C,551C 'instal':317C 'intent':629C 'iter':113C 'j22h':227C 'j3jf':217C 'j48w':253C 'json':431C 'jw3v':218C 'kb':406C,444C,454C 'key':518C 'known':206C 'leak':286C,410C,499C 'leakedsizeinlist':436C 'link-to-outside-secret.bin':442C 'list':101C,179C,281C,395C,484C,586C 'listingtext':440C 'local':519C 'locat':300C 'mcp':232C,424C,478C,568C 'mcp-modelcontextprotocol-server-filesystem':423C 'mcp-server-git':231C 'metadata':176C,297C,490C,620C,627C 'modelcontextprotocol':425C 'modelcontextprotocol/server-filesystem':236C,264C,320C,542C 'must':633C 'n':445C 'narrow':272C 'ncombin':451C 'node':419C 'non':203C 'non-dupl':202C 'npm':186C,307C 'ntotal':446C 'observ':429C 'output':430C 'outsid':143C,153C,288C,330C,340C,353C,373C,412C,493C,560C 'outside-target':287C 'outsidesecrets':438C 'p739':252C 'packag':308C 'path':110C,127C,594C 'path.join':601C 'persuad':581C 'place':466C 'poc':311C,416C,422C,459C 'poc-list-directory-symlink-metadata.js':428C 'point':139C,350C,556C 'powershel':418C 'precondit':538C 'prefix':200C 'present':533C 'preserv':468C 'privat':517C 'prompt':575C 'publish':196C,306C,358C 'q66q':245C 'rather':188C 'rational':205C 'read':159C,168C,368C,380C 'releas':187C 'relev':239C 'remain':278C 'remedi':590C 'report':191C,260C,618C 'repositori':207C,301C,578C 'reproduc':262C 'reproduct':315C 'request':108C 'residu':175C 'resolv':635C 'return':404C 'reveal':506C 'run':541C 'sensit':513C 'separ':329C 'server':233C,359C,426C 'servers/src/filesystem/index.ts':303C 'shown':630C 'size':104C,157C,182C,284C,291C,398C,415C,452C,487C,503C,511C,589C 'sourc':299C,302C 'start':356C 'stat':610C 'stdio':361C 'still':285C 'symlink':130C,133C,163C,198C,294C,345C,385C,409C,470C,528C,549C,619C 'symlinkreaddeni':434C 'target':154C,289C,413C,624C,626C,632C 'text':169C,369C,381C 'tool':149C,569C 'touchston':310C,421C,458C 'touchstone-poc':309C,420C,457C 'transcript':456C 'tree':537C 'true':433C,435C,437C 'ts':598C 'two':238C 'unpack':319C 'use':615C 'user':524C 'valid':105C,124C,591C 'validatepath':607C 'validentrypath':605C,613C 'validpath':602C 'version':256C 'victim':540C 'vjqx':211C 'without':123C,621C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-10-25T00:19:37+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-hhwq-wxfx-cj7g","url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-hhwq-wxfx-cj7g","severity":"low","published_at":"2026-10-25T00:19:37+00:00","modified_at":"2026-10-25T00:19:37+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":110618,"url":"https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-hhwq-wxfx-cj7g","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"}