new mac update for permission issues.

This commit is contained in:
KingKDot
2026-04-13 10:49:36 -05:00
parent 66393d5ba4
commit 792de1e22c
21 changed files with 480 additions and 29 deletions
+10 -4
View File
@@ -109,6 +109,9 @@ try {
try {
db.run(`ALTER TABLE clients ADD COLUMN elevation TEXT`);
} catch {}
try {
db.run(`ALTER TABLE clients ADD COLUMN permissions TEXT`);
} catch {}
db.run(`
CREATE TABLE IF NOT EXISTS banned_ips (
@@ -424,8 +427,8 @@ export function upsertClientRow(
) {
const now = partial.lastSeen ?? Date.now();
db.run(
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint, cpu, gpu, ram, is_admin, elevation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(?, 0), ?)
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint, cpu, gpu, ram, is_admin, elevation, permissions)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(?, 0), ?, ?)
ON CONFLICT(id) DO UPDATE SET
hwid=COALESCE(excluded.hwid, clients.hwid),
role=COALESCE(excluded.role, clients.role),
@@ -450,7 +453,8 @@ export function upsertClientRow(
gpu=COALESCE(excluded.gpu, clients.gpu),
ram=COALESCE(excluded.ram, clients.ram),
is_admin=COALESCE(excluded.is_admin, clients.is_admin),
elevation=COALESCE(excluded.elevation, clients.elevation)
elevation=COALESCE(excluded.elevation, clients.elevation),
permissions=COALESCE(excluded.permissions, clients.permissions)
`,
partial.id,
partial.hwid ?? partial.id,
@@ -477,6 +481,7 @@ export function upsertClientRow(
partial.ram ?? null,
partial.isAdmin !== undefined ? (partial.isAdmin ? 1 : 0) : null,
partial.elevation ?? null,
partial.permissions ? JSON.stringify(partial.permissions) : null,
);
if (partial.hwid) {
@@ -736,7 +741,7 @@ export function listClients(filters: ListFilters): ListResult {
const rows = db
.query<any>(
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram, is_admin as isAdmin, elevation, disconnect_reason as disconnectReason, disconnect_detail as disconnectDetail
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram, is_admin as isAdmin, elevation, permissions, disconnect_reason as disconnectReason, disconnect_detail as disconnectDetail
FROM clients
${whereSql}
${orderBy}
@@ -770,6 +775,7 @@ export function listClients(filters: ListFilters): ListResult {
ram: c.ram || null,
isAdmin: c.isAdmin === 1,
elevation: c.elevation || null,
permissions: c.permissions ? (() => { try { return JSON.parse(c.permissions); } catch { return null; } })() : null,
disconnectReason: c.disconnectReason || null,
disconnectDetail: c.disconnectDetail || null,
thumbnail: getThumbnail(c.id),
+1
View File
@@ -30,6 +30,7 @@ export function handleClientsRequest(req: Request): Response {
...item,
isAdmin: live.isAdmin ?? item.isAdmin,
elevation: live.elevation ?? item.elevation,
permissions: live.permissions ?? item.permissions,
...(live.monitorInfo?.length ? {
monitors: live.monitorInfo.length,
monitorInfo: live.monitorInfo,
@@ -666,6 +666,50 @@ export async function handleClientRoutes(
details: "Agent uninstall requested - persistence will be removed",
success: true,
});
} else if (action === "elevate") {
if (user.role !== "admin") {
return new Response("Forbidden: Admin access required", { status: 403 });
}
const password = typeof body?.password === "string" ? body.password : "";
if (!password) {
return new Response("Bad request: password required", { status: 400 });
}
const cmdId = uuidv4();
const replyPromise: Promise<{ ok: boolean; message?: string }> = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
deps.pendingCommandReplies.delete(cmdId);
reject(new Error("Elevation timed out"));
}, 30_000);
deps.pendingCommandReplies.set(cmdId, { resolve, reject, timeout, clientId: targetId });
});
target.ws.send(
encodeMessage({
type: "command",
commandType: "elevate",
id: cmdId,
payload: { password },
}),
);
metrics.recordCommand("elevate");
logAudit({
timestamp: Date.now(),
username: user.username,
ip,
action: AuditAction.COMMAND,
targetClientId: targetId,
success: true,
details: "elevate (sudo)",
});
try {
const result = await replyPromise;
return Response.json({ ok: result.ok, message: result.message || "" }, { headers: deps.CORS_HEADERS });
} catch (error: any) {
return Response.json({ ok: false, error: error.message || "Elevation failed" }, { status: 504 });
}
} else {
success = false;
return new Response("Bad request", { status: 400 });
@@ -314,6 +314,21 @@ export function handleRemoteDesktopViewerMessage(ws: ServerWebSocket<SocketData>
switch (payload.type) {
case "desktop_start":
if (!state.isStreaming) {
if (target.os === "darwin" && target.permissions) {
const missing: string[] = [];
if (!target.permissions.screenRecording) missing.push("screenRecording");
if (!target.permissions.accessibility) missing.push("accessibility");
if (missing.length > 0) {
logger.info(`[rd] macOS permission gate: client ${clientId} missing ${missing.join(", ")}`);
safeSendViewer(ws, {
type: "status",
status: "permissions_denied",
missing,
permissions: target.permissions,
});
break;
}
}
sendDesktopCommand(target, "desktop_start", {});
state.isStreaming = true;
rdStreamingState.set(clientId, state);
+1
View File
@@ -30,6 +30,7 @@ export type ClientInfo = {
ram?: string;
isAdmin?: boolean;
elevation?: string;
permissions?: Record<string, boolean>;
enrollmentStatus?: EnrollmentStatus;
publicKey?: string;
keyFingerprint?: string;
+4
View File
@@ -91,6 +91,9 @@ export function handleHello(
info.inMemory = !!(payload as any).inMemory;
info.isAdmin = !!(payload as any).isAdmin;
info.elevation = typeof (payload as any).elevation === "string" ? (payload as any).elevation : info.elevation;
if ((payload as any).permissions && typeof (payload as any).permissions === "object") {
info.permissions = (payload as any).permissions;
}
info.cpu = (payload as any).cpu || info.cpu;
info.gpu = (payload as any).gpu || info.gpu;
info.ram = (payload as any).ram || info.ram;
@@ -121,6 +124,7 @@ export function handleHello(
ram: info.ram,
isAdmin: info.isAdmin,
elevation: info.elevation,
permissions: info.permissions,
lastSeen: info.lastSeen,
online: 1,
});