CVE-2026-88046 is a medium-severity (CVSS 5.3) Path Traversal vulnerability in github.com/rclone/rclone. A fix is available for github.com/rclone/rclone — see the affected versions and patch details below.
rclone: source object names can escape the configured root on upload
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-88046.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-88046 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 395,492 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
github.com/rclone/rcloneReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Multiple backends, when given a specially crafted object to copy, can escape the backend confinement.
| Backend | Keep/Close | Per-backend severity |
|---|---|---|
| sftp | Medium | Real filesystem escape, fires under default encoding. |
| smb | Low-Medium | Escapes to a different SMB share the credential can reach. |
| ftp | Low | Real, leading-.. overshoot PoC is partly neutralized by encoding; escape bounded to at/below the login base. |
| webdav | Low | Server-side ACLs are the real boundary. |
| b2 | Low | Same-account sibling bucket crossing on a flat keyspace. |
| swift | Low | Same, container. |
| qingstor | Low | Same. |
| oracleobjectstorage | Low | Same. |
| internetarchive | Low | IA items are owner-writable only; confined to user's own items. |
| storj | Low | Can retarget a different bucket in the same access grant. |
| filelu | Low | Confined to the user's own account. |
| shade | Low | Confined to the user's own drive. |
| sia | Low | siad API password already grants full-daemon access. |
Root cause
rclone core does not sanitize .. in a source object's Remote() - verified: nothing in fs/march, fs/sync, fs/list, or fs/operations rejects .. segments before the name reaches the destination backend's Put/Update/Mkdir. Confinement is therefore each backend's responsibility, and these backends join root + remote without a check.
This divides into two classes:
-
Bucket based backends -
bucket.Split(path.Join(f.root, rootRelativePath)):backend/b2/b2.go:404,backend/swift/swift.go:464,backend/qingstor/qingstor.go:198,backend/oracleobjectstorage/oracleobjectstorage.go:245,backend/internetarchive/internetarchive.go:1016,backend/smb/smb.go:885,backend/storj/fs.go:289.path.Joincollapses..on the standard (ASCII) form before encoding is applied (e.g.FromStandardPath(path.Join(...))atbackend/b2/b2.go:1641), soEncodeDotnever gets the chance to neutralize the...lib/bucket.Joindoes not clean paths (keeps..as a literal key segment);path.Joindoes.backend/s3,backend/azureblob,backend/googlecloudstoragealready usebucket.Joinand are therefore not affected.
-
Path based backends -
path.Join(root, remote)onto a real path:- sftp:
remotePath = path.Join(f.absRoot, f.opt.Enc.FromStandardPath(remote))(backend/sftp/sftp.go:2497). Default encoding isencoder.Display(==Standard), andFromStandardPathshort-circuits to a pass-through in that mode, so..survives;f.absRootis absolute, sopath.Join("/home/user/root", "../../../../etc/passwd")->/etc/passwd. - webdav:
filePathatbackend/webdav/webdav.go:426-432. - ftp:
path.Join(f.root, remote)at ~14 sites (e.g.backend/ftp/ftp.go:1247). - filelu, shade, sia: analogous joins.
- sftp:
Precondition that limits reachability
For any of these to fire, a source must hand rclone a Remote() containing raw ... That is only possible when:
- the source is a flat-keyspace object store (not a filesystem - a local/sftp/smb source cannot represent
../../xas one directory entry), and - the offending key was written with native, non-rclone tooling - rclone's own writer applies
EncodeDotand rewrites a..segment to fullwidth.., so you cannot create such a key through rclone.
rclone's source-side listing does pass a natively-planted raw .. key through unchanged (verified for b2: remote := file.Name[len(prefix):] after ToStandardPath, backend/b2/b2.go:858,867). The reports never establish this precondition; it is the same omission across every member of the class.
Example attack
# Step 1 - attacker, using NATIVE S3 tooling (NOT rclone) on a source the victim ingests from:
aws s3api put-object --bucket shared-drop --key '../../victim-backups/pwned.txt' --body evil.txt
# Step 2 - victim's ordinary ingest:
rclone copy s3-drop:shared-drop b2:victim-uploads/incoming
# path.Join("victim-uploads/incoming", "../../victim-backups/pwned.txt") = "victim-backups/pwned.txt"
# -> lands in the victim's victim-backups bucket instead of under incoming/
The blast radius is the victim's own account (a bucket/share/path the configured credential already reaches) - integrity misdirection, not a cross-tenant or confidentiality breach. sftp/smb are the exception in reach (server filesystem / other share), still bounded by the login's own permissions.
Precedent
This is the same class as the already-fixed local backend advisory https://github.com/rclone/rclone/security/advisories/GHSA-7p4m-qxvv-g567, which added (*Fs).localPath returning errPathEscapes for names resolving outside the root (backend/local/local.go:819-826). That fix was justified because the destination was the operator's own OS filesystem; the same reasoning extends (at lower severity) to sftp/smb.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/rclone/rclone | all versions | 1.75.1go get github.com/rclone/rclone@v1.75.1 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/rclone/rclone, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/rclone/rclone to 1.75.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-88046 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This flaw requires an untrusted, flat-keyspace source object store, populated by tooling other than rclone, that contains a raw .. key segment. Syncs or copies from such sources should avoid destination backends that construct paths with path.Join(root, remote): b2, swift, qingstor, oracleobjectstorage, internetarchive, smb, storj, sftp, webdav, ftp, filelu, shade, and sia. Restricting untrusted-source syncs to destinations outside this list, or auditing source object keys for .. segments beforehand, prevents the path traversal until rclone is upgraded to 1.75.1.Source: Red Hat security advisory for CVE-2026-88046 (CC BY 4.0)
Frequently Asked Questions
Is CVE-2026-88046 in your dependencies?
Find it across Go, including transitive dependencies.