Skip to content

Windows port review: data-loss & supply-chain fixes, parity/docs honesty, test gaps, brand assets (PR #92) #93

Description

@caezium

Tracking issue for the code review of the Windows port (#92, originally #90). Covers the WinUI app, the bundled PowerShell Mole engine, monorepo wiring, mac↔Windows consistency, the test suite, and brand assets.

Recommendation: do not merge #92 to a shipping build until the 🔴 destructive-delete and �� supply-chain items are resolved or gated. The read/preview paths are fine; the removal paths are the risk. File/line refs are against the windows/ tree on feat/windows-port.


🔴 Safety / data-loss — Windows deletes permanently, with weak guards

macOS routes every removal through moTrash (recoverable). The Windows fallbacks use File.Delete/Directory.Delete with no Recycle Bin — every item below is permanent.

  • 1. IsSafeDeletionTarget only blocks paths that exactly equal a protected rootwindows/Services/WindowsInstalledApplicationService.cs:427. C:\Windows is blocked but C:\Windows\System32 is not (.All(blocked => !Equals(fullPath, blocked)) is exact-match only). Leftover paths come from the vendor-controlled registry InstallLocation + app-name-derived folders, are pre-selected (LeftoverCandidate.IsSelected = true), and RemoveLeftover does Directory.Delete(recursive: true). A sloppy/malicious uninstall entry pointing into a shared system or user subfolder is permanently wiped on one confirm. Fix: change the guard to an is-under-protected-root (prefix) check, and don't default-select leftovers.

  • 2. Bundled engine has no drive-root guardwindows/Assets/Mole/lib/core/base.ps1:186 (+ lib/core/file_ops.ps1:168). Test-ProtectedPath is blocklist-only and omits bare drive roots (C:\). If a cache path's env var ($env:LOCALAPPDATA etc.) is empty, the expansion roots at the drive and Remove-Item -Recurse -Force can enumerate top-level entries. No allowlist/"must be under a known cache root" floor. Fix: reject drive roots + require an allowlisted base.

  • 3. Orphaned-AppData heuristic deletes by fuzzy name matchwindows/Assets/Mole/lib/clean/apps.ps1:156. Any %APPDATA%/%LOCALAPPDATA% folder whose name doesn't fuzzily match an installed program is treated as orphaned and recursively removed (>10 MB, >60 d). Portable tools, token/license stores, and project data routinely live in folders that never appear in the Uninstall registry → silent permanent loss of real user data. Fix: drop this heuristic, or make it preview-only and never auto-select.

  • 4. Permanent delete vs macOS recoverable Trash (systemic)InstallerCleanupService.cs:147, PurgeArtifactService.cs:364/369, WindowsInstalledApplicationService.cs:342/348. Same UI as mac, irreversible on Windows. Fix: route deletes through the Recycle Bin (Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(..., RecycleOption.SendToRecycleBin) or SHFileOperation).

  • 5. Purge globs are too genericwindows/Services/PurgeArtifactService.cs:350. build/dist/bin/obj/target match hand-authored source/asset dirs (a committed dist/, a Go bin/ with scripts), then get recursively deleted. Fix: require stronger artifact markers, not bare directory names.

🟠 Supply-chain

  • 6. Bundled engine downloads + executes an .exe with no integrity checkwindows/Assets/Mole/lib/core/tui_binaries.ps1:106. Restore-PrebuiltTuiBinary pulls a GitHub release asset via Invoke-WebRequest and the app runs it — no checksum/signature. Hijacked release asset or TLS MITM → RCE at install/first-run. Fix: pin + verify a SHA-256 before executing.

🟡 Correctness

  • 7. MCP REST /tools/call can hang the clientwindows/Services/LocalMcpServerService.cs:385. A {"confirm":"true"} (string) makes GetValue<bool>() throw; unlike the JSON-RPC path this has no try/catch, and it runs inside _ = Task.Run(...) → unobserved exception, response never written, client hangs until timeout. Fix: wrap CallToolAsync in try/catch and write a JSON-RPC error.

  • 8. Locale-sensitive number parsingwindows/Services/CleanPreviewParser.cs:97 (ParseSize) and windows/ViewModels/DashboardViewModel.cs:552 (ParseGpuPercent) use double.TryParse without CultureInfo.InvariantCulture. On comma-decimal locales (de-DE, fr-FR), mo's 1.5 GB → 0 (undercounted reclaimable size on a destructive preview); GPU 45,0% → wrong chart value. Fix: pass CultureInfo.InvariantCulture (also the formatters that emit .-decimal strings).

  • 9. Clear-RecycleBin infinite recursionwindows/Assets/Mole/lib/clean/user.ps1:111. The custom function is named Clear-RecycleBin and calls Clear-RecycleBin unqualified → resolves to itself, not the cmdlet. Recycle Bin never empties; can fault. Fix: call Microsoft.PowerShell.Management\Clear-RecycleBin.

🔵 Parity / docs honesty (mac ↔ Windows consistency)

  • 10. "Junk cleanup ✅" is a stub on Windowswindows/ViewModels/CleanupViewModel.cs:38. The GUI Clean tab's ScanAsync/CleanAsync both just print "Mole Windows is still being updated, stay tuned."CleanPreviewParser is never used. README.md (comparison table) and BURROW_WINDOWS_ALIGNMENT.md claim a working mo clean --dry-run preview flow. Fix: either wire the GUI Clean tab to CleanPreviewParser (the parser + its tests already exist), or soften the README's (Windows · beta) column to "partial — preview stub" so the beta column stays truthful.

  • 11. MCP tool set diverges from macOSwindows/Services/LocalMcpServerService.cs. Windows omits burrow_purge / burrow_installer and folds app listing into a single burrow_uninstall with an action arg (macOS has them as distinct tools — see macos/Sources/MCP.swift). An agent targeting "Burrow MCP" cross-platform calling burrow_purge/burrow_list_apps works on mac, errors on Windows. (The 5 read-only tools the README setup blurb names do match.) Fix: align tool names/shapes, or document the platform delta.

  • 12. stdio MCP dies when HTTP is offwindows/Tools/McpStdioBridge/Program.cs. The bridge proxies to 127.0.0.1:9277; if the (documented, supported) HTTP toggle is off, every tool call errors. macOS's --mcp is self-contained. Fix: make the bridge self-contained, or couple the toggle so disabling HTTP still leaves stdio MCP working.

⚙️ Monorepo structure — clean except one gap

The restructure wiring all resolves (sln/csproj/Tools/Tests references stay inside windows/, Assets\Mole\** content includes are project-relative, windows-ci.yml artifact path is correctly workspace-relative, the .iss/PowerShell scripts are $PSScriptRoot-anchored). One gap:

  • 13. .gitignore doesn't ignore Windows build output — root .gitignore only has macOS patterns; no windows/bin/, obj/, artifacts/, publish, and no windows/.gitignore. Hundreds of MB of build junk (incl. bundled-engine copies) is committable. (Restructure regression — being fixed in feat: Burrow for Windows (WinUI 3 / .NET 8 port) #92 directly.)

🧪 Test suite (21 files, ~60 cases — good style, critical gaps)

The suite favors real temp-filesystem round-trips over heavy mocking (good). Gaps:

  • 14. No test catches finding [codex] Add Simplified Chinese localization #1. IsSafeDeletionTarget_BlocksRoots (WindowsInstalledApplicationServiceTests.cs:86) only asserts the two exact-match cases (C:\, C:\Windows) — it never exercises a subdir like C:\Windows\System32, so the bug is invisible to CI. Add subdir + under-profile cases.
  • 15. No culture-invariant ParseSize / formatter test (finding feat(clean): show real freed-space in the Cleaned banner #8) — every formatter test asserts hard-coded dot-decimal strings under the dev machine's culture.
  • 16. MCP server + HTTP loopback gating: zero coverage. No test references MCP, HttpListener, loopback, or origin. macOS tests exactly this (MCPEnvelopeTests.swift, MCPTests.swift). Largest untested attack surface on Windows.
  • 17. Leftover removal "outside safe root" rejection untested — only the happy path (RemoveLeftoversAsync_RemovesSafeDirectory) is tested; the refusal branch never asserts.

Shared mac↔Windows tests (answering "can both run the same tests?")

They can't share code (Swift vs C#), but they run against the same mo output format and currently keep separate hand-copied fixtures. Recommendation: a repo-root fixtures/ dir of language-agnostic golden files (canonical mo output + expected parsed JSON), with a thin contract test in each language asserting its parser matches the golden. Highest-leverage seams (each is a place Windows currently has the worst coverage):

  1. clean-list parse + size table (incl. a comma-decimal sample) — CleanListTests.swiftCleanPreviewParserTests.cs. Closes the ParseSize culture gap for both.
  2. MCP JSON-RPC envelope goldens (-32700/-32601/-32602, initialize/tools/list) — MCPEnvelopeTests.swift ↔ (untested) LocalMcpServerService.
  3. ANSI stripping, mo history --json parse, mo status --json → metrics, treemap invariants.

Keep platform-specific: live OS state (startup inventory, SQLite routing, process sampling) and the genuinely-different safety policies.

🎨 Brand assets — Windows should reuse the Mac assets (it currently doesn't)

The original design is one brand; Windows currently ships default Visual Studio placeholder art and diverges on every color/font.

  • 18. Icons/tiles are VS template placeholderswindows/Assets/ (AppIcon.ico, Square150x150Logo, SplashScreen, StoreLogo, etc.) render the grey crossed-circle template glyph, none derived from the Mac mark. BurrowWin.csproj has no <ApplicationIcon>, so the .exe ships iconless. Package.appxmanifest DisplayName/Description are literally BurrowWin. Fix: regenerate all tiles + .ico from the Mac source macos/Resources/Assets.xcassets/AppIcon.appiconset/icon_512@2x.png (1024²); add <ApplicationIcon>; set real "Burrow" name + branded BackgroundColor.
  • 19. Palette + fonts divergewindows/Styles/BurrowTheme.xaml has no peach accent (#D9A066), every metric hex differs from canonical (#57D58E / #E6A93C / #F2894E / #5AA8F0 / #F0604E), and fonts are Georgia + Cascadia Mono instead of Cal Sans + Geist / Geist Mono. Fix: align hexes to the canonical palette (docs/index.html :root + macos/Sources/Brand.swift) and bundle the same fonts (already self-hosted in docs/assets/fonts/).
  • 20. Establish a shared source-of-truth. Add a repo-root brand/ dir (canonical 1024² icon + palette.json + fonts) that both macOS xcassets and Windows tiles/.ico derive from, so they can't drift again.

Suggested triage

Full review thread + verification in the PR. cc @Ltcc0 — flagging the data-loss + supply-chain items as the priority; happy to pair on the IsSafeDeletionTarget prefix-check + Recycle-Bin routing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions