Releases: cacheout-app/cacheout
Release list
Cacheout v2.1.7
Security
DaemonModePID file is no longer susceptible to a symlink-swap overwrite. The daemon created its PID file withopen(O_WRONLY | O_CREAT | O_CLOEXEC)— withoutO_NOFOLLOW, an attacker with write access to the parent directory could plant a symlink at the PID path and redirect the open to truncate or overwrite an arbitrary file. Now opens withO_NOFOLLOWand an explicit0o600mode so the kernel refuses to follow a symlink at the final component. (#416)StatusSocketPOSIX path bridging hardened. Theopen(2)calls inStatusSocket.swift(state-directory hardening and config-validation read) now bridge their paths throughURL(fileURLWithPath:).withUnsafeFileSystemRepresentationinstead of passing SwiftStrings directly, bringing both call sites in line with the project's path-bridging standard. (#417)
Changed
SysctlJournalstale-entry and rollback index maps now build viareduce(into:)overstate.entries.indicesinstead of incrementalfor-loop mutation. Behavior-preserving cleanup ofrevertStaleEntries()andperformRollback(). (#414)
UX / Accessibility
- Check For Updates button now carries a
.help()tooltip that explains why it is disabled when update checking is unavailable (e.g. no appcast), removing ambiguity around the greyed-out control. (#415)
Cacheout v2.1.6
Maintenance release. Lands a CRITICAL TOCTOU security fix on the daemon state-directory setup (distinct from v2.1.5's chmod fix on the config-reload path), plus an a11y polish and a perf cleanup on the daemon hot path.
Security
DaemonModestate-directory permissions are no longer settable via symlink swap. The daemon hardens its state directory (~/Library/Application Support/Cacheoutor--state-dir) by chmod'ing it to0o700aftercreateDirectory— but the previousFileManager.setAttributes(ofItemAtPath:)follows symlinks, so an attacker with write access to the parent could swap the directory for a symlink between the two calls and redirect the chmod. Now opens the directory withopen(O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)and appliesfchmod(0o700)to the resulting descriptor — the kernel can't be tricked into chmod'ing somewhere else. Distinct from v2.1.5's #346 which fixed a different chmod site on the config-reload path. (#410)
Changed
RecommendationEnginerecommendation loops now usefor proc in scanResult.processes where conditioninstead ofscanResult.processes.filter { ... }.forEach. Avoids two intermediate array allocations on every recommendation pass — runs on the daemon hot path, so the saving compounds at long-running-daemon scale. (#407)
UX / Accessibility
- Disk-usage bar and MenuBar stat pills now read as single coherent items in VoiceOver. Added
.accessibilityElement(children: .combine)at the outermost modifier position onDiskUsageBar's outer frame and onMenuBarView'sstatPillso the entire visible card announces together. (#409)
Install
Download Cacheout-2.1.6.dmg below, or via Homebrew:
brew upgrade --cask cacheoutCacheout v2.1.5
Maintenance release. Lands the DaemonMode TOCTOU security fix that was deferred from v2.1.4 due to a merge conflict, plus two small polish PRs.
Security
DaemonModeconfig load no longer susceptible to chmod/read TOCTOU. The previous path-basedchmod(path, 0o600)followed byFileManager.contents(atPath:)could be redirected by a symlink swap between the two calls — an attacker with write access to the directory could trick the daemon into chmod'ing or reading an arbitrary file. Now opens the config withopen(O_RDONLY | O_NOFOLLOW | O_CLOEXEC)once, appliesfchmod(0o600)to the resulting descriptor, and reads viaFileHandle.readToEnd(). Both the permission change and the read happen on the same fd so the kernel can't be tricked into operating on a substituted path. (#346)
Changed
CacheoutViewModel.selectedSizeusesscanResults.lazy.filter(\.isSelected)instead of materializingselectedResultsto compute a sum — saves the intermediate array allocation on every recomputation.CacheoutViewModel.hasSelectionusescontains(where:)on bothscanResultsandnodeModulesItemsinstead of comparingselectedNodeModulesSize > 0, which previously forced a fullreduceovernodeModulesItemsjust to check whether any item was selected. Now short-circuits on the first match. (#404)
UX / Accessibility
- Memory dashboard stat cards are now read as a single coherent item by VoiceOver. Added
.accessibilityElement(children: .combine)to the two stat-card views inMemoryViewso the title and value announce together. (#401)
Install
Download Cacheout-2.1.5.dmg below, or via Homebrew:
brew upgrade --cask cacheoutCacheout v2.1.4
Maintenance release consolidating the TOCTOU/symlink security hardening landed since v2.1.3, plus a handful of accessibility and performance improvements.
Security
SysctlJournalno longer leaves the journal temp file world-readable during the umask window. The previousData.write(to:)+setAttributes(0o600)sequence created the file under the process's default umask before tightening permissions. Now uses POSIXopen(O_CREAT | O_WRONLY | O_EXCL | O_CLOEXEC, 0o600)so the file is born with the right mode and never observable to other local users. (#396)CacheCleanercleanup log is no longer vulnerable to TOCTOU symlink attacks.FileHandle(forWritingTo:)and theString.write(to:atomically:)fallback both follow symlinks. Replaced withopen(O_CREAT | O_WRONLY | O_APPEND | O_NOFOLLOW | O_CLOEXEC, 0o600); the log directory is also explicitly created0o700. (#391)StatusSockethardened against directory symlink swaps andvalidate_configfile swaps. Directory chmod now usesopen(O_NOFOLLOW | O_DIRECTORY) + fchmodinstead of path-basedsetAttributes. Post-bind verificationlstats the socket path and refuses to start if it isn'tS_IFSOCK.handleValidateConfigopens withO_NOFOLLOW | O_CLOEXEConce,fstats the fd for type + size, and reads from the fd with a hard byte cap — closes thelstat → Data(contentsOf:)window where a swap could bypass the size cap orS_IFREGcheck. (#397)
Changed
- Lazy-filter
.countin MenuBarView and CleanConfirmation to avoid allocating intermediate arrays just to take a count. Minor allocator pressure win in views that re-render frequently. (#393)
UX / Accessibility
- Disabled-state tooltips on MenuBar buttons (Scan, Quick Clean, Docker Prune) —
.help()strings explain why each is disabled (in-progress, nothing to clean, etc.) instead of leaving the user to guess. (#379) - Docker Prune button in Settings now shows inline "Pruning…" text alongside the spinner and gets a
.help()tooltip explaining the action. (#263) NodeModulesSectionheader announces expanded/collapsed state to VoiceOver via.accessibilityValue. (#394)
Install
Download Cacheout-2.1.4.dmg below, or via Homebrew once the cask bumps:
brew upgrade --cask cacheoutCacheout v2.1.2
Menubar icon hotfix
v2.1.1 correctly bundled menubar icon resources into the .app, but the source PNGs in Sources/Cacheout/Resources/ were a faint single-line "C" outline that rendered effectively invisible at 18×18 in template mode. The actual master artwork had been dropped from the repo at some point.
Fixed
- Restored
Resources/menubar-icon-master.PNG(the 2048×2048 master) and regenerated all four bundled variants —MenuBarIcon.png,MenuBarIcon@2x.png,MenuBarIconTemplate.png,MenuBarIconTemplate@2x.png— at the correct 18×18 / 36×36 sizes from the master. The Cacheout "C" now actually appears in the menubar.
Everything else from v2.1.0 + v2.1.1 (Sparkle auto-update bundling, helper daemon, security/perf/UX fixes) is unchanged.
Install
brew upgrade --cask cacheoutOr download Cacheout-2.1.2.dmg below and replace your /Applications/Cacheout.app.
Full Changelog: v2.1.1...v2.1.2
Cacheout v2.1.1
Hotfix for v2.1.0
v2.1.0 shipped with two regressions: the menubar icon resources and the CacheoutHelper privileged daemon were silently dropped from the .app bundle. v2.1.1 restores both.
Fixed
- Menubar icon now displays again.
Bundle.main.image(forResource: "MenuBarIconTemplate")couldn't find the PNG resources becausescripts/bundle.shwas only copyingCacheout.icns. Now copies all four icon variants (regular +@2x, template + non-template) intoContents/Resources/. CacheoutHelperdaemon is now bundled atContents/Library/LaunchDaemons/. TheSMAppService.daemon(plistName:)registration relies on the binary + plist being inside the app bundle. Without them, the onboarding install-helper flow and--daemonautopilot silently broke. The helper is now signed before the outer app socodesign --verify --deep --strictpasses.
Everything else in v2.1.0 (Sparkle auto-update bundling, HTTPS webhook enforcement, process pipe + PID-lock-fd-leak fixes, dictionary/scanner perf work, dynamic button labels + tooltips, empty states, accessibility) is unchanged.
Install
brew install --cask cacheout-app/cacheout/cacheoutOr download Cacheout-2.1.1.dmg below and drag to /Applications.
Full Changelog: v2.1.0...v2.1.1
Cacheout v2.1.0
⚠️ Known issue — please install v2.1.1 insteadv2.1.0 is missing the custom menubar icon resources and the
CacheoutHelperdaemon binary from the.appbundle (ascripts/bundle.shregression). The menubar item can appear blank, and the install-helper onboarding flow /--daemonautopilot silently break. Both issues are fixed in v2.1.1 ↘https://github.com/cacheout-app/cacheout/releases/tag/v2.1.1
What's new in v2.1.0
🚀 Auto-updates land
Sparkle.framework is now bundled inside Cacheout.app/Contents/Frameworks/ with the correct @executable_path/../Frameworks rpath, signed inner→outer with hardened runtime. Starting from this build, future releases will be installable via in-app auto-update instead of manual reinstall.
🎨 UX polish (Palette)
- Dynamic
Scanning…/Cleaning…labels on the Scan/Clean buttons with.help()tooltips explaining disabled states .help()tooltips on per-process ellipsis menus- Visual empty state for the
node_modulessection (centered icon + callout) - Accessibility labels on icon-only controls so VoiceOver stops reading "Button"
- Empty state for the Processes view
🛡️ Security & reliability (Sentinel)
- HTTPS-only webhooks —
AutopilotConfigValidatorandWebhookConfig.parseboth rejecthttp:// - PID lock fd no longer leaks to children —
open(2)now usesO_CLOEXECandwithUnsafeFileSystemRepresentation - Process pipe deadlock fix —
CacheoutViewModel.runCleanCommand(docker prune path) reads stdout beforewaitUntilExit() - Defense-in-depth pipe-deadlock guards across other process execution paths
- Command injection vulnerabilities closed in
runCleanCommandandCacheCategory.toolExists - Docker pruning now resolves via
/usr/bin/env dockerso Apple Silicon/opt/homebrew/binusers actually run it
⚡ Performance (Bolt)
JetsamHWMpriority map built withreduce(into:)— no COW thrashProcessMemoryScanneruses a sliding-window TaskGroup- Scanner TaskGroups marked
nonisolatedso subtasks parallelize off the actor executor - Shared
ByteCountFormatter/ISO8601DateFormatterinstances +.lazy.filter.reduce - Batched
@Publishedarray mutations — one SwiftUI re-render per scan instead of N CacheScannerenumerator prefetches.isRegularFileKeyNodeModulesScanneris truly concurrent with reduced syscalls- Blocking I/O offloaded via
Task.detachedso async@MainActormethods no longer stall the UI
🐛 Bug fixes
- Process names no longer display as "unknown" in the main process list
- Guard against empty arguments in
runCleanCommand
Install
brew install --cask cacheout-app/cacheout/cacheoutOr download Cacheout-2.1.0.dmg below and drag to /Applications.
Full Changelog: v2.0.0...v2.1.0
Cacheout v2.0.0
Cacheout v2.0.0
Developer cache cleaner & memory manager for macOS.
Features
- SSD Cache Cleaning — scan and clean 12 cache categories (Xcode, npm, pip, Homebrew, etc.)
- Memory Pressure Monitoring — real-time telemetry with compressor tracking
- Predictive Intelligence — memory exhaustion warnings before you run out
- Intervention Engine — tiered memory recovery (purge, jetsam, sysctl tuning)
- Privileged Helper — optional helper for advanced memory management
- Headless Daemon Mode — long-running monitoring with Unix socket API
- Autopilot Rules — automated actions with webhook/Telegram alerts
- Docker System Prune — one-click Docker cleanup
- CLI Interface — full JSON output for scripting and automation
- MCP Server — available separately via
pip install cacheout-mcp
Install
Download: Cacheout-2.0.0.dmg (signed & notarized)
Homebrew:
brew install --cask cacheout-app/tap/cacheoutRequirements
- macOS 14 (Sonoma) or later
- Apple Silicon or Intel
License
Sustainable Use License v1.0