import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js"; import { listChannelPlugins } from "../channels/plugins/index.js"; import type { ChannelId } from "../channels/plugins/types.js"; import type { ClawdbotConfig } from "../config/config.js"; import { readChannelAllowFromStore } from "../pairing/pairing-store.js"; import { note } from "../terminal/note.js"; export async function noteSecurityWarnings(cfg: ClawdbotConfig) { const warnings: string[] = []; const warnDmPolicy = async (params: { label: string; provider: ChannelId; dmPolicy: string; allowFrom?: Array | null; policyPath?: string; allowFromPath: string; approveHint: string; normalizeEntry?: (raw: string) => string; }) => { const dmPolicy = params.dmPolicy; const policyPath = params.policyPath ?? `${params.allowFromPath}policy`; const configAllowFrom = (params.allowFrom ?? []).map((v) => String(v).trim(), ); const hasWildcard = configAllowFrom.includes("*"); const storeAllowFrom = await readChannelAllowFromStore( params.provider, ).catch(() => []); const normalizedCfg = configAllowFrom .filter((v) => v !== "*") .map((v) => (params.normalizeEntry ? params.normalizeEntry(v) : v)) .map((v) => v.trim()) .filter(Boolean); const normalizedStore = storeAllowFrom .map((v) => (params.normalizeEntry ? params.normalizeEntry(v) : v)) .map((v) => v.trim()) .filter(Boolean); const allowCount = Array.from( new Set([...normalizedCfg, ...normalizedStore]), ).length; if (dmPolicy === "open") { const allowFromPath = `${params.allowFromPath}allowFrom`; warnings.push( `- ${params.label} DMs: OPEN (${policyPath}="open"). Anyone can DM it.`, ); if (!hasWildcard) { warnings.push( `- ${params.label} DMs: config invalid — "open" requires ${allowFromPath} to include "*".`, ); } return; } if (dmPolicy === "disabled") { warnings.push( `- ${params.label} DMs: disabled (${policyPath}="disabled").`, ); return; } if (allowCount === 0) { warnings.push( `- ${params.label} DMs: locked (${policyPath}="${dmPolicy}") with no allowlist; unknown senders will be blocked / get a pairing code.`, ); warnings.push(` ${params.approveHint}`); } }; for (const plugin of listChannelPlugins()) { if (!plugin.security) continue; const accountIds = plugin.config.listAccountIds(cfg); const defaultAccountId = resolveChannelDefaultAccountId({ plugin, cfg, accountIds, }); const account = plugin.config.resolveAccount(cfg, defaultAccountId); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : true; if (!enabled) continue; const configured = plugin.config.isConfigured ? await plugin.config.isConfigured(account, cfg) : true; if (!configured) continue; const dmPolicy = plugin.security.resolveDmPolicy?.({ cfg, accountId: defaultAccountId, account, }); if (dmPolicy) { await warnDmPolicy({ label: plugin.meta.label ?? plugin.id, provider: plugin.id, dmPolicy: dmPolicy.policy, allowFrom: dmPolicy.allowFrom, policyPath: dmPolicy.policyPath, allowFromPath: dmPolicy.allowFromPath, approveHint: dmPolicy.approveHint, normalizeEntry: dmPolicy.normalizeEntry, }); } if (plugin.security.collectWarnings) { const extra = await plugin.security.collectWarnings({ cfg, accountId: defaultAccountId, account, }); if (extra?.length) warnings.push(...extra); } } if (warnings.length > 0) { note(warnings.join("\n"), "Security"); } }