{"id":"GHSA-5q48-q4fm-g3m6","summary":"File Browser has an access rule bypass via HasPrefix without trailing separator in path matching","details":"Hi,\n\nThe `Matches()` function in `rules/rules.go` uses `strings.HasPrefix()` without a trailing directory separator when matching paths against access rules. A rule for `/uploads` also matches `/uploads_backup/`, granting or denying access to unintended directories. Verified against v2.62.2 (commit 860c19d).\n\n## Details\n\nAt `rules/rules.go:29-35`:\n\n    func (r *Rule) Matches(path string) bool {\n        if r.Regex {\n            return r.Regexp.MatchString(path)\n        }\n        return strings.HasPrefix(path, r.Path)\n    }\n\nWhen a rule has `Path: \"/uploads\"`, any path starting with `/uploads` matches, including `/uploads_backup/secret.txt`. The regex variant at line 31 uses proper matching, but the non-regex path uses a prefix check without ensuring the match ends at a directory boundary.\n\nThe `Check()` function at `http/data.go:29-48` iterates all rules with last-match-wins semantics. No secondary validation exists beyond this prefix check.\n\n## PoC\n\nAdmin configures: allow rule `Path: \"/shared\"` for a restricted user.\n\nFilesystem contains:\n- `/shared/` (intended to be accessible)\n- `/shared_private/` (intended to be restricted)\n\nUser requests `/shared_private/secret.txt`:\n- `strings.HasPrefix(\"/shared_private/secret.txt\", \"/shared\")` returns true\n- Allow rule applies\n- Access granted to the unintended directory\n\n## Impact\n\nAuthenticated users can access files in sibling directories that share a common prefix with an allowed directory, bypassing the admin's intended access configuration.\n\n## Prior art\n\nPrior advisories GHSA-4mh3-h929-w968 (path-based access control bypass) and GHSA-9f3r-2vgw-m8xp (path traversal in copy/rename) addressed related access control issues. This HasPrefix prefix-collision is a distinct, unreported variant.\n\n## Suggested Fix\n\n    func (r *Rule) Matches(path string) bool {\n        if r.Regex {\n            return r.Regexp.MatchString(path)\n        }\n        prefix := r.Path\n        if prefix != \"/\" && !strings.HasSuffix(prefix, \"/\") {\n            prefix += \"/\"\n        }\n        return path == r.Path || strings.HasPrefix(path, prefix)\n    }\n\nKoda Reef\n\n---\n\n**Update:** Fix submitted as PR #5889.","aliases":["CVE-2026-35605"],"modified":"2026-04-08T00:23:54.321431Z","published":"2026-04-08T00:04:49Z","database_specific":{"severity":"MODERATE","cwe_ids":["CWE-22"],"github_reviewed":true,"github_reviewed_at":"2026-04-08T00:04:49Z","nvd_published_at":"2026-04-07T17:16:34Z"},"references":[{"type":"WEB","url":"https://github.com/filebrowser/filebrowser/security/advisories/GHSA-5q48-q4fm-g3m6"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-35605"},{"type":"WEB","url":"https://github.com/filebrowser/filebrowser/pull/5889"},{"type":"PACKAGE","url":"https://github.com/filebrowser/filebrowser"}],"affected":[{"package":{"name":"github.com/filebrowser/filebrowser/v2","ecosystem":"Go","purl":"pkg:golang/github.com/filebrowser/filebrowser/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.63.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-5q48-q4fm-g3m6/GHSA-5q48-q4fm-g3m6.json"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"}]}