mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +00:00
fix(windows): address code review security and reliability issues
- Add protection checks to Go analyze tool before delete operations - Use try/finally to ensure Windows Update service restarts after cleanup - Don't count interactive uninstall as automatic success - Tighten orphaned app detection with stricter prefix matching
This commit is contained in:
@@ -523,10 +523,10 @@ function Uninstall-SelectedApps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $uninstalled) {
|
if (-not $uninstalled) {
|
||||||
# Fallback to interactive
|
# Fallback to interactive - don't count as automatic success
|
||||||
Write-Host " $esc[33m(launching uninstaller)$esc[0m"
|
Write-Host " $esc[33m(launching uninstaller - verify completion manually)$esc[0m"
|
||||||
Start-Process -FilePath "cmd.exe" -ArgumentList "/c", "`"$uninstallString`"" -Wait
|
Start-Process -FilePath "cmd.exe" -ArgumentList "/c", "`"$uninstallString`"" -Wait
|
||||||
$successCount++
|
# Note: Not incrementing $successCount since we can't verify if user completed or cancelled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
windows/cmd/analyze/analyze.exe
Normal file
BIN
windows/cmd/analyze/analyze.exe
Normal file
Binary file not shown.
@@ -89,6 +89,59 @@ var skipPatterns = map[string]bool{
|
|||||||
"Config.Msi": true,
|
"Config.Msi": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protected paths that should NEVER be deleted
|
||||||
|
var protectedPaths = []string{
|
||||||
|
`C:\Windows`,
|
||||||
|
`C:\Program Files`,
|
||||||
|
`C:\Program Files (x86)`,
|
||||||
|
`C:\ProgramData`,
|
||||||
|
`C:\Users\Default`,
|
||||||
|
`C:\Users\Public`,
|
||||||
|
`C:\Recovery`,
|
||||||
|
`C:\System Volume Information`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// isProtectedPath checks if a path is protected from deletion
|
||||||
|
func isProtectedPath(path string) bool {
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return true // If we can't resolve the path, treat it as protected
|
||||||
|
}
|
||||||
|
absPath = strings.ToLower(absPath)
|
||||||
|
|
||||||
|
// Check against protected paths
|
||||||
|
for _, protected := range protectedPaths {
|
||||||
|
protectedLower := strings.ToLower(protected)
|
||||||
|
if absPath == protectedLower || strings.HasPrefix(absPath, protectedLower+`\`) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against skip patterns (system directories)
|
||||||
|
baseName := strings.ToLower(filepath.Base(absPath))
|
||||||
|
for pattern := range skipPatterns {
|
||||||
|
if strings.ToLower(pattern) == baseName {
|
||||||
|
// Only protect if it's at a root level (e.g., C:\Windows, not C:\Projects\Windows)
|
||||||
|
parent := filepath.Dir(absPath)
|
||||||
|
if len(parent) <= 3 { // e.g., "C:\"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect Windows directory itself
|
||||||
|
winDir := strings.ToLower(os.Getenv("WINDIR"))
|
||||||
|
sysRoot := strings.ToLower(os.Getenv("SYSTEMROOT"))
|
||||||
|
if winDir != "" && (absPath == winDir || strings.HasPrefix(absPath, winDir+`\`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if sysRoot != "" && (absPath == sysRoot || strings.HasPrefix(absPath, sysRoot+`\`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Entry types
|
// Entry types
|
||||||
type dirEntry struct {
|
type dirEntry struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -462,9 +515,17 @@ func (m model) scanPath(path string) tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletePath deletes a file or directory
|
// deletePath deletes a file or directory with protection checks
|
||||||
func (m model) deletePath(path string) tea.Cmd {
|
func (m model) deletePath(path string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
// Safety check: never delete protected paths
|
||||||
|
if isProtectedPath(path) {
|
||||||
|
return deleteCompleteMsg{
|
||||||
|
path: path,
|
||||||
|
err: fmt.Errorf("cannot delete protected system path: %s", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := os.RemoveAll(path)
|
err := os.RemoveAll(path)
|
||||||
return deleteCompleteMsg{path: path, err: err}
|
return deleteCompleteMsg{path: path, err: err}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,10 +88,23 @@ function Find-OrphanedAppData {
|
|||||||
# Skip if recently modified
|
# Skip if recently modified
|
||||||
if ($folder.LastWriteTime -gt $cutoffDate) { continue }
|
if ($folder.LastWriteTime -gt $cutoffDate) { continue }
|
||||||
|
|
||||||
# Check if app is installed
|
# Check if app is installed using stricter matching
|
||||||
|
# Require exact match or that folder name is a clear prefix/suffix of app name
|
||||||
$isInstalled = $false
|
$isInstalled = $false
|
||||||
|
$folderLower = $folder.Name.ToLower()
|
||||||
foreach ($name in $installedNames) {
|
foreach ($name in $installedNames) {
|
||||||
if ($name -like "*$($folder.Name.ToLower())*" -or $folder.Name.ToLower() -like "*$name*") {
|
# Exact match
|
||||||
|
if ($name -eq $folderLower) {
|
||||||
|
$isInstalled = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
# Folder is prefix of app name (e.g., "chrome" matches "chrome browser")
|
||||||
|
if ($name.StartsWith($folderLower) -and $folderLower.Length -ge 4) {
|
||||||
|
$isInstalled = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
# App name is prefix of folder (e.g., "vscode" matches "vscode-data")
|
||||||
|
if ($folderLower.StartsWith($name) -and $name.Length -ge 4) {
|
||||||
$isInstalled = $true
|
$isInstalled = $true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,22 +121,25 @@ function Clear-WindowsUpdateFiles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean download cache
|
try {
|
||||||
$wuDownloadPath = "$env:WINDIR\SoftwareDistribution\Download"
|
# Clean download cache
|
||||||
if (Test-Path $wuDownloadPath) {
|
$wuDownloadPath = "$env:WINDIR\SoftwareDistribution\Download"
|
||||||
Clear-DirectoryContents -Path $wuDownloadPath -Description "Windows Update download cache"
|
if (Test-Path $wuDownloadPath) {
|
||||||
|
Clear-DirectoryContents -Path $wuDownloadPath -Description "Windows Update download cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean DataStore (old update history - be careful!)
|
||||||
|
# Only clean temp files, not the actual database
|
||||||
|
$wuDataStore = "$env:WINDIR\SoftwareDistribution\DataStore\Logs"
|
||||||
|
if (Test-Path $wuDataStore) {
|
||||||
|
Clear-DirectoryContents -Path $wuDataStore -Description "Windows Update logs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
# Clean DataStore (old update history - be careful!)
|
# Always restart service if it was running, even if cleanup failed
|
||||||
# Only clean temp files, not the actual database
|
if ($wasRunning) {
|
||||||
$wuDataStore = "$env:WINDIR\SoftwareDistribution\DataStore\Logs"
|
Start-Service -Name wuauserv -ErrorAction SilentlyContinue
|
||||||
if (Test-Path $wuDataStore) {
|
}
|
||||||
Clear-DirectoryContents -Path $wuDataStore -Description "Windows Update logs"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restart service if it was running
|
|
||||||
if ($wasRunning) {
|
|
||||||
Start-Service -Name wuauserv -ErrorAction SilentlyContinue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user