You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 mo → Trash (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 root — windows/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 guard — windows/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 match — windows/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 generic — windows/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 check — windows/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 client — windows/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 parsing — windows/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 recursion — windows/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 Windows — windows/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 macOS — windows/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 off — windows/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.
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):
clean-list parse + size table (incl. a comma-decimal sample) — CleanListTests.swift ↔ CleanPreviewParserTests.cs. Closes the ParseSize culture gap for both.
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 placeholders — windows/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.appxmanifestDisplayName/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 diverge — windows/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.
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.
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 onfeat/windows-port.🔴 Safety / data-loss — Windows deletes permanently, with weak guards
macOS routes every removal through
mo→ Trash (recoverable). The Windows fallbacks useFile.Delete/Directory.Deletewith no Recycle Bin — every item below is permanent.1.
IsSafeDeletionTargetonly blocks paths that exactly equal a protected root —windows/Services/WindowsInstalledApplicationService.cs:427.C:\Windowsis blocked butC:\Windows\System32is not (.All(blocked => !Equals(fullPath, blocked))is exact-match only). Leftover paths come from the vendor-controlled registryInstallLocation+ app-name-derived folders, are pre-selected (LeftoverCandidate.IsSelected = true), andRemoveLeftoverdoesDirectory.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 guard —
windows/Assets/Mole/lib/core/base.ps1:186(+lib/core/file_ops.ps1:168).Test-ProtectedPathis blocklist-only and omits bare drive roots (C:\). If a cache path's env var ($env:LOCALAPPDATAetc.) is empty, the expansion roots at the drive andRemove-Item -Recurse -Forcecan 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 match —
windows/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)orSHFileOperation).5. Purge globs are too generic —
windows/Services/PurgeArtifactService.cs:350.build/dist/bin/obj/targetmatch hand-authored source/asset dirs (a committeddist/, a Gobin/with scripts), then get recursively deleted. Fix: require stronger artifact markers, not bare directory names.🟠 Supply-chain
.exewith no integrity check —windows/Assets/Mole/lib/core/tui_binaries.ps1:106.Restore-PrebuiltTuiBinarypulls a GitHub release asset viaInvoke-WebRequestand 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/callcan hang the client —windows/Services/LocalMcpServerService.cs:385. A{"confirm":"true"}(string) makesGetValue<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: wrapCallToolAsyncin try/catch and write a JSON-RPC error.8. Locale-sensitive number parsing —
windows/Services/CleanPreviewParser.cs:97(ParseSize) andwindows/ViewModels/DashboardViewModel.cs:552(ParseGpuPercent) usedouble.TryParsewithoutCultureInfo.InvariantCulture. On comma-decimal locales (de-DE, fr-FR),mo's1.5 GB→ 0 (undercounted reclaimable size on a destructive preview); GPU45,0%→ wrong chart value. Fix: passCultureInfo.InvariantCulture(also the formatters that emit.-decimal strings).9.
Clear-RecycleBininfinite recursion —windows/Assets/Mole/lib/clean/user.ps1:111. The custom function is namedClear-RecycleBinand callsClear-RecycleBinunqualified → resolves to itself, not the cmdlet. Recycle Bin never empties; can fault. Fix: callMicrosoft.PowerShell.Management\Clear-RecycleBin.🔵 Parity / docs honesty (mac ↔ Windows consistency)
10. "Junk cleanup ✅" is a stub on Windows —
windows/ViewModels/CleanupViewModel.cs:38. The GUI Clean tab'sScanAsync/CleanAsyncboth just print "Mole Windows is still being updated, stay tuned." —CleanPreviewParseris never used.README.md(comparison table) andBURROW_WINDOWS_ALIGNMENT.mdclaim a workingmo clean --dry-runpreview flow. Fix: either wire the GUI Clean tab toCleanPreviewParser(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 macOS —
windows/Services/LocalMcpServerService.cs. Windows omitsburrow_purge/burrow_installerand folds app listing into a singleburrow_uninstallwith anactionarg (macOS has them as distinct tools — seemacos/Sources/MCP.swift). An agent targeting "Burrow MCP" cross-platform callingburrow_purge/burrow_list_appsworks 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 off —
windows/Tools/McpStdioBridge/Program.cs. The bridge proxies to127.0.0.1:9277; if the (documented, supported) HTTP toggle is off, every tool call errors. macOS's--mcpis 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.ymlartifact path is correctly workspace-relative, the.iss/PowerShell scripts are$PSScriptRoot-anchored). One gap:.gitignoredoesn't ignore Windows build output — root.gitignoreonly has macOS patterns; nowindows/bin/,obj/,artifacts/,publish, and nowindows/.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:
IsSafeDeletionTarget_BlocksRoots(WindowsInstalledApplicationServiceTests.cs:86) only asserts the two exact-match cases (C:\,C:\Windows) — it never exercises a subdir likeC:\Windows\System32, so the bug is invisible to CI. Add subdir + under-profile cases.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.HttpListener, loopback, or origin. macOS tests exactly this (MCPEnvelopeTests.swift,MCPTests.swift). Largest untested attack surface on Windows.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
mooutput format and currently keep separate hand-copied fixtures. Recommendation: a repo-rootfixtures/dir of language-agnostic golden files (canonicalmooutput + 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):CleanListTests.swift↔CleanPreviewParserTests.cs. Closes theParseSizeculture gap for both.-32700/-32601/-32602,initialize/tools/list) —MCPEnvelopeTests.swift↔ (untested)LocalMcpServerService.mo history --jsonparse,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.
windows/Assets/(AppIcon.ico,Square150x150Logo,SplashScreen,StoreLogo, etc.) render the grey crossed-circle template glyph, none derived from the Mac mark.BurrowWin.csprojhas no<ApplicationIcon>, so the.exeships iconless.Package.appxmanifestDisplayName/Description are literallyBurrowWin. Fix: regenerate all tiles +.icofrom the Mac sourcemacos/Resources/Assets.xcassets/AppIcon.appiconset/icon_512@2x.png(1024²); add<ApplicationIcon>; set real "Burrow" name + brandedBackgroundColor.windows/Styles/BurrowTheme.xamlhas no peach accent (#D9A066), every metric hex differs from canonical (#57D58E / #E6A93C / #F2894E / #5AA8F0 / #F0604E), and fonts areGeorgia+Cascadia Monoinstead 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 indocs/assets/fonts/).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
IsSafeDeletionTargetprefix-check + Recycle-Bin routing.