Skip to content

Commit c14ab75

Browse files
authored
Merge pull request #1 from YKKULTRA/agent/repository-polish
Polish repository presentation
2 parents 46288f2 + 6927050 commit c14ab75

3 files changed

Lines changed: 209 additions & 52 deletions

File tree

Docs/SAFETY.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Safety model
2+
3+
SqueakyClean is designed around one default: incomplete evidence must stop cleanup. This document describes the guarantees implemented in the current release and the limits around them.
4+
5+
## Current trust boundary
6+
7+
The production cleanup profile is deliberately narrow. A file can become actionable only when all of the following are true:
8+
9+
1. It is a user LaunchAgent plist discovered under the allowlisted user LaunchAgents root.
10+
2. Its configured `Program`, or first `ProgramArguments` value, resolves to an absolute target path.
11+
3. That target is definitively missing, not merely inaccessible or unresolved.
12+
4. The plist is not matched to an installed application and does not fall under an Apple-managed or protected path.
13+
5. The scan captured a usable filesystem identity and the action-time checks still match it.
14+
15+
An unmatched cache, log, preference, Application Support folder, temporary item, installer receipt, script, or package is not evidence of abandonment. Those categories remain non-actionable in the current production rules.
16+
17+
## Scan boundaries
18+
19+
Standard scans inventory selected locations in the current user's Library plus the process temporary directory. Deep scans add selected `/Library` locations, shared launch items, and installer receipts.
20+
21+
Every `/Library` finding is analysis-only. Deep mode does not enable system-wide cleanup.
22+
23+
The scanner skips malformed plists and per-item metadata failures without aborting the entire scan. Unreadable roots are reported as skipped. A partial traversal is never treated as complete evidence.
24+
25+
The inventory pass examines the immediate children of configured roots. Candidate sizing and fingerprinting are recursive and include hidden descendants and package contents.
26+
27+
## Fail-closed classification
28+
29+
- Installed-app data is blocked.
30+
- Unknown ownership is ignored, not treated as orphaned.
31+
- Apple-managed paths such as CloudKit, Mobile Documents, iCloud, Containers, and Group Containers are blocked.
32+
- SqueakyClean's own application data is blocked.
33+
- Symbolic-link candidates are rejected by the action-time path policy.
34+
- Relative, malformed, unresolved, inaccessible, or still-valid launch targets are left untouched.
35+
- Launch target checks expand `~`, `$HOME`, and `${HOME}` forms before evaluating the target.
36+
37+
## Approval-time gate
38+
39+
Scan results can become stale, so approval is not enough by itself. Immediately before quarantine, SqueakyClean:
40+
41+
1. Resolves and validates the canonical source path.
42+
2. Confirms the path remains inside an allowed user root and outside every protected root.
43+
3. Rejects symbolic links.
44+
4. Verifies the filesystem number, file number, and modification state captured by the scan.
45+
5. Refreshes the installed-app catalog and blocks newly matched ownership.
46+
6. Rechecks that the launch target is still definitively missing.
47+
7. Fingerprints the payload.
48+
8. Rechecks the source identity and evidence once more before moving it.
49+
50+
If any check is unavailable, unsupported, or different from the scan evidence, the move is refused.
51+
52+
## Quarantine transactions
53+
54+
Quarantine, restore, and purge use a serialized manifest that is written atomically. Each mutation first records a pending operation, then performs the filesystem change, and finally records the completed state.
55+
56+
A filesystem lock serializes complete transactions across SqueakyClean processes. Managed payload paths, restore destinations, and the quarantine root identity are revalidated immediately around moves and deletion.
57+
58+
At startup, pending operations are reconciled against the filesystem. Clear outcomes are finalized. Ambiguous outcomes are preserved as explicit interrupted records so the app never guesses that a payload was deleted, moved, or restored.
59+
60+
## Payload integrity
61+
62+
Fingerprint version 2 separates file, directory, and symbolic-link domains. It hashes full file contents and deterministic directory entries, including hidden files and package contents. Large files are streamed in 1 MiB chunks rather than loaded into memory.
63+
64+
Restore accepts only a supported fingerprint version. It verifies the quarantined payload before moving it, verifies it again at the destination, and finalizes the record only after both checks succeed.
65+
66+
Directory sizing and fingerprinting fail closed when any descendant cannot be enumerated. Partial results are never accepted as complete.
67+
68+
## Space semantics
69+
70+
Candidate sizes are allocated-byte estimates. Compression, clones, filesystem metadata, and changes after the scan can change the amount eventually reclaimed.
71+
72+
Quarantine normally moves a payload on the same disk, so it does not free the payload's space. Only explicit permanent purge reclaims that space. Restored records can remain as audit history without retaining a quarantined payload.
73+
74+
## Local records
75+
76+
Scan snapshots, approval history, transaction state, and quarantined payloads are stored under:
77+
78+
```text
79+
~/Library/Application Support/SqueakyClean
80+
```
81+
82+
Read-only scanning is decoupled from audit and quarantine initialization, so a damaged local store does not disable the scanner itself.
83+
84+
## Permission and distribution limits
85+
86+
Full Disk Access is optional. The in-app access check probes selected protected locations, but it is not an authoritative verdict about the macOS privacy entitlement state.
87+
88+
Local bundles are ad hoc signed and verified for development. A public release still needs a Developer ID identity, Apple notarization, and a finalized permissions story. These safety controls reduce risk, but they do not turn an early preview into a substitute for backups or careful review.

