CVE-2026-88046
ADVISORY - githubSummary
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.
Sign in to Docker Scout
See which of your images are affected by this CVE and how to fix them by signing into Docker Scout.
Sign in