Skip to content

CVE-2026-72572: Verified Reproduction

CVE-2026-72572: Unauthenticated path traversal in xmysql /download allows arbitrary file read via unsanitized req.query.name .

CVE-2026-72572 is verified against xmysql · npm / JavaScript (Node.js, Express). Affected versions: all versions (project is deprecated/superseded by nocodb; no patched version exists). Vulnerability class: Path Traversal. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00324.

REPRO-2026-00324 xmysql · npm / JavaScript (Node.js, Express) Path Traversal Aug 23, 2026 CVE entry .txt
Severity
HIGH
CVSS
7.5
Confidence
HIGH
Reproduced in
12m 11s
Tool calls
126
Spend
$2.01
01 · Overview

What Is CVE-2026-72572?

CVE-2026-72572 is a high-severity Path Traversal vulnerability affecting xmysql all versions (project is deprecated/superseded by nocodb; no patched version exists). Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00324).

02 · Severity & CVSS

CVE-2026-72572 Severity & CVSS Score

CVE-2026-72572 is rated high severity, with a CVSS base score of 7.5 out of 10.

HIGH threat level
7.5 / 10 CVSS base
03 · Affected Versions

Affected xmysql Versions

xmysql · npm / JavaScript (Node.js, Express) versions all versions (project is deprecated/superseded by nocodb; no patched version exists) are affected.

How to Reproduce CVE-2026-72572

$ pruva-verify REPRO-2026-00324
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00324/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-72572

Information disclosure — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

name query parameter of GET /download (unauthenticated)

Attack chain
  1. GET /download?name=../../../../../../../etc/passwd
  2. lib/xapi.js downloadFile
  3. path.join(process.cwd(), req.query.name)
  4. res.download(file)
How the agent worked 267 events · 126 tool calls · 12 min
12 minDuration
126Tool calls
45Reasoning steps
267Events
8Dead-ends
Agent activity over 12 min
Policy
1
Support
9
Repro
141
Judge
35
Variant
76
Verify
1
0:0012:02

Root Cause and Exploit Chain for CVE-2026-72572

Versions: all versions. The project was renamed to NocoDB and the

xmysql (o1lab/xmysql, a zero-config REST API generator for MySQL/MariaDB) exposes an unauthenticated GET /download endpoint whose handler downloadFile(req, res) in lib/xapi.js builds a filesystem path with path.join(process.cwd(), req.query.name) and passes it directly to Express res.download(file). The name query parameter is never validated, normalized against, or confined to a base directory, so an unauthenticated remote attacker can supply ../ traversal sequences and read any file readable by the xmysql process.

  • Package/component: o1lab/xmysql (npm xmysql), route handler lib/xapi.js:downloadFile (lines 424–427 at commit 8c6b00e).
  • Affected versions: all versions. The project was renamed to NocoDB and the repository archived; no patched upstream version of xmysql exists.
  • Risk level and consequences: High. Unauthenticated arbitrary file read from the server filesystem (database credentials, /etc/passwd, TLS keys, application source, cloud metadata files on disk, etc.). The route is registered by default whenever the MySQL host is localhost (program.dynamic = 1 in lib/util/cmd.helper.js) and readOnly is false (the default), so default deployments are exposed.

Impact Parity

  • Disclosed/claimed maximum impact: unauthenticated remote arbitrary file disclosure (info_leak via api_remote).
  • Reproduced impact from this run: unauthenticated remote arbitrary file read — /etc/passwd returned byte-identical over HTTP 200, and a per-run planted secret file (/tmp/pruva_xmysql_secret.txt with a unique token) was recovered verbatim through the same endpoint.
  • Parity: full.
  • Not demonstrated: nothing claimed beyond file disclosure; no further impact was claimed or required.

Root Cause

In lib/xapi.js:

downloadFile(req, res) {
  let file = path.join(process.cwd(), req.query.name);
  res.download(file);
}

