Labels: bug, security
Summary
CleaningEngine.cleanItems() and ScanEngine.scanDirectory() do not resolve symlinks before deleting files. A symlink planted in any scanned directory (e.g. ~/Library/Caches/) can redirect deletion to arbitrary user-accessible paths like ~/.ssh/, ~/Documents/, or ~/.gnupg/.
Steps to reproduce
# 1. Create a symlink in a scanned cache directory
ln -s ~/.ssh ~/Library/Caches/fake-cache-entry
# 2. Run PureMac → Smart Scan
# 3. The symlink appears as a scannable cache item (auto-selected)
# 4. Click "Clean" → ~/.ssh is deleted
Impact
Any process running as the current user — malware, a rogue npm postinstall script, or even a cron job — can plant symlinks before a scheduled auto-clean runs. Combined with autoClean: true in SchedulerService, this becomes a zero-interaction attack that silently destroys targeted files.
Affected code
Services/CleaningEngine.swift:31 — removeItem(atPath:) follows symlinks
Services/ScanEngine.swift:302-347 — scanDirectory() doesn't check for symlinks
Suggested fix
Before any deletion, resolve the real path and verify it's still inside the expected scan root:
// Add to CleaningEngine, call before every removeItem
private func isSafeToDelete(path: String, allowedRoots: [String]) -> Bool {
let resolved = URL(fileURLWithPath: path)
.resolvingSymlinksInPath()
.path
// Reject if the resolved path escapes all allowed roots
return allowedRoots.contains { root in
resolved.hasPrefix(root)
}
}
Each scan category already knows its root paths — pass them through to the cleaning engine and validate before deletion.
Additionally, in scanDirectory(), check FileManager.attributesOfItem(atPath:)[.type] for .typeSymbolicLink and either skip or flag symlinks to the user rather than treating them as normal cache entries.
Labels:
bug,securitySummary
CleaningEngine.cleanItems()andScanEngine.scanDirectory()do not resolve symlinks before deleting files. A symlink planted in any scanned directory (e.g.~/Library/Caches/) can redirect deletion to arbitrary user-accessible paths like~/.ssh/,~/Documents/, or~/.gnupg/.Steps to reproduce
Impact
Any process running as the current user — malware, a rogue npm postinstall script, or even a cron job — can plant symlinks before a scheduled auto-clean runs. Combined with
autoClean: trueinSchedulerService, this becomes a zero-interaction attack that silently destroys targeted files.Affected code
Services/CleaningEngine.swift:31—removeItem(atPath:)follows symlinksServices/ScanEngine.swift:302-347—scanDirectory()doesn't check for symlinksSuggested fix
Before any deletion, resolve the real path and verify it's still inside the expected scan root:
Each scan category already knows its root paths — pass them through to the cleaning engine and validate before deletion.
Additionally, in
scanDirectory(), checkFileManager.attributesOfItem(atPath:)[.type]for.typeSymbolicLinkand either skip or flag symlinks to the user rather than treating them as normal cache entries.