Files
rikkei_simple_care/client/internal/blocker/blocker_darwin.go
2026-07-01 12:43:09 +07:00

157 lines
3.5 KiB
Go

//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)
}
}
}
}
}