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 auditHint = `- Run: clawdbot security audit --deep`; 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; const dmScope = cfg.session?.dmScope ?? "main"; const isMultiUserDm = hasWildcard || allowCount > 1; 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 "*".`, ); } } if (dmPolicy === "disabled") { warnings.push(`- ${params.label} DMs: disabled (${policyPath}="disabled").`); return; } if (dmPolicy !== "open" && 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}`); } if (dmScope === "main" && isMultiUserDm) { warnings.push( `- ${params.label} DMs: multiple senders share the main session; set session.dmScope="per-channel-peer" to isolate sessions.`, ); } }; 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); } } const lines = warnings.length > 0 ? warnings : ["- No channel security warnings detected."]; lines.push(auditHint); note(lines.join("\n"), "Security"); }