req.query.name is fully attacker-controlled. path.join resolves .. segments lexically, so a value such as ../../../../../../etc/passwd escapes the process working directory entirely (excess .. above / collapse to /). The result is handed to res.download(), which happily streams any file the process can read. There is:

  1. no authentication middleware on the route (registered as this.app.get("/download", this.downloadFile.bind(this)) inside the dynamic === 1 && !readOnly block at lib/xapi.js:322–340),
  2. no allowlist/confined upload-download directory, and
  3. no rejection of .. or absolute-path components.
  • Fix commit: none known; xmysql is unmaintained/archived (renamed to NocoDB).

Reproduction Steps

  1. bundle/repro/reproduction_steps.sh (idempotent; run twice consecutively, both exit 0).
  2. The script:
    • reuses the prepared project cache (/pruva/project-cache/repo) or clones https://github.com/o1lab/xmysql, then pins commit 8c6b00ee22860230975e43ab705d015d2235e308 (v0.6.0, latest master) and asserts the vulnerable line is present in lib/xapi.js;
    • installs and starts MariaDB, creates schema reprodb with a table, and gives the TCP account a native password (dual-mode SQL runner keeps it idempotent);
    • installs node dependencies with npm install --ignore-scripts (the declared but unused sleep@6.1.0 native module fails to build on modern Node and is irrelevant);
    • starts the real product: node bin/index.js -h 127.0.0.1 -u root -p rootpass -d reprodb -n 3000 and waits for /_health;
    • plants a unique-token secret file outside the app working directory;
    • sends the unauthenticated attacker request GET /download?name=../../../../../../../etc/passwd and GET /download?name=../../../../../../../tmp/pruva_xmysql_secret.txt;
    • runs a benign control request (name=definitely_not_here.txt, observed HTTP 400);
    • verifies byte-identity with /etc/passwd and token recovery, then writes bundle/repro/runtime_manifest.json and exits 0 on success.
  3. Expected evidence: HTTP 200 responses with Content-Disposition: attachment for both traversal requests; downloaded /etc/passwd byte-identical to the real file; planted secret token recovered.

Evidence

  • bundle/logs/reproduction_steps.log — full session log of both runs.
  • bundle/logs/xmysql_service.log — real xmysql startup banner ("REST APIs Generated: 135").
  • bundle/logs/health_response.json/_health response proving service liveness.
  • bundle/logs/download_passwd_headers.txt + bundle/logs/downloaded_passwd.txt — HTTP 200 and byte-identical /etc/passwd (diff clean, ^root: present).
  • bundle/logs/download_secret_headers.txt + bundle/logs/downloaded_secret.txt — unique per-run token (e.g. PRUVA_XMYSQL_SECRET_1786374155853866884) recovered via traversal.
  • bundle/logs/download_benign_status.txt — benign control returned HTTP 400.
  • Environment: Linux x86_64, Node.js v24.18.0, MariaDB 11.8.6, xmysql v0.6.0 @ 8c6b00ee22860230975e43ab705d015d2235e308 (target digest d7544f1501df408c3c5353b430cef5842b463250b64824974436575afb948112).

Recommendations / Next Steps

  • Fix approach: drop the endpoint or confine downloads to a dedicated storage directory: resolve path.resolve(STORAGE_DIR, name) and reject any result that does not start with STORAGE_DIR + path.sep; additionally reject ../absolute inputs and require authentication/authorization on the route.
  • Upgrade guidance: xmysql is unmaintained; migrate to NocoDB or another maintained API layer. Until then, run with --readOnly (or a non-localhost DB host) so the dynamic block (upload/download routes) is never registered, or front the service with a proxy that blocks /download.
  • Testing recommendations: regression test that GET /download?name=../... returns 4xx and that downloads are confined to the storage directory.

Additional Notes

  • Idempotency: the script was executed twice consecutively in the same workspace and both runs exited 0; DB setup is dual-mode (unix-socket root first run, TCP password on re-runs) and service startup kills any previous instance.
  • Negative control: no patched upstream version exists (all versions affected, repository archived), so a fixed-version differential is not applicable; a benign in-cwd control request is included instead (HTTP 400 for a nonexistent file).
  • Edge cases: the route only exists when dynamic === 1 && !readOnly; dynamic defaults to 1 whenever the MySQL host is localhost/127.0.0.1/::1, which is the common deployment. The server binds to localhost (may resolve to ::1); the script probes both 127.0.0.1 and localhost.

