All checks were successful
Deploy on Master Change / deploy (push) Successful in 1m16s
302 lines
13 KiB
Go
302 lines
13 KiB
Go
//go:build darwin
|
|
|
|
package blocker
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
var systemAllowed = map[string]bool{
|
|
// ── Core macOS Desktop Infrastructure ─────────────────────────────────────
|
|
"finder": true, // macOS file manager / desktop
|
|
"dock": true, // macOS Dock — kill = dock disappears
|
|
"windowserver": true, // WindowServer — kill = instant logout
|
|
"loginwindow": true, // Login/session manager — kill = logout
|
|
"systemuiserver": true, // Menu bar icons (volume, wifi, battery, clock)
|
|
"controlcenter": true, // macOS Control Center (Monterey+)
|
|
"notificationcenter": true, // Notification Center
|
|
"spotlight": true, // Spotlight search
|
|
"launchpad": true,
|
|
"mission control": true,
|
|
"exposé": true,
|
|
"universalaccessd": true,
|
|
"accessibilityuiagent": true, // Accessibility helper
|
|
|
|
// ── Input Methods & Language (critical — kill = can't type) ───────────────
|
|
"inputmethodkit": true,
|
|
"ibus": true,
|
|
"hiragana kakomi input": true,
|
|
"kinput2": true,
|
|
"squirrel": true, // Rime input method
|
|
"scim": true,
|
|
"kotoeri": true, // Japanese IME
|
|
"pinyin - simplified": true, // macOS Chinese Pinyin
|
|
"zhuyin - traditional": true,
|
|
"vietnamese": true, // macOS built-in Vietnamese IME
|
|
"abc": true, // macOS ABC keyboard input
|
|
|
|
// ── Security / Keychain / Authentication ──────────────────────────────────
|
|
"securityagent": true, // macOS security agent — kill breaks sudo GUI, Keychain prompts
|
|
"keychain": true, // Keychain access
|
|
"keychainservicesagent": true,
|
|
"trustd": true,
|
|
"opendirectoryd": true,
|
|
"authorizationhost": true, // Authorization host — UAC equivalent
|
|
"securityd": true,
|
|
"coreauthenticationd": true,
|
|
"biometricd": true,
|
|
"touchidd": true,
|
|
|
|
// ── Security utilities / AV (from app_pool + common) ──────────────────────
|
|
"activity monitor": true, // System monitor (app_pool)
|
|
"passwords": true, // Apple Passwords / iCloud Keychain (app_pool)
|
|
"xprotectservice": true, // macOS built-in malware protection
|
|
"xprotect": true,
|
|
"malware removal tool": true,
|
|
"mrt": true,
|
|
"avast": true,
|
|
"avast security": true,
|
|
"bitdefender": true,
|
|
"norton": true,
|
|
"sophos": true,
|
|
"malwarebytes": true,
|
|
"eset": true,
|
|
"little snitch": true,
|
|
"lulu": true,
|
|
|
|
// ── Audio / Media ─────────────────────────────────────────────────────────
|
|
"coreaudiod": true, // Core Audio daemon — kill = no sound
|
|
"audioundockhelper": true,
|
|
"audio midi setup": true,
|
|
"noiseremoval": true,
|
|
|
|
// ── Networking / VPN ──────────────────────────────────────────────────────
|
|
"networkd": true,
|
|
"nesessionmanager": true, // Network Extension — kill drops VPN
|
|
"scutil": true,
|
|
"configd": true,
|
|
"mDNSResponder": true, // Bonjour DNS
|
|
|
|
// ── Spotlight / File Indexing ──────────────────────────────────────────────
|
|
"mds": true, // Spotlight metadata server
|
|
"mds_stores": true,
|
|
"mdworker": true, // prefix match covers mdworker_shared
|
|
"mdworker_shared": true,
|
|
|
|
// ── iCloud / Apple Services ───────────────────────────────────────────────
|
|
"bird": true, // iCloud Drive daemon
|
|
"cloudd": true,
|
|
"com.apple.icloud": true, // prefix
|
|
"cloudphotod": true,
|
|
"nsurlsessiond": true,
|
|
|
|
// ── System Preferences / Settings ─────────────────────────────────────────
|
|
"system preferences": true, // macOS System Preferences (pre-Ventura)
|
|
"system settings": true, // macOS System Settings (Ventura+)
|
|
"software update": true,
|
|
"app store": true,
|
|
|
|
// ── Screen / Display ──────────────────────────────────────────────────────
|
|
"screensaver engine": true, // Screensaver
|
|
"com.apple.screensaver": true,
|
|
"colorsyncd": true,
|
|
"colorsync utility": true,
|
|
"nightshift": true,
|
|
"display menu": true,
|
|
|
|
// ── Clipboard / Pasteboard ────────────────────────────────────────────────
|
|
"pboard": true, // Pasteboard daemon — kill breaks copy/paste
|
|
|
|
// ── Printing ─────────────────────────────────────────────────────────────
|
|
"printingproxy": true,
|
|
"cupsd": true,
|
|
|
|
// ── Crash Reporting / Diagnostics ─────────────────────────────────────────
|
|
"crashreporter": true,
|
|
"diagnosticsd": true,
|
|
"spindump": true,
|
|
"reportmemoryexception": true,
|
|
|
|
// ── Webkit / App subprocesses ─────────────────────────────────────────────
|
|
"webkit": true, // prefix
|
|
"com.apple.webkit": true, // prefix
|
|
"com.apple.webkit.networking": true,
|
|
|
|
// ── Remote support ────────────────────────────────────────────────────────
|
|
"applescriptkit": true,
|
|
"applescript runner": true,
|
|
"rustdesk": true,
|
|
"anydesk": true,
|
|
"teamviewer": true,
|
|
"screen sharing": true,
|
|
"screensharingd": true, // macOS Screen Sharing
|
|
|
|
// ── Terminals ─────────────────────────────────────────────────────────────
|
|
"terminal": true, // macOS Terminal
|
|
"iterm": true,
|
|
"iterm2": true,
|
|
"wezterm": true,
|
|
"kitty": true,
|
|
"alacritty": true,
|
|
"hyper": true,
|
|
|
|
// ── Shells ────────────────────────────────────────────────────────────────
|
|
"bash": true,
|
|
"zsh": true,
|
|
"sh": true,
|
|
"fish": true,
|
|
|
|
// ── AppleScript / Automation ──────────────────────────────────────────────
|
|
"system events": true, // AppleScript System Events (used by our blocker itself)
|
|
"osascript": true, // AppleScript runner (used by our getVisibleProcesses)
|
|
|
|
// ── Git & Credential Helpers ──────────────────────────────────────────────
|
|
"git": true,
|
|
"git-credential-manager": true,
|
|
"git-credential-osxkeychain": true,
|
|
"github desktop": true,
|
|
"sourcetree": true,
|
|
"fork": true,
|
|
|
|
// ── Docker ───────────────────────────────────────────────────────────────
|
|
"docker": true,
|
|
"docker desktop": true,
|
|
"com.docker": true, // prefix
|
|
|
|
// ── Our app + IDE/dev tools ────────────────────────────────────────────────
|
|
"client": true,
|
|
"simple_care_v1.0": true,
|
|
"simple_care_v1.1": true,
|
|
"simple_care": true,
|
|
"wails": true,
|
|
"code": true, // VSCode
|
|
"cursor": true,
|
|
"windsurf": true,
|
|
"goland": true,
|
|
"idea": true,
|
|
"clion": true,
|
|
"webstorm": true,
|
|
"pycharm": true,
|
|
"rider": true,
|
|
"studio": true, // Android Studio
|
|
"eclipse": true,
|
|
"sublime text": true,
|
|
}
|
|
type ProcessInfo struct {
|
|
Name string
|
|
BundleID string
|
|
}
|
|
|
|
func getVisibleProcesses() (map[uint32]ProcessInfo, error) {
|
|
script := `tell application "System Events"
|
|
set out to ""
|
|
set procList to every process whose visible is true
|
|
repeat with p in procList
|
|
try
|
|
set nameStr to name of p
|
|
set pidVal to unix id of p
|
|
set bid to bundle identifier of p
|
|
if bid is missing value then
|
|
set bid to ""
|
|
end if
|
|
set out to out & nameStr & "|" & pidVal & "|" & bid & "\n"
|
|
on error
|
|
-- ignore
|
|
end try
|
|
end repeat
|
|
return out
|
|
end tell`
|
|
cmd := exec.Command("osascript", "-e", script)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
procs := make(map[uint32]ProcessInfo)
|
|
lines := strings.Split(string(out), "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.Split(line, "|")
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
pName := parts[0]
|
|
pIdStr := parts[1]
|
|
bundleID := ""
|
|
if len(parts) >= 3 {
|
|
bundleID = parts[2]
|
|
}
|
|
var pid uint32
|
|
if _, err := fmt.Sscanf(pIdStr, "%d", &pid); err == nil {
|
|
procs[pid] = ProcessInfo{
|
|
Name: pName,
|
|
BundleID: bundleID,
|
|
}
|
|
}
|
|
}
|
|
return procs, nil
|
|
}
|
|
|
|
func (b *Blocker) checkAndKill() {
|
|
b.mu.Lock()
|
|
keywords := make([]string, len(b.allowedKeywords))
|
|
copy(keywords, b.allowedKeywords)
|
|
b.mu.Unlock()
|
|
|
|
if len(keywords) == 0 {
|
|
return
|
|
}
|
|
|
|
currentExec := ""
|
|
if execPath, err := os.Executable(); err == nil {
|
|
currentExec = strings.ToLower(filepath.Base(execPath))
|
|
}
|
|
|
|
procs, err := getVisibleProcesses()
|
|
if err != nil {
|
|
log.Printf("[BLOCKER] Failed to get visible processes: %v", err)
|
|
return
|
|
}
|
|
|
|
myPid := uint32(os.Getpid())
|
|
for pid, info := range procs {
|
|
pNameLower := strings.ToLower(info.Name)
|
|
|
|
// 1. Always allow our app, system/critical developer tools, or agent helpers
|
|
if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] {
|
|
continue
|
|
}
|
|
|
|
// 2. Check if the process name contains any allowed keywords
|
|
allowed := false
|
|
for _, kw := range keywords {
|
|
if matchesAllowedKeyword(kw, pNameLower, pNameLower) {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// 3. If not allowed, kill the application
|
|
if !allowed {
|
|
if b.OnBlocked != nil {
|
|
b.OnBlocked(info.Name, info.Name)
|
|
}
|
|
log.Printf("[BLOCKER] KILLED unauthorized application: %s (PID: %d)", info.Name, pid)
|
|
proc, err := os.FindProcess(int(pid))
|
|
if err == nil {
|
|
errKill := proc.Kill()
|
|
if errKill == nil && b.OnKill != nil {
|
|
b.OnKill(info.Name, info.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|