UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

122 lines (121 loc) 7.28 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { a as readChannelAllowFromStore } from "./pairing-store-CxANz_ON.js"; import { i as normalizeAllowFromList, t as coerceNativeSetting } from "./channel-policy-CIW58SA5.js"; import { n as isDangerousNameMatchingEnabled } from "./dangerous-name-matching-D4QRC91G.js"; import { n as resolveNativeCommandsEnabled, r as resolveNativeSkillsEnabled } from "./commands-0zpR1Wsm.js"; import "./conversation-runtime-VjM1-D7a.js"; import "./dangerous-name-runtime-zP2lSdMQ.js"; import "./native-command-config-runtime-Dla2pZe-.js"; import { t as isDiscordMutableAllowEntry } from "./security-doctor-uUo8hTD5.js"; //#region extensions/discord/src/security-audit.ts function addDiscordNameBasedEntries(params) { if (!Array.isArray(params.values)) return; for (const value of params.values) { if (!isDiscordMutableAllowEntry(String(value))) continue; const text = normalizeOptionalString(String(value)) ?? ""; if (!text) continue; params.target.add(`${params.source}:${text}`); } } async function collectDiscordSecurityAuditFindings(params) { const findings = []; const discordCfg = params.account.config ?? {}; const accountId = normalizeOptionalString(params.accountId) ?? params.account.accountId ?? "default"; const dangerousNameMatchingEnabled = isDangerousNameMatchingEnabled(discordCfg); const storeAllowFrom = await readChannelAllowFromStore("discord", process.env, accountId).catch(() => []); const discordNameBasedAllowEntries = /* @__PURE__ */ new Set(); const discordPathPrefix = params.orderedAccountIds.length > 1 || params.hasExplicitAccountPath ? `channels.discord.accounts.${accountId}` : "channels.discord"; addDiscordNameBasedEntries({ target: discordNameBasedAllowEntries, values: discordCfg.allowFrom, source: `${discordPathPrefix}.allowFrom` }); addDiscordNameBasedEntries({ target: discordNameBasedAllowEntries, values: discordCfg.dm?.allowFrom, source: `${discordPathPrefix}.dm.allowFrom` }); addDiscordNameBasedEntries({ target: discordNameBasedAllowEntries, values: storeAllowFrom, source: "~/.openclaw/credentials/discord-allowFrom.json" }); const guildEntries = discordCfg.guilds ?? {}; for (const [guildKey, guildValue] of Object.entries(guildEntries)) { if (!guildValue || typeof guildValue !== "object") continue; const guild = guildValue; addDiscordNameBasedEntries({ target: discordNameBasedAllowEntries, values: guild.users, source: `${discordPathPrefix}.guilds.${guildKey}.users` }); const channels = guild.channels; if (!channels || typeof channels !== "object") continue; for (const [channelKey, channelValue] of Object.entries(channels)) { if (!channelValue || typeof channelValue !== "object") continue; addDiscordNameBasedEntries({ target: discordNameBasedAllowEntries, values: channelValue.users, source: `${discordPathPrefix}.guilds.${guildKey}.channels.${channelKey}.users` }); } } if (discordNameBasedAllowEntries.size > 0) { const examples = Array.from(discordNameBasedAllowEntries).slice(0, 5); const more = discordNameBasedAllowEntries.size > examples.length ? ` (+${discordNameBasedAllowEntries.size - examples.length} more)` : ""; findings.push({ checkId: "channels.discord.allowFrom.name_based_entries", severity: dangerousNameMatchingEnabled ? "info" : "warn", title: dangerousNameMatchingEnabled ? "Discord allowlist uses break-glass name/tag matching" : "Discord allowlist contains name or tag entries", detail: dangerousNameMatchingEnabled ? `Discord name/tag allowlist matching is explicitly enabled via dangerouslyAllowNameMatching. This mutable-identity mode is operator-selected break-glass behavior and out-of-scope for vulnerability reports by itself. Found: ${examples.join(", ")}${more}.` : `Discord name/tag allowlist matching uses normalized slugs and can collide across users. Found: ${examples.join(", ")}${more}.`, remediation: dangerousNameMatchingEnabled ? "Prefer stable Discord IDs (or <@id>/user:<id>/pk:<id>), then disable dangerouslyAllowNameMatching." : "Prefer stable Discord IDs (or <@id>/user:<id>/pk:<id>) in channels.discord.allowFrom and channels.discord.guilds.*.users, or explicitly opt in with dangerouslyAllowNameMatching=true if you accept the risk." }); } const nativeEnabled = resolveNativeCommandsEnabled({ providerId: "discord", providerSetting: coerceNativeSetting(discordCfg.commands?.native), globalSetting: params.cfg.commands?.native }); const nativeSkillsEnabled = resolveNativeSkillsEnabled({ providerId: "discord", providerSetting: coerceNativeSetting(discordCfg.commands?.nativeSkills), globalSetting: params.cfg.commands?.nativeSkills }); if (!nativeEnabled && !nativeSkillsEnabled) return findings; const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy; const groupPolicy = discordCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist"; const guildsConfigured = Object.keys(guildEntries).length > 0; const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => { if (!guild || typeof guild !== "object") return false; const record = guild; if (Array.isArray(record.users) && record.users.length > 0) return true; const channels = record.channels; if (!channels || typeof channels !== "object") return false; return Object.values(channels).some((channel) => { if (!channel || typeof channel !== "object") return false; const channelRecord = channel; return Array.isArray(channelRecord.users) && channelRecord.users.length > 0; }); }); const dmAllowFromRaw = discordCfg.dm?.allowFrom; const ownerAllowFromConfigured = normalizeAllowFromList([...Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [], ...storeAllowFrom]).length > 0; const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; if (!useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !hasAnyUserAllowlist) findings.push({ checkId: "channels.discord.commands.native.unrestricted", severity: "critical", title: "Discord slash commands are unrestricted", detail: "commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.", remediation: "Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users)." }); else if (useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !ownerAllowFromConfigured && !hasAnyUserAllowlist) findings.push({ checkId: "channels.discord.commands.native.no_allowlists", severity: "warn", title: "Discord slash commands have no allowlists", detail: "Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.", remediation: "Add your user id to channels.discord.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users." }); return findings; } //#endregion export { collectDiscordSecurityAuditFindings as t };