From af84d6f4be23f4518d4be7e6d8b7d8155d7a1abd Mon Sep 17 00:00:00 2001 From: Tw93 Date: Tue, 10 Mar 2026 15:27:24 +0800 Subject: [PATCH] docs: strengthen public security signals --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 2 + .github/ISSUE_TEMPLATE/config.yml | 3 + .github/dependabot.yml | 10 + .github/pull_request_template.md | 18 ++ .github/workflows/codeql.yml | 52 +++++ .github/workflows/release.yml | 30 ++- .github/workflows/test.yml | 10 + README.md | 20 +- SECURITY.md | 76 +++++++ SECURITY_AUDIT.md | 302 +++++++++++++++------------ lib/core/file_ops.sh | 5 +- tests/core_safe_functions.bats | 28 +++ 13 files changed, 417 insertions(+), 140 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/codeql.yml create mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..74d9b7c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @tw93 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6e0779c..ca2ca4d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,6 +10,8 @@ assignees: '' A clear and concise description of what the bug is. We suggest using English for better global understanding. +If you believe the issue may allow unsafe deletion, path validation bypass, privilege boundary bypass, or release/install integrity issues, do not file a public bug report. Report it privately using the contact details in `SECURITY.md`. + ## Steps to reproduce 1. Run command: `mo ...` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8d9ce89..ad78d2f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: + - name: Private Security Report + url: mailto:hitw93@gmail.com?subject=Mole%20security%20report + about: Report a suspected vulnerability privately instead of opening a public issue - name: Telegram Community url: https://t.me/+GclQS9ZnxyI2ODQ1 about: Join our Telegram group for questions and discussions diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 603f653..5109cab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,8 +4,18 @@ updates: directory: "/" schedule: interval: "weekly" + labels: + - "dependencies" + reviewers: + - "tw93" + open-pull-requests-limit: 10 - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" + labels: + - "dependencies" + reviewers: + - "tw93" + open-pull-requests-limit: 10 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b383243 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## Summary + +- Describe the change. + +## Safety Review + +- Does this change affect cleanup, uninstall, optimize, installer, remove, analyze delete, update, or install behavior? +- Does this change affect path validation, protected directories, symlink handling, sudo boundaries, or release/install integrity? +- If yes, describe the new boundary or risk change clearly. + +## Tests + +- List the automated tests you ran. +- List any manual checks for high-risk paths or destructive flows. + +## Safety-related changes + +- None. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..b05614e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,52 @@ +name: CodeQL + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + schedule: + - cron: '17 3 * * 1' + +permissions: + contents: read + security-events: write + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - language: go + build-mode: manual + - language: actions + build-mode: none + + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 + + - name: Set up Go + if: matrix.language == 'go' + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v5 + with: + go-version: "1.24.6" + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + + - name: Build for CodeQL + if: matrix.build-mode == 'manual' + run: make build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc22b27..3ed8a38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - 'V*' permissions: - contents: write + contents: read jobs: build: @@ -58,6 +58,10 @@ jobs: name: Publish Release needs: build runs-on: ubuntu-latest + permissions: + contents: write + attestations: write + id-token: write steps: - name: Download all artifacts uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 @@ -69,16 +73,32 @@ jobs: - name: Display structure of downloaded files run: ls -R bin/ + - name: Generate release checksums + run: | + cd bin + mapfile -t release_files < <(find . -maxdepth 1 -type f -printf '%P\n' | sort) + if [[ ${#release_files[@]} -eq 0 ]]; then + echo "No release assets found" + exit 1 + fi + sha256sum "${release_files[@]}" > SHA256SUMS + cat SHA256SUMS + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: | + bin/analyze-darwin-* + bin/status-darwin-* + bin/binaries-darwin-*.tar.gz + bin/SHA256SUMS + - name: Create Release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 if: startsWith(github.ref, 'refs/tags/') with: name: ${{ github.ref_name }} files: bin/* - body: | - Release assets are ready. - - Final curated release notes should be applied with `gh release edit` after workflow verification. generate_release_notes: false draft: false prerelease: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4151314..dbbbe44 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,9 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 + - name: Install tools + run: brew install bats-core + - name: Check for unsafe rm usage run: | echo "Checking for unsafe rm patterns..." @@ -86,3 +89,10 @@ jobs: exit 1 fi echo "✓ No secrets found" + + - name: Run high-risk path regression tests + env: + BATS_FORMATTER: tap + LANG: en_US.UTF-8 + LC_ALL: en_US.UTF-8 + run: bats tests/core_safe_functions.bats tests/purge.bats tests/installer.bats diff --git a/README.md b/README.md index 2c7ad76..0530d61 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,28 @@ mo purge --paths # Configure project scan directories mo analyze /Volumes # Analyze external drives only ``` +## Security & Safety Design + +Mole is a local system maintenance tool. Commands such as `clean`, `uninstall`, `purge`, `installer`, `remove`, and parts of `optimize` can perform destructive local operations. + +Mole is designed with safety-first defaults for local system maintenance. + +- Destructive operations are guarded by path validation, protected directory rules, conservative cleanup boundaries, and explicit confirmation where appropriate. +- Mole prioritizes bounded cleanup over aggressive cleanup. +- High-risk paths, sensitive data categories, system locations, and sudo flows have explicit protection boundaries. +- When uncertainty exists, the tool should refuse, skip, or require stronger confirmation instead of widening deletion scope. +- `mo analyze` is intentionally safer than cleanup flows for ad hoc deletion because it moves files to Trash through Finder instead of directly deleting them. +- Release assets are published with SHA-256 checksums, curated safety notes, and GitHub artifact attestations. + +Review these documents before using high-risk commands: + +- [SECURITY.md](SECURITY.md) +- [SECURITY_AUDIT.md](SECURITY_AUDIT.md) + ## Tips - Video tutorial: Watch the [Mole tutorial video](https://www.youtube.com/watch?v=UEe9-w4CcQ0), thanks to PAPAYA 電腦教室. -- Safety and logs: Deletions are permanent. Review with `--dry-run` first, and add `--debug` when needed. File operations are logged to `~/.config/mole/operations.log`. Disable with `MO_NO_OPLOG=1`. See [Security Audit](SECURITY_AUDIT.md). +- Safety and logs: `clean`, `uninstall`, `purge`, `installer`, and `remove` are destructive. Review with `--dry-run` first, and add `--debug` when needed. File operations are logged to `~/.config/mole/operations.log`. Disable with `MO_NO_OPLOG=1`. Review [SECURITY.md](SECURITY.md) and [SECURITY_AUDIT.md](SECURITY_AUDIT.md). - Navigation: Mole supports arrow keys and Vim bindings `h/j/k/l`. ## Features in Detail diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7a38830 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,76 @@ +# Security Policy + +Mole is a local system maintenance tool. It includes high-risk operations such as cleanup, uninstall, optimization, and artifact removal. We treat safety boundaries, deletion logic, and release integrity as security-sensitive areas. + +## Reporting a Vulnerability + +Please report suspected security issues privately. + +- Email: `hitw93@gmail.com` +- Subject line: `Mole security report` + +Do not open a public GitHub issue for an unpatched vulnerability. + +If GitHub Security Advisories private reporting is enabled for the repository, you may use that channel instead of email. + +Include as much of the following as possible: + +- Mole version and install method +- macOS version +- Exact command or workflow involved +- Reproduction steps or proof of concept +- Whether the issue involves deletion boundaries, symlinks, sudo, path validation, or release/install integrity + +## Response Expectations + +- We aim to acknowledge new reports within 7 calendar days. +- We aim to provide a status update within 30 days if a fix or mitigation is not yet available. +- We will coordinate disclosure after a fix, mitigation, or clear user guidance is ready. + +Response times are best-effort for a maintainer-led open source project, but security reports are prioritized over normal bug reports. + +## Supported Versions + +Security fixes are only guaranteed for: + +- The latest published release +- The current `main` branch + +Older releases may not receive security fixes. Users running high-risk commands should stay current. + +## What We Consider a Security Issue + +Examples of security-relevant issues include: + +- Path validation bypasses +- Deletion outside intended cleanup boundaries +- Unsafe handling of symlinks or path traversal +- Unexpected privilege escalation or unsafe sudo behavior +- Sensitive data removal that bypasses documented protections +- Release, installation, update, or checksum integrity issues +- Vulnerabilities in logic that can cause unintended destructive behavior + +## What Usually Does Not Qualify + +The following are usually normal bugs, feature requests, or documentation issues rather than security issues: + +- Cleanup misses that leave recoverable junk behind +- False negatives where Mole refuses to clean something +- Cosmetic UI problems +- Requests for broader or more aggressive cleanup behavior +- Compatibility issues without a plausible security impact + +If you are unsure whether something is security-relevant, report it privately first. + +## Security-Focused Areas in Mole + +The project pays particular attention to: + +- Destructive command boundaries +- Path validation and protected-directory rules +- Sudo and privilege boundaries +- Symlink and path traversal handling +- Sensitive data exclusions +- Packaging, release artifacts, checksums, and update/install flows + +For the current technical design and known limitations, see [SECURITY_AUDIT.md](SECURITY_AUDIT.md). diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md index 0a6e471..8fed2c4 100644 --- a/SECURITY_AUDIT.md +++ b/SECURITY_AUDIT.md @@ -1,18 +1,59 @@ -# Mole Security Reference +# Mole Security Audit -Version 1.30.0 | 2026-03-08 +This document describes the security-relevant behavior of the current `main` branch. It is intended as a public description of Mole's safety boundaries, destructive-operation controls, release integrity signals, and known limitations. -This document describes the security-relevant behavior of the current codebase on `main`. +## Executive Summary -## Path Validation +Mole is a local system maintenance tool. Its main risk surface is not remote code execution; it is unintended local damage caused by cleanup, uninstall, optimize, purge, installer cleanup, or other destructive operations. -All destructive file operations go through `lib/core/file_ops.sh`. +The project is designed around safety-first defaults: -- `validate_path_for_deletion()` rejects empty paths, relative paths, traversal segments such as `/../`, and control characters. -- Security-sensitive cleanup paths do not use raw `find ... -delete`. -- Removal flows use guarded helpers such as `safe_remove()`, `safe_sudo_remove()`, `safe_find_delete()`, and `safe_sudo_find_delete()`. +- destructive paths are validated before deletion +- critical system roots and sensitive user-data categories are protected +- sudo use is bounded and additional restrictions apply when elevated deletion is required +- symlink handling is conservative +- preview, confirmation, timeout, and operation logging are used to make destructive behavior more visible and auditable -Blocked paths remain protected even with sudo, including: +Mole prioritizes bounded cleanup over aggressive cleanup. When uncertainty exists, the tool should refuse, skip, or require stronger confirmation instead of widening deletion scope. + +The project continues to strengthen: + +- release integrity and public security signals +- targeted regression coverage for high-risk paths +- clearer documentation for privilege boundaries and known limitations + +## Threat Surface + +The highest-risk areas in Mole are: + +- direct file and directory deletion +- recursive cleanup across common user and system cache locations +- uninstall flows that combine app removal with remnant cleanup +- project artifact purge for large dependency/build directories +- elevated cleanup paths that require sudo +- release, install, and update trust signals for distributed artifacts + +`mo analyze` is intentionally lower-risk than cleanup flows: + +- it does not require sudo +- it respects normal user permissions and SIP +- delete actions require explicit confirmation +- deletion routes through Finder Trash behavior rather than direct permanent removal + +## Destructive Operation Boundaries + +All destructive shell file operations are routed through guarded helpers in `lib/core/file_ops.sh`. + +Core controls include: + +- `validate_path_for_deletion()` rejects empty paths +- relative paths are rejected +- path traversal segments such as `..` as a path component are rejected +- paths containing control characters are rejected +- raw `find ... -delete` is avoided for security-sensitive cleanup logic +- removal flows use guarded helpers such as `safe_remove()`, `safe_sudo_remove()`, `safe_find_delete()`, and `safe_sudo_find_delete()` + +Blocked paths remain protected even with sudo. Examples include: ```text / @@ -26,7 +67,7 @@ Blocked paths remain protected even with sudo, including: /Library/Extensions ``` -Some subpaths under protected roots are explicitly allowlisted for bounded cache and log cleanup, for example: +Some subpaths under otherwise protected roots are explicitly allowlisted for bounded cleanup where the project intentionally supports cache/log maintenance. Examples include: - `/private/tmp` - `/private/var/tmp` @@ -37,23 +78,84 @@ Some subpaths under protected roots are explicitly allowlisted for bounded cache - `/private/var/db/powerlog` - `/private/var/db/reportmemoryexception` -When running with sudo, symlinked targets are validated before deletion and system-target symlinks are refused. +This design keeps cleanup scoped to known-safe maintenance targets instead of broad root-level deletion patterns. -## Cleanup Rules +## Protected Directories and Categories -### Orphan Detection +Mole has explicit protected-path and protected-category logic in addition to root-path blocking. -Orphaned app data is handled in `lib/clean/apps.sh`. +Protected or conservatively handled categories include: -- Generic orphaned app data requires both: - - the app is not found by installed-app scanning and fallback checks, and - - the target has been inactive for at least 30 days. -- Claude VM bundles use a stricter app-specific window: - - `~/Library/Application Support/Claude/vm_bundles/claudevm.bundle` must appear orphaned, and - - it must be inactive for at least 7 days before cleanup. -- Sensitive categories such as keychains, password-manager data, and protected app families are excluded from generic orphan cleanup. +- system components such as Control Center, System Settings, TCC, Spotlight, Finder, and Dock-related state +- keychains, password-manager data, tokens, credentials, and similar sensitive material +- VPN and proxy tools such as Shadowsocks, V2Ray, Clash, and Tailscale +- AI tools in generic protected-data logic, including Cursor, Claude, ChatGPT, and Ollama +- `~/Library/Messages/Attachments` +- browser history and cookies +- Time Machine data while backup state is active or ambiguous +- `com.apple.*` LaunchAgents and LaunchDaemons +- iCloud-synced `Mobile Documents` data -Installed-app detection is broader than a simple `/Applications` scan and includes: +Project purge also uses conservative heuristics: + +- purge targets must be inside configured project boundaries +- direct-child artifact cleanup is only allowed in single-project mode +- recently modified artifacts are treated as recent for 7 days +- nested artifacts are filtered to avoid parent-child over-deletion +- protected vendor/build-output heuristics block ambiguous directories + +Developer cleanup also preserves high-value state. Examples intentionally left alone include: + +- `~/.cargo/bin` +- `~/.rustup` +- `~/.mix/archives` +- `~/.stack/programs` + +## Symlink and Path Traversal Handling + +Symlink behavior is intentionally conservative. + +- path validation checks symlink targets before deletion +- symlinks pointing at protected system targets are rejected +- `safe_sudo_remove()` refuses to sudo-delete symlinks +- `safe_find_delete()` and `safe_sudo_find_delete()` refuse to scan symlinked base directories +- installer discovery avoids treating symlinked installer files as deletion candidates +- analyzer scanning skips following symlinks to unexpected targets + +Path traversal handling is also explicit: + +- non-absolute paths are rejected for destructive helpers +- `..` is rejected when it appears as a path component +- legitimate names containing `..` inside a single path element remain allowed to avoid false positives for real application data + +## Privilege Escalation and Sudo Boundaries + +Mole uses sudo for a subset of system-maintenance paths, but elevated behavior is still bounded by validation and protected-path rules. + +Key properties: + +- sudo access is explicitly requested instead of assumed +- non-interactive preview remains conservative when sudo is unavailable +- protected roots remain blocked even when sudo is available +- sudo deletion uses the same path validation gate as non-sudo deletion +- sudo cleanup skips or reports denied operations instead of widening scope +- authentication, SIP/MDM, and read-only filesystem failures are classified separately in file-operation results + +When sudo is denied or unavailable, Mole prefers skipping privileged cleanup to forcing execution through unsafe fallback behavior. + +## Sensitive Data Exclusions + +Mole is not intended to aggressively delete high-value user data. + +Examples of conservative handling include: + +- sensitive app families are excluded from generic orphan cleanup +- orphaned app data waits for inactivity windows before cleanup +- Claude VM orphan cleanup uses a separate stricter rule +- uninstall file lists are decoded and revalidated before removal +- reverse-DNS bundle ID validation is required before LaunchAgent and LaunchDaemon pattern matching + +Installed-app detection is broader than a single `/Applications` scan and includes: - `/Applications` - `/System/Applications` @@ -61,140 +163,74 @@ Installed-app detection is broader than a simple `/Applications` scan and includ - Homebrew Caskroom locations - Setapp application paths -Spotlight fallback checks are bounded with short timeouts to avoid hangs. +This reduces the risk of incorrectly classifying active software as orphaned data. -### Uninstall Matching +## Dry-Run, Confirmation, and Audit Logging -App uninstall behavior is implemented in `lib/uninstall/batch.sh` and related helpers. +Mole exposes multiple safety controls before and during destructive actions: -- LaunchAgent and LaunchDaemon lookups require a valid reverse-DNS bundle identifier. -- Deletion candidates are decoded and validated as absolute paths before removal. -- Homebrew casks are preferentially removed with `brew uninstall --cask --zap`. -- LaunchServices unregister and rebuild steps are skipped safely if `lsregister` is unavailable. +- `--dry-run` previews are available for major destructive commands +- interactive high-risk flows require explicit confirmation before deletion +- purge marks recent projects conservatively and leaves them unselected by default +- analyzer delete uses Finder Trash rather than direct permanent removal +- operation logs are written to `~/.config/mole/operations.log` unless disabled with `MO_NO_OPLOG=1` +- timeouts bound external commands so stalled discovery or uninstall operations do not silently hang the entire flow -### Developer and Project Cleanup +Relevant timeout behavior includes: -Project artifact cleanup in `lib/clean/project.sh` protects recently modified targets: +- orphan and Spotlight checks: 2s +- LaunchServices rebuild during uninstall: bounded 10s and 15s steps +- Homebrew uninstall cask flow: 300s by default, extended for large apps when needed +- project scans and sizing operations: bounded to avoid whole-home stalls -- recently modified project artifacts are treated as recent for 7 days -- protected vendor and build-output heuristics prevent broad accidental deletions -- nested artifacts are filtered to avoid duplicate or parent-child over-deletion +## Release Integrity and Continuous Security Signals -Developer-cache cleanup preserves toolchains and other high-value state. Examples intentionally left alone include: +Mole treats release trust as part of its security posture, not just a packaging detail. -- `~/.cargo/bin` -- `~/.rustup` -- `~/.mix/archives` -- `~/.stack/programs` +Repository-level signals include: -## Protected Categories +- weekly Dependabot updates for Go modules and GitHub Actions +- CI checks for unsafe `rm -rf` usage patterns and core protection behavior +- targeted tests for path validation, purge boundaries, symlink behavior, dry-run flows, and destructive helpers +- CodeQL scanning for Go and GitHub Actions workflows +- curated changelog-driven release notes with a dedicated `Safety-related changes` section +- published SHA-256 checksums for release assets +- GitHub artifact attestations for release assets -Protected or conservatively handled categories include: +These controls do not eliminate all supply-chain risk, but they make release changes easier to review and verify. -- system components such as Control Center, System Settings, TCC, Spotlight, and `/Library/Updates` -- password managers and keychain-related data -- VPN / proxy tools such as Shadowsocks, V2Ray, Clash, and Tailscale -- AI tools in generic protected-data logic, including Cursor, Claude, ChatGPT, and Ollama -- `~/Library/Messages/Attachments` -- browser history and cookies -- Time Machine data while backup state is active or ambiguous -- `com.apple.*` LaunchAgents and LaunchDaemons +## Testing Coverage -## Analyzer - -`mo analyze` is intentionally lower-risk than cleanup flows: - -- it does not require sudo -- it respects normal user permissions and SIP -- interactive deletion requires an extra confirmation sequence -- deletions route through Trash/Finder behavior rather than direct permanent removal - -Code lives under `cmd/analyze/*.go`. - -## Timeouts and Hang Resistance - -`lib/core/timeout.sh` uses this fallback order: - -1. `gtimeout` / `timeout` -2. a Perl helper with process-group cleanup -3. a shell fallback - -Current notable timeouts in security-relevant paths: - -- orphan/Spotlight `mdfind` checks: 2s -- LaunchServices rebuild during uninstall: 10s / 15s bounded steps -- Homebrew uninstall cask flow: 300s default, extended to 600s or 900s for large apps -- Application Support sizing: direct file `stat`, bounded `du` for directories - -Additional safety behavior: - -- `brew_uninstall_cask()` treats exit code `124` as timeout failure and returns failure immediately -- font cache rebuild is skipped while browsers are running -- project-cache discovery and scans use strict timeouts to avoid whole-home stalls - -## User Configuration - -Protected paths can be added to `~/.config/mole/whitelist`, one path per line. - -Example: - -```bash -/Users/me/important-cache -~/Library/Application Support/MyApp -``` - -Exact path protection is preferred over pattern-style broad deletion rules. - -Use `--dry-run` before destructive operations when validating new cleanup behavior. - -## Testing - -There is no dedicated `tests/security.bats`. Security-relevant behavior is covered by targeted BATS suites, including: +There is no single `tests/security.bats` file. Instead, security-relevant behavior is covered by focused suites, including: +- `tests/core_safe_functions.bats` - `tests/clean_core.bats` - `tests/clean_user_core.bats` - `tests/clean_dev_caches.bats` - `tests/clean_system_maintenance.bats` - `tests/clean_apps.bats` - `tests/purge.bats` -- `tests/core_safe_functions.bats` +- `tests/installer.bats` - `tests/optimize.bats` -Local verification used for the current branch includes: +Key coverage areas include: -```bash -bats tests/clean_core.bats tests/clean_user_core.bats tests/clean_dev_caches.bats tests/clean_system_maintenance.bats tests/purge.bats tests/core_safe_functions.bats tests/clean_apps.bats tests/optimize.bats -bash -n lib/core/base.sh lib/clean/apps.sh tests/clean_apps.bats tests/optimize.bats -``` +- path validation rejects empty, relative, traversal, and system paths +- symlinked directories are rejected for destructive scans +- purge protects shallow or ambiguous paths and filters nested artifacts +- dry-run flows preview actions without applying them +- confirmation flows exist for high-risk interactive operations -CI additionally runs shell and Go validation on push. +## Known Limitations and Future Work -## Dependencies - -Primary Go dependencies are pinned in `go.mod`, including: - -- `github.com/charmbracelet/bubbletea v1.3.10` -- `github.com/charmbracelet/lipgloss v1.1.0` -- `github.com/shirou/gopsutil/v4 v4.26.2` -- `github.com/cespare/xxhash/v2 v2.3.0` - -System tooling relies mainly on Apple-provided binaries and standard macOS utilities such as: - -- `tmutil` -- `diskutil` -- `plutil` -- `launchctl` -- `osascript` -- `find` -- `stat` - -Dependency vulnerability status should be checked separately from this document. - -## Limitations - -- Cleanup is destructive. There is no undo. -- Generic orphan data waits 30 days before automatic cleanup. -- Claude VM orphan cleanup waits 7 days before automatic cleanup. -- Time Machine safety windows are hour-based, not day-based, and remain more conservative. +- Cleanup is destructive. Most cleanup and uninstall flows do not provide undo. +- `mo analyze` delete is safer because it uses Trash, but other cleanup flows are permanent once confirmed. +- Generic orphan data waits 30 days before cleanup; this is conservative but heuristic. +- Claude VM orphan cleanup waits 7 days before cleanup; this is also heuristic. +- Time Machine safety windows are hour-based and intentionally conservative. - Localized app names may still be missed in some heuristic paths, though bundle IDs are preferred where available. - Users who want immediate removal of app data should use explicit uninstall flows rather than waiting for orphan cleanup. +- Release signing and provenance signals are improving, but downstream package-manager trust also depends on external distribution infrastructure. +- Planned follow-up work includes stronger destructive-command threat modeling, more regression coverage for high-risk paths, and continued hardening of release integrity and disclosure workflow. + +For reporting procedures and supported versions, see [SECURITY.md](SECURITY.md). diff --git a/lib/core/file_ops.sh b/lib/core/file_ops.sh index 5c41618..82d70c2 100644 --- a/lib/core/file_ops.sh +++ b/lib/core/file_ops.sh @@ -92,7 +92,10 @@ validate_path_for_deletion() { # Validate resolved target against protected paths if [[ -n "$resolved_target" ]]; then case "$resolved_target" in - /System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*) + / | /System | /System/* | /bin | /bin/* | /sbin | /sbin/* | \ + /usr | /usr/bin | /usr/bin/* | /usr/lib | /usr/lib/* | \ + /etc | /etc/* | /private/etc | /private/etc/* | \ + /Library/Extensions | /Library/Extensions/*) log_error "Symlink points to protected system path: $path -> $resolved_target" return 1 ;; diff --git a/tests/core_safe_functions.bats b/tests/core_safe_functions.bats index 5805f04..a2ea495 100644 --- a/tests/core_safe_functions.bats +++ b/tests/core_safe_functions.bats @@ -66,6 +66,9 @@ teardown() { } @test "validate_path_for_deletion rejects system directories" { + run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/'" + [ "$status" -eq 1 ] + run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/System'" [ "$status" -eq 1 ] @@ -86,6 +89,15 @@ teardown() { [ "$status" -eq 1 ] } +@test "validate_path_for_deletion rejects symlink to protected system path" { + local link_path="$TEST_DIR/system-link" + ln -s "/System" "$link_path" + + run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '$link_path' 2>&1" + [ "$status" -eq 1 ] + [[ "$output" == *"protected system path"* ]] +} + @test "safe_remove successfully removes file" { local test_file="$TEST_DIR/test_file.txt" echo "test" > "$test_file" @@ -134,6 +146,22 @@ teardown() { [ "$status" -eq 1 ] } +@test "safe_sudo_remove refuses symlink paths" { + local target_dir="$TEST_DIR/real" + local link_dir="$TEST_DIR/link" + mkdir -p "$target_dir" + ln -s "$target_dir" "$link_dir" + + run bash -c " + source '$PROJECT_ROOT/lib/core/common.sh' + sudo() { return 0; } + export -f sudo + safe_sudo_remove '$link_dir' 2>&1 + " + [ "$status" -eq 1 ] + [[ "$output" == *"Refusing to sudo remove symlink"* ]] +} + @test "safe_find_delete rejects symlinked directory" { local real_dir="$TEST_DIR/real" local link_dir="$TEST_DIR/link"