fix(storage): bound Local recursive directory walks - #102
Open
loks0n wants to merge 1 commit into
Open
Conversation
`getDirectorySize()`, `delete($path, recursive: true)` and `deletePath()` each walked a tree with no iteration or time budget, so a multi-TB tenant directory held a request worker for minutes — a DoS vector on usage accounting and admin endpoints. All three now route through one lazy `walk()` helper over `RecursiveIteratorIterator`, capped at `maxEntries` (default 100k, settable on the constructor so background jobs can raise it). Past the cap the walk throws a `StorageException` naming the directory instead of running to completion. Collapsing the three hand-rolled recursions into the shared helper removes more code than it adds. `listFiles()` keeps its own traversal: it already paginates, so bounding it would change documented API behaviour rather than fix a walk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes CLO-4269 (remaining scope — the path-concat half shipped in
storage/3.1.0).Problem
Local::getDirectorySize(),Local::delete($path, recursive: true)andLocal::deletePath()each recursed through a directory tree with no iteration or time budget. On a multi-TB tenant directory a single call pins a PHP worker for minutes, which makes usage-accounting and admin endpoints a DoS vector.Change
All three route through one lazy
walk()helper overRecursiveIteratorIterator, capped atmaxEntries— default 100,000, settable on the constructor so background jobs can raise it (new Local($root, maxEntries: PHP_INT_MAX)). Past the cap the walk throws aStorageExceptionnaming the directory, instead of running to completion.Collapsing three hand-rolled recursions into the shared helper removes more code than it adds:
deletePath()becomes a resolve-then-delete()one-liner, andgetDirectorySize()loses itsopendir/readdirloop.listFiles()keeps its own traversal on purpose — it already paginates, so bounding it would change documented API behaviour rather than fix an unbounded walk.Notes
getDirectorySize()returns-1for a non-directory as before; the empty-path guard is unchanged.unlinkrather thanrmdir. Descent behaviour is otherwise unchanged from thescandirversion.Testing
bin/monorepo check storage— pint, PHPStan, Rector cleanmaxEntries: 1over a two-file directory throws)vale packages/storage/README.mdclean🤖 Generated with Claude Code