Channels: fail closed when Slack/Discord config is missing

This commit is contained in:
Brian Mendonca
2026-02-22 00:51:40 -07:00
committed by Peter Steinberger
parent 11546b1177
commit 3700151ec0
6 changed files with 121 additions and 18 deletions

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { __testing } from "./provider.js";
describe("resolveDiscordRuntimeGroupPolicy", () => {
it("fails closed when channels.discord is missing and no defaults are set", () => {
const resolved = __testing.resolveDiscordRuntimeGroupPolicy({
providerConfigPresent: false,
});
expect(resolved.groupPolicy).toBe("allowlist");
expect(resolved.providerMissingFallbackApplied).toBe(true);
});
it("keeps open default when channels.discord is configured", () => {
const resolved = __testing.resolveDiscordRuntimeGroupPolicy({
providerConfigPresent: true,
});
expect(resolved.groupPolicy).toBe("open");
expect(resolved.providerMissingFallbackApplied).toBe(false);
});
it("respects explicit provider policy", () => {
const resolved = __testing.resolveDiscordRuntimeGroupPolicy({
providerConfigPresent: false,
groupPolicy: "disabled",
});
expect(resolved.groupPolicy).toBe("disabled");
expect(resolved.providerMissingFallbackApplied).toBe(false);
});
});

View File

@@ -21,6 +21,7 @@ import {
} from "../../config/commands.js";
import type { OpenClawConfig, ReplyToMode } from "../../config/config.js";
import { loadConfig } from "../../config/config.js";
import type { GroupPolicy } from "../../config/types.base.js";
import { danger, logVerbose, shouldLogVerbose, warn } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { createDiscordRetryRunner } from "../../infra/retry-policy.js";
@@ -170,6 +171,25 @@ function dedupeSkillCommandsForDiscord(
return deduped;
}
function resolveDiscordRuntimeGroupPolicy(params: {
providerConfigPresent: boolean;
groupPolicy?: GroupPolicy;
defaultGroupPolicy?: GroupPolicy;
}): {
groupPolicy: GroupPolicy;
providerMissingFallbackApplied: boolean;
} {
const groupPolicy =
params.groupPolicy ??
params.defaultGroupPolicy ??
(params.providerConfigPresent ? "open" : "allowlist");
const providerMissingFallbackApplied =
!params.providerConfigPresent &&
params.groupPolicy === undefined &&
params.defaultGroupPolicy === undefined;
return { groupPolicy, providerMissingFallbackApplied };
}
async function deployDiscordCommands(params: {
client: Client;
runtime: RuntimeEnv;
@@ -253,16 +273,16 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const dmConfig = discordCfg.dm;
let guildEntries = discordCfg.guilds;
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
const groupPolicy = discordCfg.groupPolicy ?? defaultGroupPolicy ?? "open";
if (
discordCfg.groupPolicy === undefined &&
discordCfg.guilds === undefined &&
defaultGroupPolicy === undefined &&
groupPolicy === "open"
) {
const providerConfigPresent = cfg.channels?.discord !== undefined;
const { groupPolicy, providerMissingFallbackApplied } = resolveDiscordRuntimeGroupPolicy({
providerConfigPresent,
groupPolicy: discordCfg.groupPolicy,
defaultGroupPolicy,
});
if (providerMissingFallbackApplied) {
runtime.log?.(
warn(
'discord: groupPolicy defaults to "open" when channels.discord is missing; set channels.discord.groupPolicy (or channels.defaults.groupPolicy) or add channels.discord.guilds to restrict access.',
'discord: channels.discord is missing; defaulting groupPolicy to "allowlist" (guild messages blocked until explicitly configured).',
),
);
}
@@ -622,6 +642,7 @@ async function clearDiscordNativeCommands(params: {
export const __testing = {
createDiscordGatewayPlugin,
dedupeSkillCommandsForDiscord,
resolveDiscordRuntimeGroupPolicy,
resolveDiscordRestFetch,
resolveThreadBindingsEnabled,
};