fix: require owner identity for owner-enforced commands (#69774)

* fix: require owner identity for owner-enforced commands

Stop wildcard channel allowlists from authorizing non-owner senders when a plugin requires owner-only commands.

Add a regression test for the owner-enforced wildcard allowFrom path.

* docs(changelog): note owner identity requirement for owner-enforced commands (#69774)
This commit is contained in:
Devin Robison
2026-04-21 10:16:33 -06:00
committed by GitHub
parent 4fdd005b88
commit 2aa93d44a1
3 changed files with 47 additions and 3 deletions

View File

@@ -9,6 +9,10 @@ Docs: https://docs.openclaw.ai
- Onboard/wizard: simplify the security disclaimer copy (drop the yellow banner and warning icon in favor of plain-prose paragraphs), and flip remaining onboarding pickers with long dynamic option lists to searchable autocompletes (search provider, plugin configure, model provider filter).
- Ollama/onboard: populate the cloud-only model list from `ollama.com/api/tags` so `openclaw onboard` reflects the live cloud catalog instead of a static three-model seed; cap the discovered list at 500 and fall back to the previous hardcoded suggestions when ollama.com is unreachable or returns no models. (#68463) Thanks @BruceMacD.
### Fixes
- Auth/commands: require owner identity (an owner-candidate match or internal `operator.admin`) for owner-enforced commands instead of treating wildcard channel `allowFrom` or empty owner-candidate lists as sufficient, so non-owner senders can no longer reach owner-only commands through a permissive fallback when `enforceOwnerForCommands=true` and `commands.ownerAllowFrom` is unset. (#69774) Thanks @drobison00.
## 2026.4.20
### Changes

View File

@@ -706,9 +706,7 @@ export function resolveCommandAuthorization(params: {
? true
: ownerAllowlistConfigured
? senderIsOwner
: ownerState.allowAll ||
ownerState.ownerCandidatesForCommands.length === 0 ||
Boolean(matchedCommandOwner);
: senderIsOwnerByScope || Boolean(matchedCommandOwner);
const isAuthorizedSender = resolveCommandSenderAuthorization({
commandAuthorized,
isOwnerForCommands,

View File

@@ -159,6 +159,48 @@ describe("resolveCommandAuthorization", () => {
expect(otherAuth.isAuthorizedSender).toBe(false);
});
it("rejects wildcard channel senders when the plugin enforces owner-only commands", () => {
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "discord",
plugin: {
...createOutboundTestPlugin({
id: "discord",
outbound: { deliveryMode: "direct" },
}),
commands: { enforceOwnerForCommands: true },
config: {
listAccountIds: () => ["default"],
resolveAccount: () => ({}),
resolveAllowFrom: () => ["*"],
formatAllowFrom,
},
},
source: "test",
},
]),
);
const cfg = {
channels: { discord: { allowFrom: ["*"] } },
} as OpenClawConfig;
const auth = resolveCommandAuthorization({
ctx: {
Provider: "discord",
Surface: "discord",
ChatType: "direct",
From: "discord:123",
SenderId: "123",
} as MsgContext,
cfg,
commandAuthorized: true,
});
expect(auth.senderIsOwner).toBe(false);
expect(auth.isAuthorizedSender).toBe(false);
});
it("uses explicit owner allowlist when allowFrom is empty", () => {
const cfg = {
commands: { ownerAllowFrom: ["whatsapp:+15551234567"] },