Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
261bca6
chore(deps): update all non-major dependencies (#5929)
renovate[bot] May 17, 2026
da2dff0
chore: sync translations
transifex-integration[bot] May 17, 2026
e38c282
chore: fix typo
hacdias May 17, 2026
9cc18a8
fix: show item shares from all users to admins (#5941)
mturac May 17, 2026
d978d1e
chore: sync translations (#5945)
transifex-integration[bot] May 17, 2026
a418dd6
chore: revert node dependencies updates
hacdias May 17, 2026
22b848f
chore(release): 2.63.4
hacdias May 17, 2026
6ad8160
fix(router): handle undefined catchAll param on root redirect (#5955)
jolcese May 21, 2026
a1e442e
chore(release): 2.63.5
hacdias May 21, 2026
ca0108f
chore: disable automatic major updates
hacdias May 21, 2026
34ae34e
fix: remove undocumented hook auth with shell replacement
hacdias Jun 3, 2026
0d3eb9b
docs: clarify hide dotfiles
hacdias Jun 3, 2026
e07c59d
fix: incorrect access control in public directory shares via rule pat…
hacdias Jun 3, 2026
0231b7e
fix: cross-user unauthorized share-link deletion
hacdias Jun 3, 2026
847d08b
fix: address three security disclosures (archive traversal, login DoS…
hacdias Jun 3, 2026
5328e80
fix: parse csv files with uneven columns in their rows (#5965)
ArielLeyva Jun 3, 2026
1036830
chore: Updates for project File Browser (#5947)
transifex-integration[bot] Jun 3, 2026
4edabb9
chore(docs): update CLI documentation
hacdias Jun 3, 2026
85b7d27
chore(release): 2.63.6
hacdias Jun 3, 2026
166583d
fix: disallow shares for non-existent paths
hacdias Jun 3, 2026
4488f5b
chore(release): 2.63.7
hacdias Jun 3, 2026
ca019ae
fix: check if share is within scope when creating
hacdias Jun 3, 2026
f5e0c4e
chore(release): 2.63.8
hacdias Jun 3, 2026
103acd1
fix: force octet-stream for attachment downloads (#5942)
mturac Jun 3, 2026
cdd666f
fix: prevent symlink scope escape in copy/move/rename
hacdias Jun 3, 2026
1951436
fix: use constant-time comparison for share access token
hacdias Jun 3, 2026
35db07d
fix: set X-Content-Type-Options: nosniff on raw file responses
hacdias Jun 3, 2026
503fd6b
chore(release): 2.63.9
hacdias Jun 3, 2026
6b04cbf
fix: allow writes when user scope resolves to filesystem root
hacdias Jun 3, 2026
69c76d1
chore(release): 2.63.10
hacdias Jun 3, 2026
3471ec2
fix: incomplete fix for symlinked directories let scopes users and pu…
hacdias Jun 4, 2026
1086903
chore(release): 2.63.11
hacdias Jun 4, 2026
7b7ff8a
fix: skip inaccessible children when listing directories (#5958)
puneetdixit200 Jun 4, 2026
0bb2768
fix: keep mobile file sort controls visible (#5977)
puneetdixit200 Jun 4, 2026
c1abe8f
fix: await copy move conflict detection (#5978)
puneetdixit200 Jun 4, 2026
998bd95
chore(release): 2.63.12
hacdias Jun 4, 2026
5f7311d
refactor: cleanup and simplify upload.ts
hacdias Jun 6, 2026
a1a514d
fix: copy/move allow overwrite
hacdias Jun 6, 2026
67ed670
chore(release): 2.63.13
hacdias Jun 6, 2026
3406d3d
fix: recursive check
hacdias Jun 7, 2026
7c2c0a1
refactor: ScopedFs to avoid escaping symlinks
hacdias Jun 7, 2026
d9816b1
chore: add symlink tests
hacdias Jun 7, 2026
dfe6e5b
chore(release): 2.63.14
hacdias Jun 7, 2026
d6e21ab
merge upstream/master into oadp-dev
oadp-maintainers Jun 8, 2026
3f474d4
UPSTREAM: <carry> Add disableUserProfile branding option
mpryc Oct 18, 2025
25c577a
UPSTREAM: <carry> Add defaultLoginUser branding option
mpryc Oct 18, 2025
435acf5
UPSTREAM: <carry> Disable delete and rename from Help
mpryc Oct 19, 2025
53bf7d5
UPSTREAM: <carry> Containerfile to be used with UBI
mpryc Nov 12, 2025
561998e
UPSTREAM: <carry> Add OWNERS file
mpryc Feb 5, 2026
ba68e79
UPSTREAM: <carry> Fix broken rebase of branding options
mpryc Feb 9, 2026
a7a33ec
UPSTREAM: <carry>: fix github actions
mpryc Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,102 @@

All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.

## [2.63.14](https://github.com/filebrowser/filebrowser/compare/v2.63.13...v2.63.14) (2026-06-07)


### Bug Fixes

* recursive check ([3406d3d](https://github.com/filebrowser/filebrowser/commit/3406d3d7f98dfc3c16e4ff7ff4a87e3bdfe221dd))


### Refactorings

* ScopedFs to avoid escaping symlinks ([7c2c0a1](https://github.com/filebrowser/filebrowser/commit/7c2c0a11b31b2bb214d741005a0b02b1764208b3))

## [2.63.13](https://github.com/filebrowser/filebrowser/compare/v2.63.12...v2.63.13) (2026-06-06)


### Bug Fixes

* copy/move allow overwrite ([a1a514d](https://github.com/filebrowser/filebrowser/commit/a1a514dcbb216d2080412c5354eea1e1fb033050))


### Refactorings

* cleanup and simplify upload.ts ([5f7311d](https://github.com/filebrowser/filebrowser/commit/5f7311d32437e98d7c14c7b307a4f68109275535))

## [2.63.12](https://github.com/filebrowser/filebrowser/compare/v2.63.11...v2.63.12) (2026-06-04)


### Bug Fixes

* await copy move conflict detection ([#5978](https://github.com/filebrowser/filebrowser/issues/5978)) ([c1abe8f](https://github.com/filebrowser/filebrowser/commit/c1abe8f561208bf36bde70879d1a15ef9de998fa))
* keep mobile file sort controls visible ([#5977](https://github.com/filebrowser/filebrowser/issues/5977)) ([0bb2768](https://github.com/filebrowser/filebrowser/commit/0bb2768754d11b865d68e72dcd7cebb232a6308a))
* skip inaccessible children when listing directories ([#5958](https://github.com/filebrowser/filebrowser/issues/5958)) ([7b7ff8a](https://github.com/filebrowser/filebrowser/commit/7b7ff8ae8f97393b2e6ae6e061c1f780077c32b6))

## [2.63.11](https://github.com/filebrowser/filebrowser/compare/v2.63.10...v2.63.11) (2026-06-04)


### Bug Fixes

* incomplete fix for symlinked directories let scopes users and public-share recipients read and write files outside of scope ([3471ec2](https://github.com/filebrowser/filebrowser/commit/3471ec2c4b6473831c72ee889cb3c1a6849a1fb1))

## [2.63.10](https://github.com/filebrowser/filebrowser/compare/v2.63.9...v2.63.10) (2026-06-03)


### Bug Fixes

* allow writes when user scope resolves to filesystem root ([6b04cbf](https://github.com/filebrowser/filebrowser/commit/6b04cbf5e9db1f5b9c0b1624843607ce2881cfc4))

## [2.63.9](https://github.com/filebrowser/filebrowser/compare/v2.63.8...v2.63.9) (2026-06-03)


### Bug Fixes

* force octet-stream for attachment downloads ([#5942](https://github.com/filebrowser/filebrowser/issues/5942)) ([103acd1](https://github.com/filebrowser/filebrowser/commit/103acd15fe57554fe0246bfe70a49b6cb4ae0c51))
* prevent symlink scope escape in copy/move/rename ([cdd666f](https://github.com/filebrowser/filebrowser/commit/cdd666fc95f569ad583c32391e45646fed676dfd))
* set X-Content-Type-Options: nosniff on raw file responses ([35db07d](https://github.com/filebrowser/filebrowser/commit/35db07d0159c520a6b3c969ac52033593914fadd))
* use constant-time comparison for share access token ([1951436](https://github.com/filebrowser/filebrowser/commit/19514367adf2d9fe5be2b7666e397979ea679b94))

## [2.63.8](https://github.com/filebrowser/filebrowser/compare/v2.63.7...v2.63.8) (2026-06-03)


### Bug Fixes

* check if share is within scope when creating ([ca019ae](https://github.com/filebrowser/filebrowser/commit/ca019ae7d966a7c28de2b2341271cd13e3458ae6))

## [2.63.7](https://github.com/filebrowser/filebrowser/compare/v2.63.6...v2.63.7) (2026-06-03)


### Bug Fixes

* disallow shares for non-existent paths ([166583d](https://github.com/filebrowser/filebrowser/commit/166583db632e088e9f0adce30aec43bb9d9019f4))

## [2.63.6](https://github.com/filebrowser/filebrowser/compare/v2.63.5...v2.63.6) (2026-06-03)


### Bug Fixes

* address three security disclosures (archive traversal, login DoS, symlink escape) ([847d08b](https://github.com/filebrowser/filebrowser/commit/847d08bdd135e5c3659f2e6dea2f0cd36617af9b))
* cross-user unauthorized share-link deletion ([0231b7e](https://github.com/filebrowser/filebrowser/commit/0231b7ebdfbe77a6c54027d30c4856c3fd81ee4d))
* incorrect access control in public directory shares via rule path rebasing ([e07c59d](https://github.com/filebrowser/filebrowser/commit/e07c59df0b850f5924d5b1683e8609661ddcf534))
* parse csv files with uneven columns in their rows ([#5965](https://github.com/filebrowser/filebrowser/issues/5965)) ([5328e80](https://github.com/filebrowser/filebrowser/commit/5328e80d2e88d1c279a1250a7dfee4fc96f703ec))
* remove undocumented hook auth with shell replacement ([34ae34e](https://github.com/filebrowser/filebrowser/commit/34ae34e764d72540c039f1f5ea2ec4c974168c1f))

## [2.63.5](https://github.com/filebrowser/filebrowser/compare/v2.63.4...v2.63.5) (2026-05-21)


### Bug Fixes

* **router:** handle undefined catchAll param on root redirect ([#5955](https://github.com/filebrowser/filebrowser/issues/5955)) ([6ad8160](https://github.com/filebrowser/filebrowser/commit/6ad8160aa3309314c1b471c5090b67c824464396))

## [2.63.4](https://github.com/filebrowser/filebrowser/compare/v2.63.3...v2.63.4) (2026-05-17)


### Bug Fixes

* show item shares from all users to admins ([#5941](https://github.com/filebrowser/filebrowser/issues/5941)) ([9cc18a8](https://github.com/filebrowser/filebrowser/commit/9cc18a81e3e1b8bf96795bfbe3d83ced294ecfd7))

## [2.63.3](https://github.com/filebrowser/filebrowser/compare/v2.63.2...v2.63.3) (2026-05-05)


Expand Down
16 changes: 0 additions & 16 deletions auth/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,6 @@ func (a *HookAuth) LoginPage() bool {
// RunCommand starts the hook command and returns the action
func (a *HookAuth) RunCommand() (string, error) {
command := strings.Split(a.Command, " ")
envMapping := func(key string) string {
switch key {
case "USERNAME":
return a.Cred.Username
case "PASSWORD":
return a.Cred.Password
default:
return os.Getenv(key)
}
}
for i, arg := range command {
if i == 0 {
continue
}
command[i] = os.Expand(arg, envMapping)
}

cmd := exec.Command(command[0], command[1:]...)
cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
Expand Down
88 changes: 88 additions & 0 deletions auth/hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package auth

import (
"os"
"path/filepath"
"runtime"
"testing"
)

// writeHookScript writes a POSIX shell script to a temp file and returns its
// path, marking it executable.
func writeHookScript(t *testing.T, body string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "hook.sh")
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0o700); err != nil {
t.Fatalf("failed to write hook script: %v", err)
}
return path
}

// TestRunCommandNoCredentialInjection ensures that attacker-controlled
// credentials submitted at the unauthenticated login endpoint cannot be
// injected into the hook command string. Credentials must only ever reach the
// hook through the USERNAME/PASSWORD environment variables, never via string
// substitution into the command itself (CWE-78/CWE-88).
func TestRunCommandNoCredentialInjection(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("uses POSIX shell")
}

marker := filepath.Join(t.TempDir(), "pwned")

// The hook simply blocks. If the credential were ever interpolated into the
// command string and evaluated by a shell, the embedded `touch` would
// create the marker file.
script := writeHookScript(t, "echo hook.action=block\n")

a := &HookAuth{
Command: script,
Cred: hookCred{
Username: `"; touch ` + marker + `; #`,
Password: `$(touch ` + marker + `)`,
},
}

action, err := a.RunCommand()
if err != nil {
t.Fatalf("RunCommand returned error: %v", err)
}
if action != "block" {
t.Fatalf("expected action %q, got %q", "block", action)
}
if _, err := os.Stat(marker); err == nil {
t.Fatalf("credential injection executed: marker file %q was created", marker)
}
}

// TestRunCommandReceivesCredentialsViaEnv verifies the supported contract: the
// hook receives credentials through the USERNAME and PASSWORD environment
// variables.
func TestRunCommandReceivesCredentialsViaEnv(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("uses POSIX shell")
}

script := writeHookScript(t, `if [ "$USERNAME" = alice ] && [ "$PASSWORD" = secret ]; then
echo hook.action=auth
else
echo hook.action=block
fi
`)

a := &HookAuth{
Command: script,
Cred: hookCred{
Username: "alice",
Password: "secret",
},
}

action, err := a.RunCommand()
if err != nil {
t.Fatalf("RunCommand returned error: %v", err)
}
if action != "auth" {
t.Fatalf("expected action %q, got %q", "auth", action)
}
}
2 changes: 1 addition & 1 deletion cmd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.Bool("singleClick", false, "use single clicks only")
flags.Bool("redirectAfterCopyMove", false, "redirect to destination after copy/move")
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
flags.Bool("hideDotfiles", false, "hide dotfiles")
flags.Bool("hideDotfiles", false, "hide dotfiles in file listings")
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
}

Expand Down
75 changes: 67 additions & 8 deletions files/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import (
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"time"

"github.com/spf13/afero"

fberrors "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero"
)

var (
Expand Down Expand Up @@ -391,8 +391,7 @@ func (i *FileInfo) addSubtitle(fPath string) {
}

func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRes bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
dir, err := readDir(i.Fs, i.Path)
if err != nil {
return err
}
Expand All @@ -414,12 +413,19 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRe
isSymlink, isInvalidLink := false, false
if IsSymlink(f.Mode()) {
isSymlink = true
// It's a symbolic link. We try to follow it. If it doesn't work,
// we stay with the link information instead of the target's.
// It's a symbolic link. We try to follow it. The scoped filesystem
// refuses to dereference a link whose target escapes the scope
// (permission error); such a link is omitted from the listing
// entirely so it cannot leak the target's metadata. Any other
// failure means a broken link, which we surface as an invalid link
// rather than the target's information.
info, err := i.Fs.Stat(fPath)
if err == nil {
switch {
case err == nil:
f = info
} else {
case errors.Is(err, os.ErrPermission):
continue
default:
isInvalidLink = true
}
}
Expand Down Expand Up @@ -467,3 +473,56 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRe
i.Listing = listing
return nil
}

func readDir(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
dir, err := afero.ReadDir(afs, dirname)
if err == nil {
return dir, nil
}

dir, fallbackErr := readDirNames(afs, dirname)
if fallbackErr != nil {
return nil, err
}

return dir, nil
}

func readDirNames(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
file, err := afs.Open(dirname)
if err != nil {
return nil, err
}

names, err := file.Readdirnames(-1)
if closeErr := file.Close(); err == nil && closeErr != nil {
err = closeErr
}
if err != nil {
return nil, err
}

sort.Strings(names)
dir := make([]os.FileInfo, 0, len(names))
for _, name := range names {
fPath := path.Join(dirname, name)
info, err := lstatIfPossible(afs, fPath)
if err != nil {
log.Printf("Skipping inaccessible file %s: %v", fPath, err)
continue
}

dir = append(dir, info)
}

return dir, nil
}

func lstatIfPossible(afs afero.Fs, name string) (os.FileInfo, error) {
if lstaterFs, ok := afs.(afero.Lstater); ok {
info, _, err := lstaterFs.LstatIfPossible(name)
return info, err
}

return afs.Stat(name)
}
Loading