|
| 1 | +# SqueakyClean |
| 2 | + |
| 3 | +`SqueakyClean` is a native macOS cleanup app built with `SwiftUI` and a safety-first scanning engine. |
| 4 | + |
| 5 | +## What It Does |
| 6 | + |
| 7 | +- Scans only allowlisted locations that commonly accumulate technical leftovers. |
| 8 | +- Treats installed-app data as blocked and unmatched app-like data as unknown, not orphaned. |
| 9 | +- Offers cleanup only when the scan has positive evidence, currently a user launch item whose resolved target is missing. |
| 10 | +- Inventories `/Library` in Deep mode for analysis, but never offers cleanup there. |
| 11 | +- Requires explicit user approval before cleanup. |
| 12 | +- Supports async scans, cancellation, search, kind filters, multi-select, and batch quarantine. |
| 13 | +- Shows an allocated-size estimate for candidates, including hidden files and package contents. |
| 14 | +- Moves approved items into an app-managed quarantine area with restore support. Quarantine itself does not free disk space. |
| 15 | +- Can permanently purge quarantined items through explicit destructive confirmation. |
| 16 | +- Records scan snapshots and approval history in the app's own storage. |
| 17 | + |
| 18 | +## Current Scope |
| 19 | + |
| 20 | +Milestone 1 deliberately keeps its actionable scope narrow. In the production scan profile, a user LaunchAgent becomes reviewable only when its configured target resolves to an absolute path and that path is confirmed missing. A relative command, unresolved path, malformed plist, or still-valid launch item is not offered for cleanup. |
| 21 | + |
| 22 | +An app-like folder or preference that fails to match the installed-app catalog is treated as unknown and ignored. A missing catalog match is not proof that an app was removed. Orphaned caches, logs, Application Support folders, preferences, stale scripts, and installer remnants are therefore not production cleanup categories today. |
| 23 | + |
| 24 | +Deep mode inventories selected `/Library` locations, including shared launch items and installer receipts, but every system-wide finding is analysis-only. The app deliberately does **not** propose cleanup for installed-app data, Apple-managed paths such as CloudKit, Mobile Documents, iCloud, Containers, or Group Containers, its own data, or any `/Library` item. |
| 25 | + |
| 26 | +## Safety Model |
| 27 | + |
| 28 | +- Scans are read-only until the user explicitly approves a candidate. |
| 29 | +- Classification fails closed: unknown ownership is ignored, while installed, Apple-managed, protected, and system-wide data is blocked. |
| 30 | +- Immediately before quarantine, an action-time gate requires a recorded filesystem identity, verifies the canonical path is inside an allowed user root, outside protected roots, is not a symbolic link, and still has the identity and modification state recorded by the scan. It also refreshes the installed-app catalog and, for dead launch items, confirms that the target is still definitively missing. It rechecks the candidate after fingerprinting and before moving it. |
| 31 | +- Quarantine writes a pending operation to a serialized, atomically saved manifest before moving the source. Pending quarantine, restore, and purge states are reconciled after interrupted operations; ambiguous states are preserved as interrupted instead of guessed or deleted. |
| 32 | +- A filesystem lock serializes complete quarantine, restore, and purge transactions across app processes. Managed payload and restore paths are revalidated immediately around mutations. |
| 33 | +- Version 2 fingerprints domain-separate files, directories, and symbolic links, then hash full file contents and deterministic directory entries, including hidden files and package contents. Restore requires a supported fingerprint and verifies it both before moving and at the destination before finalizing. |
| 34 | +- Candidate sizes are allocated-byte estimates, not guarantees of space that will be reclaimed. Filesystem compression, clones, metadata, and later changes can affect the result. |
| 35 | +- Quarantine preserves restore support but normally moves data on the same disk, so it frees no space. Only permanent purge reclaims the payload's space, and purge requires separate confirmation. |
| 36 | +- Malformed launch agent plists, unreadable files, and unreadable roots are skipped or surfaced as skipped roots instead of aborting the whole scan. |
| 37 | +- Launch agent target checks expand `~`, `$HOME`, and `${HOME}`-style paths before deciding whether a target is missing. |
| 38 | +- Large files are hashed with streaming SHA256 to avoid loading the whole payload into memory. |
| 39 | +- Directory sizing and fingerprinting fail closed if any descendant cannot be enumerated; partial traversals are never treated as complete evidence. |
| 40 | + |
| 41 | +## Project Layout |
| 42 | + |
| 43 | +- `Sources/SqueakyCleanCore`: scanning, ownership resolution, rule engine, quarantine, restore, audit persistence |
| 44 | +- `Sources/SqueakyCleanApp`: `SwiftUI` app shell and user flows |
| 45 | +- `Resources/Info.plist`: bundle metadata used by the local app build script |
| 46 | +- `Scripts/build-app.sh`: builds, signs, and verifies `build/SqueakyClean.app` |
| 47 | +- `Tests/SqueakyCleanCoreTests`: unit tests for the core safety and cleanup behavior |
| 48 | + |
| 49 | +## Run It |
| 50 | + |
| 51 | +```bash |
| 52 | +Scripts/build-app.sh |
| 53 | +open build/SqueakyClean.app |
| 54 | +``` |
| 55 | + |
| 56 | +The default build targets the current Mac architecture and uses an ad hoc signature, which is suitable for local development. To create a universal app containing both Apple silicon and Intel executables: |
| 57 | + |
| 58 | +```bash |
| 59 | +ARCHS="arm64 x86_64" Scripts/build-app.sh |
| 60 | +``` |
| 61 | + |
| 62 | +For a distribution build, select an installed signing identity. Non-ad-hoc identities automatically enable the hardened runtime and request a secure timestamp: |
| 63 | + |
| 64 | +```bash |
| 65 | +ARCHS="arm64 x86_64" \ |
| 66 | +CODE_SIGN_IDENTITY="Developer ID Application: Example, Inc. (TEAMID)" \ |
| 67 | +Scripts/build-app.sh |
| 68 | +``` |
| 69 | + |
| 70 | +`BUILD_DIR` can select another output directory, `MACOSX_DEPLOYMENT_TARGET` can raise the plist default, and `WARNINGS_AS_ERRORS=1` enables strict compiler warnings during packaging. Running the raw SwiftPM executable can make Finder open a Terminal window. Use the `.app` bundle above for normal macOS launching. |
| 71 | + |
| 72 | +## Verify It |
| 73 | + |
| 74 | +```bash |
| 75 | +swift build |
| 76 | +swift test |
| 77 | +WARNINGS_AS_ERRORS=1 Scripts/build-app.sh |
| 78 | +plutil -lint build/SqueakyClean.app/Contents/Info.plist |
| 79 | +codesign --verify --deep --strict --verbose=2 build/SqueakyClean.app |
| 80 | +``` |
| 81 | + |
| 82 | +GitHub Actions runs release builds and tests with warnings treated as errors, then packages and verifies a universal app bundle. |
| 83 | + |
| 84 | +## Local App Data |
| 85 | + |
| 86 | +The app stores its own audit and quarantine data under: |
| 87 | + |
| 88 | +`~/Library/Application Support/SqueakyClean` |
| 89 | + |
| 90 | +## Distribution Status |
| 91 | + |
| 92 | +The local app bundle is ad-hoc signed and strictly verified. That is appropriate for development and personal testing. Public distribution still needs a Developer ID identity, Apple notarization, and a clearer permissions story. The build script supports Developer ID signing and the hardened runtime, but it deliberately does not submit credentials or notarize automatically. |
0 commit comments