//go:build linux package blocker import ( "fmt" "log" "os" "path/filepath" "strconv" "strings" ) var systemAllowed = map[string]bool{ "gnome-shell": true, "mutter": true, "xterm": true, "gnome-terminal": true, "konsole": true, "ptyxis": true, // Ubuntu terminal "bash": true, "zsh": true, "sh": true, "wails": true, "code": true, "cursor": true, "windsurf": true, "goland": true, "idea": true, "client": true, "simple_care_v1.0": true, "xwayland": true, "mutter-x11-fram": true, "ibus-x11": true, "ibus-daemon": true, "gsd-": true, // prefix "ibus-": true, // prefix "xdg-": true, // prefix "at-spi": true, // prefix "evolution-": true, // prefix "goa-daemon": true, "gjs": true, "snapd-": true, // prefix "systemd": true, "webkit": true, // WebKit subprocesses "glycin": true, // GNOME image helper "zenity": true, // Dialog utility "rustdesk": true, // Remote support safety "rustdesk-bin": true, "anydesk": true, "teamviewer": true, "remmina": true, } func isSystemAllowed(name string) bool { name = strings.ToLower(name) if systemAllowed[name] { return true } for pattern := range systemAllowed { if strings.HasPrefix(name, pattern) && pattern != name { return true } } return false } func getPPID(pid uint32) uint32 { statusBytes, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)) if err != nil { return 0 } lines := strings.Split(string(statusBytes), "\n") for _, line := range lines { if strings.HasPrefix(line, "PPid:") { parts := strings.Fields(line) if len(parts) >= 2 { if ppid, err := strconv.ParseUint(parts[1], 10, 32); err == nil { return uint32(ppid) } } } } return 0 } func isDescendantOf(pid, targetPid uint32) bool { curr := pid for i := 0; i < 10; i++ { // limits lookup to 10 ancestor levels ppid := getPPID(curr) if ppid == 0 { return false } if ppid == targetPid { return true } curr = ppid } return false } type ProcessInfo struct { PID uint32 Name string } func getVisibleProcesses() (map[uint32]ProcessInfo, error) { files, err := os.ReadDir("/proc") if err != nil { return nil, err } procs := make(map[uint32]ProcessInfo) for _, f := range files { if !f.IsDir() { continue } pid, err := strconv.ParseUint(f.Name(), 10, 32) if err != nil { continue } mapsPath := fmt.Sprintf("/proc/%d/maps", pid) mapsBytes, err := os.ReadFile(mapsPath) if err != nil { // Skip processes we don't own (permission denied) continue } mapsStr := string(mapsBytes) isGUI := strings.Contains(mapsStr, "libgtk") || strings.Contains(mapsStr, "libQt") || strings.Contains(mapsStr, "libX11") || strings.Contains(mapsStr, "libwayland-client") if !isGUI { continue } // Read process name from /proc/PID/comm commBytes, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)) if err != nil { continue } procName := strings.TrimSpace(string(commBytes)) if procName != "" { procs[uint32(pid)] = ProcessInfo{ PID: uint32(pid), Name: procName, } } } 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, our sub-processes, system/critical developer tools, or agent helpers isOurSubprocess := pid == myPid || isDescendantOf(pid, myPid) isAntigravity := strings.Contains(pNameLower, "antigravity") if isOurSubprocess || (currentExec != "" && pNameLower == currentExec) || isSystemAllowed(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) } } } } }