Files
openclaw/extensions/discord/src/security.ts
Peter Steinberger 63401b730b fix: keep doctor security conditions as single findings (#124666)
* fix(doctor): record security finding severity

* refactor(security): keep audit severity internal

* fix(security): preserve channel finding severity
2026-08-16 10:24:40 -07:00

67 lines
2.7 KiB
TypeScript

// Discord plugin module implements security behavior.
import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
import {
createConditionalWarningCollector,
createOpenProviderConfiguredRouteWarningCollector,
} from "openclaw/plugin-sdk/channel-policy";
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
import {
resolveDiscordAccountAllowFrom,
resolveDiscordAccountDmPolicy,
type ResolvedDiscordAccount,
} from "./accounts.js";
import type { ChannelPlugin } from "./channel-api.js";
const resolveDiscordDmPolicy = createScopedDmSecurityResolver<ResolvedDiscordAccount>({
channelKey: "discord",
resolvePolicy: (account) => account.config.dmPolicy,
resolveAllowFrom: (account) => account.config.allowFrom,
resolveAccess: ({ cfg, account }) => ({
dmPolicy: resolveDiscordAccountDmPolicy({ cfg, accountId: account.accountId }),
allowFrom: resolveDiscordAccountAllowFrom({ cfg, accountId: account.accountId }),
}),
policyPathSuffix: "dmPolicy",
normalizeEntry: (raw) =>
raw
.trim()
.replace(/^(discord|user):/i, "")
.replace(/^<@!?(\d+)>$/, "$1"),
});
const collectDiscordSecurityWarnings =
createOpenProviderConfiguredRouteWarningCollector<ResolvedDiscordAccount>({
providerConfigPresent: (cfg) => cfg.channels?.discord !== undefined,
resolveGroupPolicy: (account) => account.config.groupPolicy,
resolveRouteAllowlistConfigured: (account) =>
Object.keys(account.config.guilds ?? {}).length > 0,
configureRouteAllowlist: {
surface: "Discord guilds",
openScope: "any channel not explicitly denied",
groupPolicyPath: "channels.discord.groupPolicy",
routeAllowlistPath: "channels.discord.guilds.<id>.channels",
},
missingRouteAllowlist: {
surface: "Discord guilds",
openBehavior: "with no guild/channel allowlist; any channel can trigger (mention-gated)",
remediation:
'Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds.<id>.channels',
},
});
const collectDiscordSecurityFindings = createConditionalWarningCollector.findings({
collectWarnings: collectDiscordSecurityWarnings,
checkId: "channels.discord.groups.open",
severity: "critical",
title: "Discord security warning",
});
const loadDiscordSecurityAuditModule = createLazyRuntimeModule(
() => import("./security-audit.runtime.js"),
);
export const discordSecurityAdapter = {
resolveDmPolicy: resolveDiscordDmPolicy,
collectWarnings: collectDiscordSecurityFindings,
collectAuditFindings: async (params) =>
(await loadDiscordSecurityAuditModule()).collectDiscordSecurityAuditFindings(params),
} satisfies NonNullable<ChannelPlugin<ResolvedDiscordAccount>["security"]>;