Docs/images/scan-workspace.png

371 KB
Loading

README.md

Lines changed: 121 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,161 @@
1-
# SqueakyClean
1+
<div align="center">
2+
<h1>SqueakyClean</h1>
3+
<p><strong>A macOS cleaner that treats uncertainty as a stop sign.</strong></p>
4+
<p>Inspect known leftover locations, review the evidence, and quarantine only what you approve.</p>
5+
<p>
6+
<a href="https://github.com/YKKULTRA/squeakyclean/actions/workflows/ci.yml"><img alt="macOS CI" src="https://github.com/YKKULTRA/squeakyclean/actions/workflows/ci.yml/badge.svg"></a>
7+
<img alt="macOS 14 or newer" src="https://img.shields.io/badge/macOS-14%2B-111827?logo=apple&logoColor=white">
8+
<img alt="Swift 6.3" src="https://img.shields.io/badge/Swift-6.3-F05138?logo=swift&logoColor=white">
9+
</p>
10+
<p>
11+
<a href="#quick-start">Build from source</a> ·
12+
<a href="#safety-by-design">Safety model</a> ·
13+
<a href="CHANGELOG.md">Changelog</a>
14+
</p>
15+
</div>
16+
17+
<p align="center">
18+
<img src="Docs/images/scan-workspace.png" alt="SqueakyClean scan workspace in dark mode" width="100%">
19+
</p>
20+
21+
SqueakyClean is a native SwiftUI utility for careful macOS cleanup. It scans an allowlisted set of leftover locations, explains why a finding is actionable, and keeps cleanup reversible until you explicitly purge it. Scanning and cleanup happen locally.
22+
23+
> [!NOTE]
24+
> SqueakyClean is an early, deliberately narrow preview. Today it offers cleanup only for user LaunchAgent plists whose configured absolute target is definitively missing. It is not yet a general cache cleaner or app uninstaller.
25+
26+
## Why SqueakyClean
27+
28+
| Evidence first | Reversible by default | Local and transparent |
29+
|---|---|---|
30+
| Uncertain ownership is ignored, never treated as proof that data is abandoned. | Approved items enter an app-managed quarantine and can be restored before purge. | Every candidate includes a reason and evidence. There are no third-party Swift package dependencies. |
31+
32+
Most cleaners optimize for how much they can find. SqueakyClean optimizes for how confidently it can leave the right things alone.
33+
34+
## How it works
35+
36+
```mermaid
37+
flowchart LR
38+
A["Read-only scan"] --> B["Explain the evidence"]
39+
B --> C["You review and approve"]
40+
C --> D["Revalidate path, identity, and evidence"]
41+
D --> E["Journal and quarantine"]
42+
E --> F["Restore"]
43+
E --> G["Explicit purge"]
44+
```
245

3-
`SqueakyClean` is a native macOS cleanup app built with `SwiftUI` and a safety-first scanning engine.
46+
1. **Scan:** Standard mode inventories selected user locations. Deep mode adds system-wide locations for analysis only.
47+
2. **Classify:** The rules fail closed. Protected, installed, Apple-managed, inaccessible, unresolved, and unknown data is not offered for cleanup.
48+
3. **Review:** Search, filter, inspect evidence, select one or many candidates, and approve the exact items you want moved.
49+
4. **Revalidate:** Immediately before a move, SqueakyClean checks the canonical path, filesystem identity, modification state, ownership, and missing-target evidence again.
50+
5. **Quarantine:** The move is journaled and fingerprinted. Restore remains available until a separately confirmed purge.
451

5-
## What It Does
52+
> [!IMPORTANT]
53+
> Quarantine normally does not free disk space because the payload remains on the same disk. Space is reclaimed only after a separate, explicitly confirmed purge. Displayed sizes are allocated-byte estimates.
654
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.
55+
## Current cleanup scope
1756

18-
## Current Scope
57+
| Finding | Current behavior |
58+
|---|---|
59+
| User LaunchAgent with an absolute target confirmed missing | Reviewable cleanup candidate |
60+
| Unmatched app-like data | Ignored as unknown |
61+
| Installed-app or Apple-managed data | Blocked |
62+
| Relative, malformed, unresolved, or inaccessible launch target | Left untouched |
63+
| Any `/Library` finding from Deep mode | Analysis only |
64+
| Cache, log, preference, Application Support, temporary, or receipt data | Inventoried, but not actionable in this release |
1965

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.
66+
This narrow scope is intentional. A catalog miss or an old file is not enough evidence that something is safe to remove.
2167

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.
68+
## Safety by design
2369

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.
70+
- **Read-only until approval.** Scanning never mutates discovered files.
71+
- **Fail-closed decisions.** Unknown or incomplete evidence stops the cleanup path.
72+
- **Approval-time revalidation.** Allowed roots, protected paths, filesystem identity, modification state, installed-app ownership, and launch-target evidence are checked again around the move.
73+
- **Crash-aware transactions.** An atomically written journal and cross-process lock serialize quarantine, restore, and purge operations. Ambiguous interrupted states remain visible instead of being guessed away.
74+
- **Verified restore.** Type-separated SHA-256 fingerprints cover full file contents and deterministic directory entries, including hidden files and package contents. Restore verifies the payload before and after moving it.
2575

