//go:build darwin package blocker import ( "fmt" "log" "os" "os/exec" "path/filepath" "strings" ) var systemAllowed = map[string]bool{ "finder": true, "terminal": true, "iterm": true, "iterm2": true, "bash": true, "zsh": true, "sh": true, "wails": true, "code": true, "cursor": true, "windsurf": true, "goland": true, "idea": true, "clion": true, "webstorm": true, "pycharm": true, "rider": true, "studio": true, "eclipse": true, "sublime_text": true, "git": true, "docker": true, "system events": true, "osascript": true, "client": true, "simple_care_v1.0": 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) bundleIDLower := strings.ToLower(info.BundleID) // 1. Always allow our app, system/critical developer tools, or agent helpers (and Antigravity IDE) isAntigravity := strings.Contains(pNameLower, "antigravity") || strings.Contains(bundleIDLower, "antigravity") if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] || isAntigravity { 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) } } } } }