PT-2026-89502 · Go+2 · Github.Com/Rclone/Rclone+1
CVE-2026-88046
·
Published
2026-09-10
·
Updated
2026-09-11
CVSS v3.1
5.3
Medium
| Vector | AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N |
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.
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
bash
# 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.Exploit
Fix
Path traversal
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Github.Com/Rclone/Rclone
Rclone