test: cover Utils.safe_filename and .safe_filename? - #23773
Merged
Conversation
Neither had any coverage, and both gate attacker-influenced basenames: the cask cleanup path feeds them File.basename of a URL, and the download strategies build cache paths from the result. Pins what the regex actually strips (control characters and path separators) and, for the case that reads like a gap, that a bare ".." is left alone while "../etc" is rejected.
Member
|
Thanks! |
MikeMcQuaid
approved these changes
Sep 4, 2026
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.
brew benchmarkresults.brewcommands to reproduce the bug?brew lgtm(style, typechecking and tests) locally?Tests only, no behaviour change.
Utils.safe_filenameandUtils.safe_filename?had no coverage at all, and both sit on paths where the input is not fully ours:Cleanup#cleanup_caskpassesFile.basenameof a cask URL throughsafe_filename, andVCSDownloadStrategy/CurlGitHubPackagesDownloadStrategybuild cache paths out of the result. A sanitiser with no tests is one refactor away from quietly widening.Why I bothered: reading it,
SAFE_FILENAME_REGEXlooked like it might be missing relative-path handling, sinceFile.basename("http://example.com/../")really does return"..". It is not a hole - every caller that builds a path prefixes the value ("#{name}--","#{Digest::SHA256.hexdigest(url)}--"), and the one caller that does not only uses it for astart_with?comparison, so a bare".."can never be a whole path component. That reasoning is exactly what was not written down anywhere, so the specs now pin it:safe_filename?("..")istrue,safe_filename?("../etc")isfalse.The rest is the current contract: control characters and path separators are stripped, everything else survives, and the output of
safe_filenamealways satisfiessafe_filename?.The unticked box is the bug-fix one - there is no bug here to reproduce, and I did not change
utils.rb.Verification:
brew lgtmpasses locally (style, typechecking,brew tests --changed), andbrew tests --only=utilsis green. To check the specs actually bite, I replacedbasename.gsub(SAFE_FILENAME_REGEX, "")withbasenameand both newsafe_filenameexamples failed, then restored it.AI/LLM disclosure: I used Claude Code (Claude Opus 5) to audit the helper and draft these specs, and reviewed the result; no commit is attributed to AI. I will answer review questions myself.