26-
## Safety Model
76+
The detailed invariants and current trust boundaries are documented in [the safety model](Docs/SAFETY.md).
2777

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.
78+
## Quick start
4079

41-
## Project Layout
80+
### Requirements
4281

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
82+
- macOS 14 or newer
83+
- A Swift 6.3-capable toolchain
84+
- Xcode Command Line Tools for building and code signing
4885

49-
## Run It
86+
### Build the app
5087

5188
```bash
89+
git clone https://github.com/YKKULTRA/squeakyclean.git
90+
cd squeakyclean
5291
Scripts/build-app.sh
5392
open build/SqueakyClean.app
5493
```
5594

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:
95+
The default local build targets the current Mac architecture and uses an ad hoc signature. To build a universal app for Apple silicon and Intel Macs:
5796

5897
```bash
5998
ARCHS="arm64 x86_64" Scripts/build-app.sh
6099
```
61100

62-
For a distribution build, select an installed signing identity. Non-ad-hoc identities automatically enable the hardened runtime and request a secure timestamp:
101+
For Developer ID signing, select an installed identity:
63102

64103
```bash
65104
ARCHS="arm64 x86_64" \
66105
CODE_SIGN_IDENTITY="Developer ID Application: Example, Inc. (TEAMID)" \
67106
Scripts/build-app.sh
68107
```
69108

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.
109+
Real signing identities automatically enable the hardened runtime and request a secure timestamp. `BUILD_DIR`, `MACOSX_DEPLOYMENT_TARGET`, and `WARNINGS_AS_ERRORS=1` provide additional build controls.
71110

72-
## Verify It
111+
## Development
73112

74113
```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
114+
swift build -Xswiftc -warnings-as-errors
115+
swift test -Xswiftc -warnings-as-errors
116+
ARCHS="arm64 x86_64" WARNINGS_AS_ERRORS=1 Scripts/build-app.sh
80117
```
81118

82-
GitHub Actions runs release builds and tests with warnings treated as errors, then packages and verifies a universal app bundle.
119+
GitHub Actions performs strict release builds and tests, packages a universal app bundle, verifies its architectures and signature, and uploads the archive as a workflow artifact.
120+
121+
### Project layout
122+
123+
| Path | Purpose |
124+
|---|---|
125+
| `Sources/SqueakyCleanCore` | Inventory, classification, path policy, quarantine, restore, and audit logic |
126+
| `Sources/SqueakyCleanApp` | SwiftUI interface and user workflows |
127+
| `Tests/SqueakyCleanCoreTests` | Core correctness and transaction-safety coverage |
128+
| `Resources/Info.plist` | App bundle metadata |
129+
| `Scripts/build-app.sh` | Native or universal app packaging, signing, and verification |
130+
131+
## FAQ
132+
133+
<details>
134+
<summary><strong>Does SqueakyClean delete anything automatically?</strong></summary>
135+
136+
No. Scans are read-only. A candidate moves only after explicit approval, and permanent purge requires a separate destructive confirmation.
137+
</details>
138+
139+
<details>
140+
<summary><strong>Is Full Disk Access required?</strong></summary>
141+
142+
No. Start without it. The app explains when a deliberately selected scan location could not be read, and its access check is a practical probe rather than an operating-system entitlement verdict.
143+
</details>
144+
145+
<details>
146+
<summary><strong>Does it remove caches or Application Support folders?</strong></summary>
147+
148+
Not in the current release. Those locations may be inventoried, but only positively identified dead user LaunchAgents can become cleanup candidates.
149+
</details>
83150

84-
## Local App Data
151+
<details>
152+
<summary><strong>Where does SqueakyClean keep its own data?</strong></summary>
85153

86-
The app stores its own audit and quarantine data under:
154+
Audit history, transaction state, and quarantined payloads live under `~/Library/Application Support/SqueakyClean`.
155+
</details>
87156

88-
`~/Library/Application Support/SqueakyClean`
157+
## Project status
89158

90-
## Distribution Status
159+
SqueakyClean is currently version `0.1.0` and under active development. This repository ships source code and development artifacts, not a notarized public release. Public distribution still requires Developer ID signing, Apple notarization, and a finalized permissions story.
91160

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.
161+
A project license has not yet been selected. See the [changelog](CHANGELOG.md) for the latest work and known scope.

0 commit comments

Comments
 (0)