This repository was archived by the owner on Aug 31, 2026. It is now read-only.
Repository navigation
fix: avoid recursive conflict checks for copy and move - #6009
Merged
hacdias merged 1 commit intoJul 4, 2026
Merged
Conversation
Issue: filebrowser#6005 Copy, move, paste, and drag-to-folder operations only need to detect conflicts against direct children of the selected destination. The previous preflight reused the recursive upload conflict scan and could walk large directory trees before sending the copy or move request, making the UI appear unresponsive. This change keeps recursive conflict detection for uploads, switches copy/move conflict checks to the regular destination listing, and preserves directory conflict handling for copy/move operations. Tests: - .\\node_modules\\.bin\\prettier.cmd --check src\\utils\\upload.ts src\\utils\\__tests__\\check-conflict.test.ts src\\components\\prompts\\__tests__\\copy-move-conflict.test.ts - .\\node_modules\\.bin\\vitest.cmd run src\\utils\\__tests__\\check-conflict.test.ts src\\components\\prompts\\__tests__\\copy-move-conflict.test.ts - .\\node_modules\\.bin\\vue-tsc.cmd -p .\\tsconfig.app.json --noEmit
hacdias
approved these changes
Jul 4, 2026
hacdias
left a comment
Member
There was a problem hiding this comment.
Yeah, I agree it doesn't make sense to check everything before, otherwise it can take a long time indeed.
aeltawela
added a commit
to aeltawela/filebrowser
that referenced
this pull request
Aug 22, 2026
* chore: update translations (filebrowser#5990) * fix: preserve SRT subtitle line breaks (filebrowser#6002) * fix: match admin share paths by owner scope (filebrowser#5992) * fix: normalize recursive listing paths to forward slashes (filebrowser#6003) * docs,cmd: warn about broad scope for self-signup users (GHSA-6759-996p-gpj6) When Signup is enabled with the default scope and createUserDir off, every self-registered user inherits the served root and can read/modify/delete all files. Add a startup WARNING for this configuration and document the risk and the --createUserDir mitigation. No behavior or default change. * fix(raw): neutralize backslashes in archive entry names (GHSA-83xp-526h-j3ww) The fix for CVE-2026-54093 rewrote backslashes to the path separator "/" in archive entry names. On POSIX hosts a backslash is a legal filename byte, so that rewrite manufactured a traversal sequence ("..\..\x" -> "../../x") out of a single in-scope file, turning a Windows-only zip-slip into a cross-platform one. Neutralize backslashes to an inert character instead, and reject any entry whose name is not already a normalized root-relative path. Adds a regression test that downloads a folder containing a backslash-named file as a zip. * fix(auth): reject signup when normalized home dir collides (GHSA-7rc3-g7h6-22m7) cleanUsername is many-to-one, so distinct usernames (e.g. "teamone/x" and "teamone-x") can normalize to the same home directory. With CreateUserDir enabled, the second registrant silently reused the first user's directory, breaking per-user isolation. Add a GetByScope lookup and reject a signup whose derived scope is already taken. The check is gated on CreateUserDir: when it is off, signups intentionally share the configured default scope. Adds a regression test for the colliding-username case. * fix(share): stop exposing password hash and bypass token in share API (GHSA-833g-cqhp-h72j) The share management endpoints serialized the storage struct directly, returning the bcrypt password_hash (crackable offline) and the bypass token for every share an authenticated caller could list, with admins seeing them for all users. Return a response DTO that exposes only whether a share is password-protected (hasPassword) and drops both secrets. The storage struct keeps its tags so the secrets stay persisted and the server-side auth/public flows are unchanged. Updates the frontend to use hasPassword and adds a regression test. * fix(share): delete exact directory share on trailing-slash delete (GHSA-pp88-jhwj-5qh5) DeleteWithPathPrefix queried the share index with the raw path, so deleting a directory through a trailing-slash path (e.g. DELETE /api/resources/a/) only matched descendants like /a/child and missed the exact /a share, leaving it in storage. If the same path was later recreated, the stale public share re-exposed the new content. Normalize the path before the prefix query so the exact share and its descendants are both removed. Adds a regression test. * chore(release): 2.63.17 * docs: warning about hook executor * fix(preview): keep the EPUB table-of-contents button clear of the header (filebrowser#6010) * fix: avoid recursive conflict checks for copy and move (filebrowser#6009) * chore: update dependencies * fix: deduplicate PT language * chore: update translations * chore(release): 2.63.18 * fix(http): run upload hooks for directories (filebrowser#6034) * chore: update translations (filebrowser#6019) Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> * fix: return error instead of panicking on an unreadable directory during copy (filebrowser#6020) Co-authored-by: Claude Fable 5 <[email protected]> * docs: fix typo * fix: process --FollowExternalSymlinks * fix(upload): handle encoded path conflicts safely (filebrowser#6040) * fix: accessibility and security improvements (filebrowser#6033) * chore: update translations * docs: update readme * docs: remove go report * fix(auth): isolate auto-provisioned proxy and hook users to their own home Proxy- and hook-authenticated users were auto-provisioned by applying the default scope (".") and passing it straight to MakeUserDir, which normalizes "." to "/". With CreateUserDir enabled, every provisioned user therefore received the server root as its scope instead of a per-user home directory, letting one user read, overwrite and delete another user's files. The signup handler already cleared the scope before deriving the home directory. Centralize that logic into Settings.CreateUserHome (clear the scope when CreateUserDir is on and no explicit scope was supplied, derive the home dir, then reject a scope already owned by another user) and use it from signup, proxy and hook auth so the three provisioning paths cannot diverge. Refs GHSA-j7jh-37pf-mf8h, GHSA-j2fc-28fx-hc8q * fix(storage): reject case-folded home directory collisions GetByScope compared scopes with a case-sensitive exact match, so on a case-insensitive filesystem two accounts whose scopes differ only in case (e.g. /users/CaseVictim and /users/casevictim) were treated as distinct even though both resolve to the same physical home directory. The second account could then read, overwrite and delete the first account's files. Match the scope case-insensitively so the collision check performed during signup, proxy and hook provisioning catches these aliases and rejects the duplicate. Refs GHSA-576v-w77m-gr84 * fix(http): enforce download permission on the checksum branch The ?checksum= branch of the resource GET handler read the whole file to compute its digest and returned it without checking Perm.Download, unlike the sibling raw, preview, subtitle and X-Encoding paths. A user provisioned with Perm.Download=false could therefore obtain md5/sha1/sha256/sha512 digests of files they are not allowed to download, giving a file-content hash oracle, and force a full-file read. Return 202 Accepted before computing the checksum when the user lacks Perm.Download, matching the other read paths. This completes the fix for CVE-2026-35606, whose original patch gated only the content-returning paths. Refs GHSA-7whw-q6gh-xr59 * fix(http): delete abandoned TUS uploads through the scoped filesystem The in-memory upload cache deleted expired incomplete uploads with a raw os.Remove on the absolute path it had stored, bypassing ScopedFs entirely. Because the stored path is only lexically cleaned (no symlink evaluation) and os.Remove resolves symlinked parent directories, a Create-only user could register an upload and then, within the 3-minute TTL, swap an in-scope ancestor directory for a symlink so the eviction deleted an arbitrary file outside their scope. Carry a removal callback with each cache entry and invoke it on eviction instead of os.Remove. For TUS uploads the callback deletes via the uploading user's scoped filesystem, whose Remove is guarded by the same within() check that CVE-2026-55667 added, so eviction can no longer follow a symlink out of scope. The redis backend ignores the callback (it never deleted partial files). Refs GHSA-m9f5-2232-frp6 * fix(http): enforce declared Upload-Length on TUS uploads The TUS PATCH handler copied the whole request body to disk and used the declared Upload-Length only to decide when the upload was complete. A client could declare a tiny Upload-Length and then stream an arbitrarily large body in a single PATCH, writing it all to disk, so any user with create permission could exhaust the filesystem. Bound each write to the bytes still expected (Upload-Length - Upload-Offset), reject a PATCH whose offset already exceeds the declared length, and roll back and reject a body that exceeds it. This complements the existing negative Upload-Length handling. Refs GHSA-ffv3-7h97-993q * test(auth): cover per-user scope isolation for proxy and hook provisioning Unit tests for Settings.CreateUserHome (derives a per-user home, rejects a colliding scope, preserves an explicit scope) plus proxy and hook regression tests asserting that with CreateUserDir enabled two provisioned users receive distinct home directories instead of the server root. Refs GHSA-j7jh-37pf-mf8h, GHSA-j2fc-28fx-hc8q * test(storage): cover case-insensitive GetByScope matching Refs GHSA-576v-w77m-gr84 * test(http): cover download-permission gate on the checksum branch Refs GHSA-7whw-q6gh-xr59 * test(http): cover scope-safe removal on TUS upload eviction Refs GHSA-m9f5-2232-frp6 * test(http): cover TUS Upload-Length enforcement Refs GHSA-ffv3-7h97-993q * chore(release): 2.63.19 * docs: update SECURITY.md and add CLAUDE.md * docs: update CLAUDE.md * docs: update CLAUDE instructions * fix: use aria-selected * fix(users): make the provisioned scope check atomic with the save The check that stops two usernames normalizing to the same home directory ran as a storage operation separate from the save, so two first-time users provisioned concurrently could both observe a free scope and both be saved into it, ending up sharing one home directory. Move the check into users.Storage.SaveProvisioned, which holds a single lock across the lookup and the save, and reduce CreateUserHome to deriving and creating the directory. This covers all three provisioning paths: signup, proxy auth and hook auth. Refs GHSA-j7jh-37pf-mf8h * test(users): cover concurrent provisioning into one scope Two usernames that normalize to the same home directory must not both be provisioned when their requests are handled concurrently. Also cover that an explicit, non-derived scope may still be legitimately shared. Refs GHSA-j7jh-37pf-mf8h * chore(release): 2.63.20 * chore(deps): update all non-major dependencies (filebrowser#5946) * Revert "chore(deps): update all non-major dependencies (filebrowser#5946)" This reverts commit 032271b. * chore: update Go deps * docs: update CLAUDE.md * fix(http): canonicalize paths before checking access rules (filebrowser#6045) * chore(release): 2.63.21 * chore: update renovate.json * chore(deps): update dependency dompurify to v3.4.12 [security] (filebrowser#6046) * chore: fix renovate.json order * chore(deps): update dependency postcss to v8.5.18 [security] (filebrowser#6047) * fix: unblock stalled and frozen uploads A TUS PATCH rejected before its body was read left the client still streaming a chunk. net/http gives up draining an unread request body after 256KiB and then closes the connection, so the status we replied with was lost and the browser saw a bare transport error instead. The client could not tell a conflict from a network fault, retried blindly, and the upload stalled after the first chunk — while curl, which reads the response as it writes, still reported the status and appeared to work. Drain what is left of a rejected chunk so the answer arrives on a connection that stays usable. The explicit r.Body.Close() had to go with it: it ran before the drain, and the server closes the request body itself once the handler returns. Pressing Upload awaited a recursive listing of the whole destination subtree before a single byte was sent, with no sign that anything was happening. On a large destination that reads as a dead button, and the walk kept running after the client gave up, then reported the failed write as a 500. A flat upload can only collide with a direct child of the destination, so walk recursively only for folder uploads, stop the walk once the request is done, and show the action as busy while the destination is checked. Along the way: a failed upload was credited as fully sent, so the progress bar reported 100% when nothing had been written; the progress timer was recreated without clearing the previous one; and the chunk size field threw on a value with no unit and raced its own debounce on submit, so it appeared to refuse every change. Fixes filebrowser#6006 Refs filebrowser#5987 * docs: add wind down notice * docs: fix date * fix: make the sidebar scrollable when content overflows (filebrowser#6031) * fix: enforce rules on recursive operations and expired proxy tokens (filebrowser#6053) * fix: use absolute URLs for the PWA manifest icon sources (filebrowser#6032) * chore: update translations * chore: remove renovate.json * chore(release): 2.63.22 * docs: clear inconsistencies, point at repo instead of website * docs: cleanup header * chore(release): 2.63.23 * docs: include issue summary in readme * docs: update post link --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> Co-authored-by: Rayan Salhab <[email protected]> Co-authored-by: JinHyuk Sung <[email protected]> Co-authored-by: Henrique Dias <[email protected]> Co-authored-by: Aditya Raj Singh <[email protected]> Co-authored-by: 林桉 <[email protected]> Co-authored-by: tianrking <[email protected]> Co-authored-by: TowyTowy <[email protected]> Co-authored-by: Claude Fable 5 <[email protected]> Co-authored-by: Archit Goyal <[email protected]> Co-authored-by: superafun <[email protected]> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Copy, move, paste, and drag-to-folder operations reused the recursive upload conflict scan before sending the copy or move request.
For large destination directories, especially on Docker/Unraid or network-backed storage, this could walk a large tree and make the UI appear unresponsive.
Fixes #6005.
Changes
Tests
.\node_modules\.bin\prettier.cmd --check src\utils\upload.ts src\utils\__tests__\check-conflict.test.ts src\components\prompts\__tests__\copy-move-conflict.test.ts.\node_modules\.bin\vitest.cmd run src\utils\__tests__\check-conflict.test.ts src\components\prompts\__tests__\copy-move-conflict.test.ts.\node_modules\.bin\vue-tsc.cmd -p .\tsconfig.app.json --noEmit