CVE-2026-72572 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:09
0:00
session startedaccounts/fireworks/models/kimi-k3 · CVE-2026-72572 · REPRO-20
0:05
0:06
web search
0:07
0:26
0:26
extract_facts
no facts extracted
0:27
0:27
supportclaim_contract
0:32
0:32
0:32
0:35
0:35
0:37
0:37
0:43
0:43
$ls -la /pruva/project-cache/ 2>/dev/null; ls /pruva/project-cache/repo-mirrors 2>/dev/null; node --version; npm --version; which mysqld mariadbd 2>/dev/null; echo "---"; df -h /pruva 2>/dev/null | tail -1
0.4s
total 0
drwxr-xr-x 4 vscode vscode 80 Aug 10 14:57 .
drwxr-xr-x 1 root   root   26 Aug 10 14:57 ..
drwxr-xr-x 2 vscode vscode 60 Aug 10 14:57 .pruva
drwxr-xr-x 2 vscode vscode 40 Aug 10 14:57 repo-mirrors
v24.18.0
11.16.0
---
0:47
0:48
$git clone --depth 50 https://github.com/o1lab/xmysql /pruva/project-cache/repo 2>&1 | tail -3; ls /pruva/project-cache/repo
1.0s
Cloning into '/pruva/project-cache/repo'...
CONTRIBUTING.md
ISSUE_TEMPLATE.md
LICENSE
README.md
assets
bin
dev.Dockerfile
0:48
$apt-cache policy mariadb-server mysql-server 2>/dev/null | head -20; ls /etc/mysql 2>/dev/null; which docker
0.3s
/usr/bin/docker
0:51
$grep -n "download" /pruva/project-cache/repo/lib/xapi.js | head; grep -n "dynamic\|readOnly" /pruva/project-cache/repo/lib/xapi.js | head -20
0.2s
338:      this.app.get("/download", this.downloadFile.bind(this));
424:  downloadFile(req, res) {
426:    res.download(file);
156:            if (!this.config.readOnly)
171:            if (!this.config.readOnly) {
183:            if (!this.config.readOnly) {
195:            if (!this.config.readOnly) {
209:            if (!this.config.readOnly) {
0:51
$docker ps 2>&1 | head -3; docker images 2>&1 | head -10
0.3s
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
WARNING: This output is designed for human readability. For machine-readable output, please use --format.
IMAGE                                ID             DISK USAGE   CONTENT SIZE   EXTRA
pruva/rootless-readiness:docker-27   0aec26e1b423       1.47MB             0B        
0:55
0:55
1:00
1:03
$sudo apt-get update -qq 2>&1 | tail -2; apt-cache policy mariadb-server 2>&1 | head -5
2.6s
mariadb-server:
  Installed: (none)
  Candidate: 1:11.8.6-5ubuntu0.1
  Version table:
     1:11.8.6-5ubuntu0.1 500
1:03
$grep -n "setupRoutesForEachTable\|dynamic\|init\|connect" /pruva/project-cache/repo/lib/xapi.js | head -20; head -60 /pruva/project-cache/repo/index.js
0.3s
35:  init(cbk) {
36:    this.mysql.init((err, results) => {
322:    if (this.config.dynamic === 1 && !this.config.readOnly) {
324:        .route("/dynamic*")
exports.xapi = require('./lib/xapi.js')
1:09
08 · How to Fix

How to Fix CVE-2026-72572

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-72572 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-72572

Is CVE-2026-72572 exploitable?

Yes. Pruva independently reproduced CVE-2026-72572 in xmysql and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00324).

How severe is CVE-2026-72572?

CVE-2026-72572 is rated high severity, with a CVSS score of 7.5 out of 10.

What type of vulnerability is CVE-2026-72572?

CVE-2026-72572 is classified as CWE-22 (Path Traversal) (Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')), a Path Traversal vulnerability.

Which versions of xmysql are affected by CVE-2026-72572?

xmysql all versions (project is deprecated/superseded by nocodb; no patched version exists) is affected by CVE-2026-72572.

How can I reproduce CVE-2026-72572?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-72572 reproduction verified?

Yes. Pruva reproduced CVE-2026-72572 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-72572

Authoritative sources for CVE-2026-72572 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.