203 lines
6.0 KiB
JavaScript
203 lines
6.0 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
|
|
const SAFE_DISMISS_BUTTONS = [
|
|
"稍后",
|
|
"跳过",
|
|
"以后再说",
|
|
"不,谢谢",
|
|
"Not now",
|
|
"Skip",
|
|
"Later",
|
|
"Cancel",
|
|
"Maybe later",
|
|
"No thanks",
|
|
];
|
|
|
|
const BLOCKED_TEXT_PATTERNS = [
|
|
/screen recording/i,
|
|
/accessibility/i,
|
|
/input monitoring/i,
|
|
/full disk access/i,
|
|
/keychain/i,
|
|
/administrator/i,
|
|
/apple id/i,
|
|
/user account control/i,
|
|
/make changes to your device/i,
|
|
/屏幕录制/,
|
|
/辅助功能/,
|
|
/输入监控/,
|
|
/完整磁盘访问/,
|
|
/钥匙串/,
|
|
/管理员密码/,
|
|
/用户帐户控制/,
|
|
/用户账户控制/,
|
|
];
|
|
|
|
export function normalizeDialogText(value) {
|
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
export function normalizeDialogSnapshot(input = {}) {
|
|
const buttons = Array.isArray(input.buttons)
|
|
? input.buttons.map(normalizeDialogText).filter(Boolean)
|
|
: [];
|
|
const appName = normalizeDialogText(input.appName || input.app || "Unknown App");
|
|
return {
|
|
platform: normalizeDialogText(input.platform || process.platform || "unknown"),
|
|
deviceId: normalizeDialogText(input.deviceId || "unknown-device"),
|
|
appName,
|
|
appBundleId: normalizeDialogText(input.appBundleId || input.appId || appName || "unknown-app"),
|
|
title: normalizeDialogText(input.title),
|
|
text: normalizeDialogText(input.text),
|
|
buttons,
|
|
raw: input.raw,
|
|
};
|
|
}
|
|
|
|
function parseSnapshotJson(raw, sourceName) {
|
|
const value = String(raw || "").trim();
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
const parsed = JSON.parse(value);
|
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
throw new Error(`INVALID_DIALOG_GUARD_SNAPSHOT:${sourceName}`);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
export function readDialogSnapshotFromEnv(env = process.env, platform = process.platform) {
|
|
const normalizedPlatform = normalizeDialogText(platform || process.platform || "unknown");
|
|
const platformSnapshotKey =
|
|
normalizedPlatform === "darwin"
|
|
? "BOSS_MAC_DIALOG_GUARD_SNAPSHOT_JSON"
|
|
: normalizedPlatform === "win32"
|
|
? "BOSS_WINDOWS_DIALOG_GUARD_SNAPSHOT_JSON"
|
|
: "";
|
|
const parsed =
|
|
parseSnapshotJson(env.BOSS_DIALOG_GUARD_SNAPSHOT_JSON, "BOSS_DIALOG_GUARD_SNAPSHOT_JSON") ||
|
|
(platformSnapshotKey ? parseSnapshotJson(env[platformSnapshotKey], platformSnapshotKey) : undefined);
|
|
if (!parsed) {
|
|
return undefined;
|
|
}
|
|
return normalizeDialogSnapshot({
|
|
...parsed,
|
|
platform: parsed.platform || normalizedPlatform,
|
|
});
|
|
}
|
|
|
|
function hash(value) {
|
|
return createHash("sha256").update(String(value || "")).digest("hex").slice(0, 16);
|
|
}
|
|
|
|
export function createDialogSignature(snapshotInput = {}) {
|
|
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
|
const titleHash = hash(snapshot.title.toLowerCase());
|
|
const textHash = hash(snapshot.text.toLowerCase());
|
|
const buttonHash = hash(snapshot.buttons.join("|").toLowerCase());
|
|
return {
|
|
id: hash([
|
|
snapshot.platform,
|
|
snapshot.deviceId,
|
|
snapshot.appBundleId,
|
|
titleHash,
|
|
textHash,
|
|
buttonHash,
|
|
].join("|")),
|
|
scopeKey: [snapshot.platform, snapshot.deviceId, snapshot.appBundleId].join(":"),
|
|
platform: snapshot.platform,
|
|
deviceId: snapshot.deviceId,
|
|
appBundleId: snapshot.appBundleId,
|
|
titleHash,
|
|
textHash,
|
|
buttonHash,
|
|
};
|
|
}
|
|
|
|
function textMatchesAny(text, patterns) {
|
|
return patterns.some((pattern) => pattern.test(text));
|
|
}
|
|
|
|
function findSafeDismissButton(buttons) {
|
|
return buttons.find((button) =>
|
|
SAFE_DISMISS_BUTTONS.some((candidate) => candidate.toLowerCase() === button.toLowerCase()),
|
|
);
|
|
}
|
|
|
|
function isBlockedPrompt(snapshot) {
|
|
const combined = `${snapshot.title} ${snapshot.text}`;
|
|
return textMatchesAny(combined, BLOCKED_TEXT_PATTERNS);
|
|
}
|
|
|
|
export function evaluateDialogSnapshot(snapshotInput = {}) {
|
|
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
|
const signature = createDialogSignature(snapshot);
|
|
if (isBlockedPrompt(snapshot)) {
|
|
return {
|
|
disposition: "needs_user_action",
|
|
kind: "permission_required",
|
|
risk: "high",
|
|
action: "pause_for_user",
|
|
signature,
|
|
};
|
|
}
|
|
|
|
const safeButton = findSafeDismissButton(snapshot.buttons);
|
|
if (safeButton) {
|
|
return {
|
|
disposition: "auto_action",
|
|
kind: "safe_dismiss",
|
|
risk: "low",
|
|
action: "click_button",
|
|
button: safeButton,
|
|
signature,
|
|
};
|
|
}
|
|
|
|
return {
|
|
disposition: "needs_user_action",
|
|
kind: "unknown_dialog",
|
|
risk: "medium",
|
|
action: "pause_for_user",
|
|
signature,
|
|
};
|
|
}
|
|
|
|
export function buildDialogAuditEntry({ requestId, snapshot: snapshotInput, decision, handledAt = new Date().toISOString() }) {
|
|
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
|
const signature = decision?.signature || createDialogSignature(snapshot);
|
|
return {
|
|
kind: "desktop_dialog_guard",
|
|
requestId: requestId || undefined,
|
|
handledAt,
|
|
platform: snapshot.platform,
|
|
appName: snapshot.appName,
|
|
dialogId: signature.id,
|
|
risk: decision?.risk || "medium",
|
|
disposition: decision?.disposition || "unknown",
|
|
action: decision?.action || "pause_for_user",
|
|
button: decision?.button || undefined,
|
|
policyKind: decision?.kind || "unknown_dialog",
|
|
};
|
|
}
|
|
|
|
export function buildDialogInterventionResult({ requestId, snapshot: snapshotInput, decision }) {
|
|
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
|
const signature = decision?.signature || createDialogSignature(snapshot);
|
|
const blocked = decision?.risk === "high";
|
|
return {
|
|
status: "needs_user_action",
|
|
requestId: requestId || undefined,
|
|
kind: "dialog_intervention_required",
|
|
dialogId: signature.id,
|
|
risk: decision?.risk || "medium",
|
|
summary: `${snapshot.appName} 弹窗需要确认:${snapshot.title || snapshot.text || "未知弹窗"}`,
|
|
recommendedAction: blocked ? "handled_on_device" : "allow_once",
|
|
availableActions: blocked
|
|
? ["handled_on_device", "cancel_task"]
|
|
: ["allow_once", "allow_for_device_dialog", "deny"],
|
|
platform: snapshot.platform,
|
|
appName: snapshot.appName,
|
|
};
|
|
}
|