Modernize MacOS-Maid: full Python rewrite (v1.0.0) - #4
Merged
Conversation
Remove old maid.sh bash script (preserved in git history). Set up src layout with pyproject.toml, click/pyyaml/rich deps, and dev tooling (pytest, ruff, mypy). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScanResult, CleanResult, Finding, AuditResult dataclasses with .empty() factory methods. Abstract Module base class with scan/clean/audit interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs every destructive action with module, detail, and bytes reclaimed. Saves to ~/.maid/last_run.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects arch, macOS version, filesystem type, Homebrew prefix, and WiFi interface. Adapts for Intel vs Apple Silicon. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deep merges user config with defaults. Safety-critical values default to off (git cleanup, docker stopped containers, etc). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Filters by category or module name, skips sudo modules unless --sudo flag is set, logs all actions to audit log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Formats cleanup and audit results for terminal, JSON, or markdown output. Includes platform header and byte formatting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ands Click-based CLI with --dry-run, --sudo, --dev, --security flags, category/module filtering, and config file support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements HomebrewModule with scan, clean, and audit methods: - scan() checks for outdated packages and cache size - clean() runs brew update, upgrade, and cleanup --prune=all - audit() returns empty (no security checks) - Returns empty results if brew is not installed Added 5 comprehensive tests covering metadata, scan (installed and not installed), clean, and audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements trash cleanup module that reports trash size via du -sk and empties trash via AppleScript. Module includes scan(), clean(), and audit() methods with full error handling. Tests use mocked _get_trash_size and _empty_trash functions. All 6 tests pass: metadata, scan with items, scan empty, clean success, clean error, and audit empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SystemCacheModule for safe cleanup of: - User Library caches (~/Library/Caches) - System diagnostic reports (/Library/Logs/DiagnosticReports) - User diagnostic reports (~/Library/Logs/DiagnosticReports) SAFETY: Module explicitly avoids /private/var/folders which the original macos-maid script dangerously cleaned. Tests include critical safety check that verifies NONE of the directories contain /private/var/folders. Module requires sudo for system log access, category="both". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements GitModule for cleaning up git repositories with safety features: - Disabled by default, requires explicit configuration - Prunes remote branches and optionally deletes merged local branches - Never deletes protected branches (main, master, develop) - Reports large repositories when enabled - Only deletes branches that are merged into the default branch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds WiFi module to clean up stale wireless networks with safety features. ALWAYS keeps currently connected network regardless of config. Features: - Configurable retention period (default: 90 days) - Network allowlist (keep_ssids) - Hardcoded safety: never removes current network - Uses networksetup commands for network management Tests include coverage for: - Current network protection (always kept) - Allowlist functionality - Empty/stale network detection - Error handling - Command parsing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add read-only audit module that checks macOS security settings: - System Integrity Protection (SIP) - FileVault disk encryption - Gatekeeper - XProtect malware protection - Application Firewall Module returns empty for scan() and clean() (audit-only). audit() runs all 5 checks and determines worst status (fail > warn > pass). All checks include remediation guidance for failed/warn states. Includes comprehensive test coverage with mocked subprocess calls. Added SystemIntegrityModule to module registry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements NetworkModule with DNS cache flushing and network security audits: - scan(): Reports DNS cache flush action - clean(): Flushes DNS cache using dscacheutil and mDNSResponder - audit(): Checks firewall status, open TCP ports, and VPN profiles Security checks include: - macOS Application Firewall status (pass/fail) - Open TCP listening ports with process names (info) - Configured VPN profiles (info) Module requires sudo for DNS flush operations. Tests mock all system commands and verify: - Module metadata and category - DNS flush success and error handling - Firewall enabled/disabled detection - Open ports and VPN profile reporting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add read-only audit module that flags non-Apple launch daemons and agents. Never removes or disables them. Scans /Library/LaunchDaemons, /Library/LaunchAgents, and ~/Library/LaunchAgents for .plist files and reports non-Apple items as info findings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements privacy and security auditing with TCC permission tracking and Downloads cleanup awareness. Module is read-only by default and never deletes Downloads files without explicit configuration. Features: - TCC permissions audit (reads user-level TCC.db in read-only mode) - Recent items clearing (optional, configured via clear_recent flag) - Old Downloads reporting (report-only by default, never deletes) - Configurable thresholds for Downloads age tracking Safety: - Never requests Full Disk Access - Downloads are report-only by default - TCC database opened in read-only mode - No destructive operations without explicit configuration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ToolsModule to integrate with external security tools: - Lynis: System hardening audit with hardening index and suggestions - osquery: Targeted queries for unsigned processes and network listeners - KnockKnock: Persistent malware scanning (manual usage) Module is audit-only (no cleanup operations) and never installs tools. Suggests installation via Homebrew if tools are not found. Config keys: lynis_enabled, osquery_enabled, knockknock_enabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements read-only security audit for installed applications. Scans /Applications for .app bundles and checks code signing status with tiered warnings: - Signed or App Store apps marked as ok - Unsigned apps without elevated permissions marked as info - Unsigned apps with elevated permissions marked as warn Checks include codesign verification, App Store origin detection, and heuristic checks for elevated permissions via helper tools and privileged executables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive supporting documentation for MacOS Maid v1.0.0: - CHANGELOG.md: Keep a Changelog format with v1.0.0 release notes documenting the complete Python rewrite, 13 modules, safety improvements, and removed dangerous operations - SECURITY.md: Documents privacy guarantees (no telemetry), privilege model (unprivileged by default, sudo opt-in), safe defaults (dry-run first run, git disabled), audit-only modules, and vulnerability reporting via GitHub Security Advisories - CONTRIBUTING.md: Development guide covering setup, module writing pattern (scan/clean/audit), testing requirements, code style (ruff + mypy), and PR process - docs/modules.md: Detailed reference for all 13 modules including what each does, system commands used, config options, usage examples, and known limitations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Creates GitHub release on tag push. Runs tests before releasing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix line-length violations (E501), unused imports (F401), unused variables (F841), ambiguous variable name (E741), and mypy type errors. Add uv.lock to .gitignore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes: - Fix git branch parsing to skip current branch (was using lstrip incorrectly) - Fix audit log double-counting bytes_reclaimed per item Important fixes: - Fix run_audit() to include sudo modules (audit is read-only) - Replace rm -rf subprocess with shutil.rmtree in system_cache (+ symlink check) - Fix format_bytes integer truncation for large values - Remove unused rich dependency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add confirmation prompts, subprocess timeouts, sudo validation, symlink checks, and non-zero exit codes for audit failures to improve safety and reliability. Changes: - Add --yes/-y flag to clean and report commands to skip confirmation - Show preview and require confirmation before destructive operations - Validate sudo access upfront when --sudo is passed - Add timeout=120 to homebrew._run_brew() for slow operations - Add timeout=10 to network DNS flush operations - Add timeout=15 to network lsof port scanning - Add timeout=5 to network firewall and VPN checks - Add timeout=30 to trash du command - Add timeout=60 to trash osascript empty operation - Add timeout=10 to all wifi networksetup calls - Add timeout=10 to privacy osascript call - Add timeout=30 to dev_caches._dir_size() du command - Add symlink check in dev_caches before shutil.rmtree() - Add sys.exit(1) in audit command if any finding has severity "fail" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove no-op scan() and clean() stubs from audit-only modules (system_integrity, app_audit, launch_audit, tools) since base class now provides default implementations - Replace inline severity ordering logic with shared worst_severity() function from base module across all audit modules (network, privacy, system_integrity, tools, app_audit) - Create shared dir_size() utility in utils.py and update dev_caches and system_cache to use it - Fix osquery SQL query in tools.py to use simpler non-system process detection instead of broken signature table subquery - Improve KnockKnock integration in tools.py with clearer messaging - Move json import to top of tools.py file - Update tests to mock new shared utilities instead of removed private methods All tests pass. Ruff checks pass on modified files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fix: Module constructors now receive config parameters instead of hardcoded defaults. This connects the YAML config system to actual module behavior. Changes: - Updated get_all_modules() to accept MaidConfig parameter and pass config values to module constructors - Added constructor parameters to WiFiModule, PrivacyModule, ToolsModule, GitModule, HomebrewModule, and DockerModule - All CLI commands now pass loaded config to get_all_modules() - Module constructors maintain sensible defaults for backward compatibility - Updated tests to match new behavior Safety defaults (install no software without explicit config): - homebrew.update: False (was True) - homebrew.upgrade: False (was True) Modules affected: - WiFiModule: keep_days, keep_ssids - PrivacyModule: clear_recent, downloads_move_to_trash, downloads_older_than - ToolsModule: lynis_enabled, osquery_enabled, knockknock_enabled - GitModule: enabled, repos_dir, prune_remotes, delete_merged, protected_branches - HomebrewModule: update, upgrade, cleanup - DockerModule: remove_dangling_images, remove_unused_volumes, remove_stopped_containers All tests pass. Ruff checks pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix CI mypy failure: add types-PyYAML to dev deps - Change homebrew update/upgrade defaults to False (installs new software) - Add Severity IntEnum and worst_severity() shared helper to base.py - Remove @AbstractMethod from Module ABC, provide default empty implementations - Replace assert with proper validation in MaidConfig - Fix README: correct class names and example output to match actual CLI - Fix CHANGELOG date, correct development status to Alpha - Fix homebrew config fallback defaults in module registry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace macos-13 with macos-15 in CI matrix (macos-13 no longer available) - Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to silence Node.js 20 deprecation - Fix test_cli_audit: accept exit code 1 when audit finds real failures (CI runners have firewall disabled, triggering severity "fail") Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- actions/checkout v4 → v6 - actions/setup-python v5 → v6 - Remove FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 workaround (no longer needed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntegration, severity Added comprehensive tests closing key coverage gaps: 1. Docker _parse_reclaimed_space: GB/MB/KB/B parsing, empty/no-match cases 2. Docker config flags: stopped containers, all flags disabled 3. CLI report command: dry-run with nonexistent config 4. CLI config command: displays 'not found, using defaults' 5. Config integration: verifies config values reach module constructors 6. Severity enum: from_str for all levels, str conversion 7. worst_severity: empty list, single/mixed findings Coverage increased from 79% to 81% (187 tests passing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e enabled, severity validation Fix #1: system_cache too aggressive - Changed from wiping entire ~/Library/Caches to explicit allowlist - Now only cleans: Xcode, Homebrew, pip, yarn, nsurlsessiond caches - Prevents destruction of app caches that don't regenerate gracefully (Outlook, Teams, browsers) - Updated scan() to only report allowlisted directories - Updated tests to reflect new allowlist approach Fix #2: No per-module enabled config key - Added "enabled": True to all module sections in DEFAULT_CONFIG - Git remains "enabled": False by default - Updated get_all_modules() to check cfg.get("enabled", True) for each module - Modules with enabled=False are now excluded from the module list - Applies to all 13 modules including those without explicit config Fix #3: Finding.severity validation - Added __post_init__ validation to Finding dataclass - Raises ValueError if severity not in {"pass", "info", "warn", "fail"} - Catches typos without requiring full enum migration - Maintains Finding.severity as str for backward compatibility All tests pass (186/186 excluding pre-existing wifi test failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…le-counting Homebrew and pip caches were counted by both their dedicated modules (homebrew, dev_caches) and the system_cache module. Remove them from SAFE_CACHE_DIRS so each cache is only reported once. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oped) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t-time Path.home() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace dict-based get_module_config().get() pattern with typed property accessors (cfg.homebrew.update, cfg.git.enabled, etc.). Dataclass defaults now carry fallback values. Eliminates 13 dict lookups. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Apple jumped from macOS 15 (Sequoia) to macOS 26 (Tahoe), skipping versions 16-25. Smoke tests on macOS 26.4.1 reported 'macOS Unknown 26.4.1'. This update adds macOS 26 to the name map and improves the test to validate both the new mapping and ensure truly-unknown versions (99) still return 'Unknown'. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Full modernization of MacOS-Maid: the 2018 bash script is replaced by a Python 3.10+ package with a Click CLI, YAML config, safety-first defaults, and 13 modules spanning disk cleanup and security auditing.
Safety fixes from review
Three rounds of review (enterprise IT, Python expert, adversarial) shaped the final design. Headline fixes:
keep_dayswas dead code in an earlier pass and defaulted to wipe-all. Now honors a 90-day retention window.system_cacheallowlist — previously iterated all of~/Library/Caches. Now uses a strict allowlist (Xcode / yarn / nsurlsessiond) plus diagnostic reports only. Never touches/private/var/folders.enabledflag on all 13 modules for fleet/MDM control.Finding.severitytyped as aSeverityenum with back-compat str coercion.update/upgradedefault toFalse— never installs software without explicit opt-in.requires_sudolowered toFalse(SFL2 / TCC / Downloads are all user-scoped).SystemCacheModulepaths computed lazily to avoid import-timePath.home()issues undersudo -H/ containers.Architecture
src/macos_maid/modules/— 13 modules subclassingModulefrombase.pyMaidConfigwith 13 per-module typed@dataclasses (HomebrewConfig,WiFiConfig,GitConfig, …) — no more.get("key", default)in module wiringRunnerorchestrates module execution with sudo gating and audit loggingReporterrenders results to terminal / JSON / markdownIntEnumwithworst_severity()helperCLI
Eight commands:
clean,audit,report,list,init,version,log,config. Safety-first defaults (dry-run, no sudo unless requested). Smoke-tested on macOS 26 Tahoe.Quality gates
pytest --cov=macos_maid)ruff check .cleanmypy src/clean (strict mode)DEFAULT_CONFIGdict and dataclass defaultsWhat changed from the original bash
maid.shwith a modular Python packagediskutil secureErase freespace,/private/var/foldersdeletion, hardcoded SSID blacklistSeverityenum, typed config, audit log, JSON/markdown reportersBreaking changes
N/A — this is a clean rewrite replacing the old bash entrypoint.
🤖 Generated with Claude Code