fix(discord): block channel policy auth bypass · openclaw/openclaw@a32fe80
vincentkoc
·
2026-04-24
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
8 | 8 | |
9 | 9 | ### Fixes |
10 | 10 | |
| 11 | +- 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. |
11 | 12 | - 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. |
12 | 13 | - 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. |
13 | 14 | - 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. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -230,6 +230,67 @@ describe("Discord native slash commands with commands.allowFrom", () => {
|
230 | 230 | expectNotUnauthorizedReply(interaction); |
231 | 231 | }); |
232 | 232 | |
| 233 | +it("authorizes guild slash commands when channel access restrictions include the member", async () => { |
| 234 | +const { dispatchSpy, interaction } = await runGuildSlashCommand({ |
| 235 | +mutateConfig: (cfg) => { |
| 236 | +cfg.commands = { |
| 237 | + ...cfg.commands, |
| 238 | +allowFrom: undefined, |
| 239 | +}; |
| 240 | +cfg.channels = { |
| 241 | + ...cfg.channels, |
| 242 | +discord: { |
| 243 | + ...cfg.channels?.discord, |
| 244 | +guilds: { |
| 245 | +"345678901234567890": { |
| 246 | +channels: { |
| 247 | +"234567890123456789": { |
| 248 | +enabled: true, |
| 249 | +requireMention: false, |
| 250 | +users: ["user:123456789012345678"], |
| 251 | +}, |
| 252 | +}, |
| 253 | +}, |
| 254 | +}, |
| 255 | +}, |
| 256 | +}; |
| 257 | +}, |
| 258 | +}); |
| 259 | +expect(dispatchSpy).toHaveBeenCalledTimes(1); |
| 260 | +expectNotUnauthorizedReply(interaction); |
| 261 | +}); |
| 262 | + |
| 263 | +it("rejects guild slash commands when channel access restrictions exclude the member even if channel policy would allow them", async () => { |
| 264 | +const { dispatchSpy, interaction } = await runGuildSlashCommand({ |
| 265 | +userId: "999999999999999999", |
| 266 | +mutateConfig: (cfg) => { |
| 267 | +cfg.commands = { |
| 268 | + ...cfg.commands, |
| 269 | +allowFrom: undefined, |
| 270 | +}; |
| 271 | +cfg.channels = { |
| 272 | + ...cfg.channels, |
| 273 | +discord: { |
| 274 | + ...cfg.channels?.discord, |
| 275 | +guilds: { |
| 276 | +"345678901234567890": { |
| 277 | +channels: { |
| 278 | +"234567890123456789": { |
| 279 | +enabled: true, |
| 280 | +requireMention: false, |
| 281 | +users: ["user:123456789012345678"], |
| 282 | +}, |
| 283 | +}, |
| 284 | +}, |
| 285 | +}, |
| 286 | +}, |
| 287 | +}; |
| 288 | +}, |
| 289 | +}); |
| 290 | +expect(dispatchSpy).not.toHaveBeenCalled(); |
| 291 | +expectUnauthorizedReply(interaction); |
| 292 | +}); |
| 293 | + |
233 | 294 | it("rejects guild slash commands outside the Discord allowlist when commands.useAccessGroups is false and commands.allowFrom is not configured", async () => { |
234 | 295 | const { dispatchSpy, interaction } = await runGuildSlashCommand({ |
235 | 296 | mutateConfig: (cfg) => { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -543,12 +543,12 @@ describe("Discord native plugin command dispatch", () => {
|
543 | 543 | "thread-123": { |
544 | 544 | enabled: true, |
545 | 545 | requireMention: false, |
546 | | -users: ["owner"], |
| 546 | +users: ["user:owner"], |
547 | 547 | }, |
548 | 548 | "parent-456": { |
549 | 549 | enabled: true, |
550 | 550 | requireMention: false, |
551 | | -users: ["owner"], |
| 551 | +users: ["user:owner"], |
552 | 552 | }, |
553 | 553 | }, |
554 | 554 | }, |
@@ -613,12 +613,12 @@ describe("Discord native plugin command dispatch", () => {
|
613 | 613 | "partial-thread-123": { |
614 | 614 | enabled: true, |
615 | 615 | requireMention: false, |
616 | | -users: ["owner"], |
| 616 | +users: ["user:owner"], |
617 | 617 | }, |
618 | 618 | "partial-parent-456": { |
619 | 619 | enabled: true, |
620 | 620 | requireMention: false, |
621 | | -users: ["owner"], |
| 621 | +users: ["user:owner"], |
622 | 622 | }, |
623 | 623 | }, |
624 | 624 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -243,7 +243,12 @@ function resolveDiscordGuildNativeCommandAuthorized(params: {
|
243 | 243 | configured: hasAccessRestrictions, |
244 | 244 | allowed: memberAllowed, |
245 | 245 | }; |
246 | | -const fallbackAuthorizers = [policyAuthorizer, ownerAuthorizer, memberAuthorizer]; |
| 246 | +const hasStricterAccessRestrictions = ownerAuthorizer.configured || memberAuthorizer.configured; |
| 247 | +const policyFallbackAuthorizer = { |
| 248 | +configured: policyAuthorizer.configured && !hasStricterAccessRestrictions, |
| 249 | +allowed: policyAuthorizer.allowed, |
| 250 | +}; |
| 251 | +const fallbackAuthorizers = [policyFallbackAuthorizer, ownerAuthorizer, memberAuthorizer]; |
247 | 252 | return resolveCommandAuthorizedFromAuthorizers({ |
248 | 253 | useAccessGroups: params.useAccessGroups, |
249 | 254 | authorizers: params.useAccessGroups |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。