Another in the series of redacted-format engagement reports, this one on Hidden Bastard, a macOS junk-file eliminator that scans for caches, build artifacts, and stale developer cruft and moves what it finds to Trash. A tool whose entire premise is deciding what's safe to delete is also a tool where the review should specifically go looking for the one case where that decision is wrong. That's the target here: not the scanner's junk-detection accuracy, but the logic standing between "found it" and "it's in the Trash."
Scope and Objective
Target: FileScanner.swift's risk classification for the deep "biggest folders" scan, AppState.swift's selection logic that decides what gets acted on, and CleaningRulesEngine.swift's background automated-rule executor.
Objective: read the actual deletion path end to end rather than the README's description of it, and check whether every route into "files get removed" actually honors the same safety tiering the app advertises. No test suite exists in the repo, so verification meant extracting the real decision logic out of the source and running it, unmodified, against real fixture files in a disposable /tmp directory, never against anything live.
Findings
1. SELECT ALL bypassed the app's own risk gate (High)
Hidden Bastard's deep scan walks ~/Library for large folders no hardcoded path list would catch, then classifies each one by how safe it looks. classifyBigFolder() in FileScanner.swift is reasonable: known cache and build-artifact path signatures get tagged .low or .medium risk, and anything big and unrecognized defaults to "Large App Data" at .high risk. The app's own category description for that tier is explicit: "this also holds real data like profiles and logins. Review each item before deleting." The README repeats the promise in its own words: unfamiliar big folders are "flagged review-first" and "never auto-selected."
That promise held for exactly one of the two places selection happens. After a scan finishes, both scannerDidFinishScan() and the completion handler in startScan() set the initial selection with problemFiles.filter { $0.riskLevel == .low }, so high-risk items correctly start unchecked. But the SELECT ALL button in FileListView called AppState.selectAll(), and that function was:
func selectAll() {
selectedFileIds = Set(problemFiles.map { $0.id })
}
No risk filter at all. One click selected every item on screen, high-risk Large App Data folders included, the exact category the app's own documentation says can hold a browser profile or login database. There was no confirmation dialog on MOVE TO TRASH either, and ContentView.swift's file list row has zero references to riskLevel: no color, no badge, nothing visually distinguishing "safe cache" from "might be your Chrome profile" beyond a line of description text styled identically to every other category. SELECT ALL, then MOVE TO TRASH, two clicks, would have caught anything the deep scan surfaced.
2. The Rules Engine's "Delete" action bypassed Trash entirely (Medium)
CleaningRulesEngine.swift runs user-defined automated rules on a schedule (hourly through monthly) via a background Timer, with no human in the loop at execution time. It fires silently and posts a notification afterward. RuleEditorView.swift lets a user build a custom rule and pick an action from RuleAction.allCases, which includes .delete. The executor's switch statement handled that case with FileManager.default.removeItem(atPath: file), a real, permanent delete, not trashItem(). The repo's own shipped default rule, "Clean Application Caches," was configured with action: .delete out of the box (disabled by default, but present as the template a user sees first).