{"id":"CVE-2026-40163","aliases":["EUVD-2026-21517","GHSA-32pv-mpqg-h292"],"status":"published","title":"Saltcorn has an Unauthenticated Path Traversal in sync endpoints allows arbitrary file write and directory read","description":"Saltcorn is an extensible, open source, no-code database application builder. Prior to 1.4.5, 1.5.5, and 1.6.0-beta.4, the POST /sync/offline_changes endpoint allows an unauthenticated attacker to create arbitrary directories and write a changes.json file with attacker-controlled JSON content anywhere on the server filesystem. The GET /sync/upload_finished endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files. This vulnerability is fixed in 1.4.5, 1.5.5, and 1.6.0-beta.4.","severity":{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00339,"percentile":0.27701,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22"],"published":"2026-04-10T17:07:49Z","modified":"2026-10-28T02:31:14Z","fixes":[{"ecosystem":"npm","package":"@saltcorn/server","fixed_in":["1.4.5","1.5.5","1.6.0-beta.4"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.4.5"}]}]},{"source":"ghsa","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.5.0-beta.0"},{"fixed":"1.5.5"}]}]},{"source":"ghsa","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.6.0-alpha.0"},{"fixed":"1.6.0-beta.4"}]}]},{"source":"osv","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.5"}]}]},{"source":"osv","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"SEMVER","events":[{"introduced":"1.5.0-beta.0"},{"fixed":"1.5.5"}]}]},{"source":"osv","ecosystem":"npm","package":"@saltcorn/server","ranges":[{"type":"SEMVER","events":[{"introduced":"1.6.0-alpha.0"},{"fixed":"1.6.0-beta.4"}]}]}],"products":[{"vendor":"saltcorn","product":"saltcorn"}],"references":[{"url":"https://github.com/saltcorn/saltcorn/security/advisories/GHSA-32pv-mpqg-h292","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-40163","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-32pv-mpqg-h292"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/40xxx/CVE-2026-40163.json","tags":["advisory"]},{"url":"https://github.com/saltcorn/saltcorn","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-40163","title":"Saltcorn has an Unauthenticated Path Traversal in sync endpoints allows arbitrary file write and directory read","description":"Saltcorn is an extensible, open source, no-code database application builder. Prior to 1.4.5, 1.5.5, and 1.6.0-beta.4, the POST /sync/offline_changes endpoint allows an unauthenticated attacker to create arbitrary directories and write a changes.json file with attacker-controlled JSON content anywhere on the server filesystem. The GET /sync/upload_finished endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files. This vulnerability is fixed in 1.4.5, 1.5.5, and 1.6.0-beta.4.","status":"published","published":"2026-04-10T17:07:49.067Z","modified":"2026-04-15T14:50:01.616Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T13:43:22.599321Z"}],"euvd":[{"id":"EUVD-2026-21517","description":"Saltcorn is an extensible, open source, no-code database application builder. Prior to 1.4.5, 1.5.5, and 1.6.0-beta.4, the POST /sync/offline_changes endpoint allows an unauthenticated attacker to create arbitrary directories and write a changes.json file with attacker-controlled JSON content anywhere on the server filesystem. The GET /sync/upload_finished endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files. This vulnerability is fixed in 1.4.5, 1.5.5, and 1.6.0-beta.4.","status":"published","published":"2026-04-10T17:07:49Z","modified":"2026-04-15T14:50:01Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T19:24:25.212217Z"}],"ghsa":[{"id":"GHSA-32pv-mpqg-h292","title":"Saltcorn has an Unauthenticated Path Traversal in sync endpoints, allowing arbitrary file write and directory read","description":"### Summary\n\nTwo unauthenticated path traversal vulnerabilities exist in Saltcorn's mobile sync endpoints. The `POST /sync/offline_changes` endpoint allows an unauthenticated attacker to create arbitrary directories and write a `changes.json` file with attacker-controlled JSON content anywhere on the server filesystem. The `GET /sync/upload_finished` endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files.\n\nThe safe path validation function `File.normalise_in_base()` exists in the codebase and is correctly used by the `clean_sync_dir` endpoint in the **same file** (fix for GHSA-43f3-h63w-p6f6), but was not applied to these two endpoints.\n\n### Details\n\n**Finding 1: Arbitrary file write — `POST /sync/offline_changes` (sync.js line 226)**\n\nThe `newSyncTimestamp` parameter from the request body is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = path.join(\n    rootFolder.location, \"mobile_app\", \"sync\", syncDirName\n);\nawait fs.mkdir(syncDir, { recursive: true });        // creates arbitrary dir\nawait fs.writeFile(\n    path.join(syncDir, \"changes.json\"),\n    JSON.stringify(changes)                           // writes attacker content\n);\n```\n\nNo authentication middleware is applied to this route. Since `path.join()` normalizes `../` sequences, setting `newSyncTimestamp` to `../../../../tmp/evil` causes the path to resolve outside the sync directory.\n\n**Finding 2: Arbitrary directory read — `GET /sync/upload_finished` (sync.js line 288)**\n\nThe `dir_name` query parameter is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDir = path.join(\n    rootFolder.location, \"mobile_app\", \"sync\", dir_name\n);\nlet entries = await fs.readdir(syncDir);\n```\n\nAlso unauthenticated. An attacker can list directory contents and read files named `translated-ids.json`, `unique-conflicts.json`, `data-conflicts.json`, or `error.json` from any directory.\n\n**Contrast — fixed endpoint in the same file (line 342):**\n\nThe `clean_sync_dir` endpoint correctly uses `File.normalise_in_base()`:\n\n```javascript\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    dir_name\n);\nif (syncDir) await fs.rm(syncDir, { recursive: true, force: true });\n```\n\n### PoC\n\n```bash\n# Write arbitrary file to /tmp/\ncurl -X POST http://TARGET:3000/sync/offline_changes \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"newSyncTimestamp\": \"../../../../tmp/saltcorn_poc\",\n    \"oldSyncTimestamp\": \"0\",\n    \"changes\": {\"proof\": \"path_traversal_write\"}\n  }'\n# Result: /tmp/saltcorn_poc_public/changes.json created with attacker content\n\n# List /etc/ directory\ncurl \"http://TARGET:3000/sync/upload_finished?dir_name=../../../../etc\"\n```\n\n### Impact\n\n- **Unauthenticated arbitrary directory creation** anywhere on the filesystem\n- **Unauthenticated arbitrary JSON file write** (`changes.json`) to any writable directory\n- **Unauthenticated directory listing** of arbitrary directories\n- **Unauthenticated read** of specific JSON files from arbitrary directories\n- Potential for **remote code execution** via writing to sensitive paths (cron, systemd, Node.js module paths)\n\n### Remediation\n\nApply `File.normalise_in_base()` to both endpoints, matching the existing pattern in `clean_sync_dir`:\n\n```javascript\n// offline_changes fix\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    syncDirName\n);\nif (!syncDir) {\n    return res.status(400).json({ error: \"Invalid sync directory name\" });\n}\n\n// upload_finished fix\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    dir_name\n);\nif (!syncDir) {\n    return res.json({ finished: false });\n}\n```\n\nAdditionally, add `loggedIn` middleware to endpoints that modify server state.","status":"published","published":"2026-04-10T19:30:27Z","modified":"2026-04-10T19:30:28Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:39:52.102227Z"}],"nvd":[{"id":"CVE-2026-40163","description":"Saltcorn is an extensible, open source, no-code database application builder. Prior to 1.4.5, 1.5.5, and 1.6.0-beta.4, the POST /sync/offline_changes endpoint allows an unauthenticated attacker to create arbitrary directories and write a changes.json file with attacker-controlled JSON content anywhere on the server filesystem. The GET /sync/upload_finished endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files. This vulnerability is fixed in 1.4.5, 1.5.5, and 1.6.0-beta.4.","status":"published","published":"2026-04-10T18:16:46.233Z","modified":"2026-06-17T10:44:47.933Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:12:14.37744Z"}],"osv":[{"id":"CVE-2026-40163","title":"Saltcorn has an Unauthenticated Path Traversal in sync endpoints allows arbitrary file write and directory read","description":"Saltcorn is an extensible, open source, no-code database application builder. Prior to 1.4.5, 1.5.5, and 1.6.0-beta.4, the POST /sync/offline_changes endpoint allows an unauthenticated attacker to create arbitrary directories and write a changes.json file with attacker-controlled JSON content anywhere on the server filesystem. The GET /sync/upload_finished endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files. This vulnerability is fixed in 1.4.5, 1.5.5, and 1.6.0-beta.4.","status":"published","published":"2026-04-10T17:07:49.067Z","modified":"2026-10-28T02:31:14.658250451Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:45:03.040464Z"},{"id":"GHSA-32pv-mpqg-h292","title":"Saltcorn has an Unauthenticated Path Traversal in sync endpoints, allowing arbitrary file write and directory read","description":"### Summary\n\nTwo unauthenticated path traversal vulnerabilities exist in Saltcorn's mobile sync endpoints. The `POST /sync/offline_changes` endpoint allows an unauthenticated attacker to create arbitrary directories and write a `changes.json` file with attacker-controlled JSON content anywhere on the server filesystem. The `GET /sync/upload_finished` endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files.\n\nThe safe path validation function `File.normalise_in_base()` exists in the codebase and is correctly used by the `clean_sync_dir` endpoint in the **same file** (fix for GHSA-43f3-h63w-p6f6), but was not applied to these two endpoints.\n\n### Details\n\n**Finding 1: Arbitrary file write — `POST /sync/offline_changes` (sync.js line 226)**\n\nThe `newSyncTimestamp` parameter from the request body is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = path.join(\n    rootFolder.location, \"mobile_app\", \"sync\", syncDirName\n);\nawait fs.mkdir(syncDir, { recursive: true });        // creates arbitrary dir\nawait fs.writeFile(\n    path.join(syncDir, \"changes.json\"),\n    JSON.stringify(changes)                           // writes attacker content\n);\n```\n\nNo authentication middleware is applied to this route. Since `path.join()` normalizes `../` sequences, setting `newSyncTimestamp` to `../../../../tmp/evil` causes the path to resolve outside the sync directory.\n\n**Finding 2: Arbitrary directory read — `GET /sync/upload_finished` (sync.js line 288)**\n\nThe `dir_name` query parameter is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDir = path.join(\n    rootFolder.location, \"mobile_app\", \"sync\", dir_name\n);\nlet entries = await fs.readdir(syncDir);\n```\n\nAlso unauthenticated. An attacker can list directory contents and read files named `translated-ids.json`, `unique-conflicts.json`, `data-conflicts.json`, or `error.json` from any directory.\n\n**Contrast — fixed endpoint in the same file (line 342):**\n\nThe `clean_sync_dir` endpoint correctly uses `File.normalise_in_base()`:\n\n```javascript\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    dir_name\n);\nif (syncDir) await fs.rm(syncDir, { recursive: true, force: true });\n```\n\n### PoC\n\n```bash\n# Write arbitrary file to /tmp/\ncurl -X POST http://TARGET:3000/sync/offline_changes \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"newSyncTimestamp\": \"../../../../tmp/saltcorn_poc\",\n    \"oldSyncTimestamp\": \"0\",\n    \"changes\": {\"proof\": \"path_traversal_write\"}\n  }'\n# Result: /tmp/saltcorn_poc_public/changes.json created with attacker content\n\n# List /etc/ directory\ncurl \"http://TARGET:3000/sync/upload_finished?dir_name=../../../../etc\"\n```\n\n### Impact\n\n- **Unauthenticated arbitrary directory creation** anywhere on the filesystem\n- **Unauthenticated arbitrary JSON file write** (`changes.json`) to any writable directory\n- **Unauthenticated directory listing** of arbitrary directories\n- **Unauthenticated read** of specific JSON files from arbitrary directories\n- Potential for **remote code execution** via writing to sensitive paths (cron, systemd, Node.js module paths)\n\n### Remediation\n\nApply `File.normalise_in_base()` to both endpoints, matching the existing pattern in `clean_sync_dir`:\n\n```javascript\n// offline_changes fix\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    syncDirName\n);\nif (!syncDir) {\n    return res.status(400).json({ error: \"Invalid sync directory name\" });\n}\n\n// upload_finished fix\nconst syncDir = File.normalise_in_base(\n    path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n    dir_name\n);\nif (!syncDir) {\n    return res.json({ finished: false });\n}\n```\n\nAdditionally, add `loggedIn` middleware to endpoints that modify server state.","status":"published","published":"2026-04-10T19:30:27Z","modified":"2026-04-10T19:56:21.826357Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:46:35.050232Z"}]}}
