fix(discord): block channel policy auth bypass

This commit is contained in:
Vincent Koc
2026-04-23 11:33:24 -07:00
parent 9cae47a956
commit a32fe807f4
4 changed files with 72 additions and 5 deletions

View File

@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Discord/security: keep native slash-command channel policy from bypassing configured owner or member restrictions, while preserving channel-policy fallback when no stricter access rule exists. (#70711) Thanks @vincentkoc.
- Android/security: stop `ASK_OPENCLAW` intents from auto-sending injected prompts, so external app actions only prefill the draft instead of dispatching it immediately. (#70714) Thanks @vincentkoc.
- Control UI/chat: queue Stop-button aborts across Gateway reconnects so a disconnected active run is canceled on reconnect instead of only clearing local UI state. (#70673) Thanks @chinar-amrutkar.
- Secrets/Windows: strip UTF-8 BOMs from file-backed secrets and keep unavailable ACL checks fail-closed unless trusted file or exec providers explicitly opt into `allowInsecurePath`. (#70662) Thanks @zhanggpcsu.

View File

@@ -230,6 +230,67 @@ describe("Discord native slash commands with commands.allowFrom", () => {
expectNotUnauthorizedReply(interaction);
});
it("authorizes guild slash commands when channel access restrictions include the member", async () => {
const { dispatchSpy, interaction } = await runGuildSlashCommand({
mutateConfig: (cfg) => {
cfg.commands = {
...cfg.commands,
allowFrom: undefined,
};
cfg.channels = {
...cfg.channels,
discord: {
...cfg.channels?.discord,
guilds: {
"345678901234567890": {
channels: {
"234567890123456789": {
enabled: true,
requireMention: false,
users: ["user:123456789012345678"],
},
},
},
},
},
};
},
});
expect(dispatchSpy).toHaveBeenCalledTimes(1);
expectNotUnauthorizedReply(interaction);
});
it("rejects guild slash commands when channel access restrictions exclude the member even if channel policy would allow them", async () => {
const { dispatchSpy, interaction } = await runGuildSlashCommand({
userId: "999999999999999999",
mutateConfig: (cfg) => {
cfg.commands = {
...cfg.commands,
allowFrom: undefined,
};
cfg.channels = {
...cfg.channels,
discord: {
...cfg.channels?.discord,
guilds: {
"345678901234567890": {
channels: {
"234567890123456789": {
enabled: true,
requireMention: false,
users: ["user:123456789012345678"],
},
},
},
},
},
};
},
});
expect(dispatchSpy).not.toHaveBeenCalled();
expectUnauthorizedReply(interaction);
});
it("rejects guild slash commands outside the Discord allowlist when commands.useAccessGroups is false and commands.allowFrom is not configured", async () => {
const { dispatchSpy, interaction } = await runGuildSlashCommand({
mutateConfig: (cfg) => {

View File

@@ -543,12 +543,12 @@ describe("Discord native plugin command dispatch", () => {
"thread-123": {
enabled: true,
requireMention: false,
users: ["owner"],
users: ["user:owner"],
},
"parent-456": {
enabled: true,
requireMention: false,
users: ["owner"],
users: ["user:owner"],
},
},
},
@@ -613,12 +613,12 @@ describe("Discord native plugin command dispatch", () => {
"partial-thread-123": {
enabled: true,
requireMention: false,
users: ["owner"],
users: ["user:owner"],
},
"partial-parent-456": {
enabled: true,
requireMention: false,
users: ["owner"],
users: ["user:owner"],
},
},
},

View File

@@ -243,7 +243,12 @@ function resolveDiscordGuildNativeCommandAuthorized(params: {
configured: hasAccessRestrictions,
allowed: memberAllowed,
};
const fallbackAuthorizers = [policyAuthorizer, ownerAuthorizer, memberAuthorizer];
const hasStricterAccessRestrictions = ownerAuthorizer.configured || memberAuthorizer.configured;
const policyFallbackAuthorizer = {
configured: policyAuthorizer.configured && !hasStricterAccessRestrictions,
allowed: policyAuthorizer.allowed,
};
const fallbackAuthorizers = [policyFallbackAuthorizer, ownerAuthorizer, memberAuthorizer];
return resolveCommandAuthorizedFromAuthorizers({
useAccessGroups: params.useAccessGroups,
authorizers: params.useAccessGroups