fix(commands): scope owner allowlist prefixes (#72928)

* fix(commands): scope owner allowlist prefixes

Co-authored-by: zsx <git@zsxsoft.com>

* fix(commands): annotate owner allowlist short-circuit

* docs: move changelog entry to unreleased fixes

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
This commit is contained in:
Agustin Rivera
2026-04-29 13:00:07 -07:00
committed by GitHub
parent f05b789736
commit fef42acda0
3 changed files with 85 additions and 1 deletions

View File

@@ -229,6 +229,7 @@ Docs: https://docs.openclaw.ai
- Auto-reply/subagents: reject `/focus` from leaf subagents and scope fallback target resolution to the requesting subagent's children, so subagents cannot bind conversations outside their control boundary. (#73613) Thanks @drobison00.
- Gateway/startup: skip inherited workspace startup memory for sandboxed spawned sessions without real-workspace write access, so `/new` no longer preloads host workspace memory into isolated child runs. (#73611) Thanks @drobison00.
- Agents/tool policy: validate caller group IDs against session or spawned context before applying group-scoped tool policies or persisting gateway group metadata, so forged group IDs cannot unlock more permissive tools. (#73720) Thanks @mmaps.
- Commands: keep channel-prefixed owner allowlist entries scoped to matching providers so webchat command contexts cannot inherit external channel owners. Thanks @zsxsoft.
## 2026.4.27

View File

@@ -289,7 +289,8 @@ function resolveOwnerAllowFromList(params: {
const prefix = trimmed.slice(0, separatorIndex);
const channel = normalizeAnyChannelId(prefix);
if (channel) {
if (params.providerId && channel !== params.providerId) {
// Channel-prefixed entries require a known matching provider; webchat leaves it unset.
if (!params.providerId || channel !== params.providerId) {
continue;
}
const remainder = trimmed.slice(separatorIndex + 1).trim();

View File

@@ -356,6 +356,88 @@ describe("resolveCommandAuthorization", () => {
expect(auth.isAuthorizedSender).toBe(true);
});
it("does not apply channel-prefixed owner wildcards to webchat command contexts", () => {
const cfg = {
commands: { ownerAllowFrom: ["discord:*"] },
} as OpenClawConfig;
const auth = resolveCommandAuthorization({
ctx: {
Provider: "webchat",
Surface: "webchat",
OriginatingChannel: "webchat",
SenderId: "123456789012345678",
GatewayClientScopes: ["operator.write"],
} as MsgContext,
cfg,
commandAuthorized: true,
});
expect(auth.providerId).toBeUndefined();
expect(auth.senderIsOwner).toBe(false);
});
it("does not apply channel-prefixed owner identities to webchat command contexts", () => {
const cfg = {
commands: { ownerAllowFrom: ["discord:123456789012345678"] },
} as OpenClawConfig;
const auth = resolveCommandAuthorization({
ctx: {
Provider: "webchat",
Surface: "webchat",
OriginatingChannel: "webchat",
SenderId: "123456789012345678",
GatewayClientScopes: ["operator.write"],
} as MsgContext,
cfg,
commandAuthorized: true,
});
expect(auth.providerId).toBeUndefined();
expect(auth.senderIsOwner).toBe(false);
});
it("applies channel-prefixed owner identities to matching providers", () => {
const cfg = {
commands: { ownerAllowFrom: ["discord:123456789012345678"] },
} as OpenClawConfig;
const auth = resolveCommandAuthorization({
ctx: {
Provider: "discord",
Surface: "discord",
From: "discord:123456789012345678",
SenderId: "123456789012345678",
} as MsgContext,
cfg,
commandAuthorized: true,
});
expect(auth.providerId).toBe("discord");
expect(auth.senderIsOwner).toBe(true);
});
it("does not apply channel-prefixed owner wildcards to mismatched providers", () => {
const cfg = {
commands: { ownerAllowFrom: ["telegram:*"] },
} as OpenClawConfig;
const auth = resolveCommandAuthorization({
ctx: {
Provider: "discord",
Surface: "discord",
From: "discord:123456789012345678",
SenderId: "123456789012345678",
} as MsgContext,
cfg,
commandAuthorized: true,
});
expect(auth.providerId).toBe("discord");
expect(auth.senderIsOwner).toBe(false);
});
it("preserves external channel command auth in mixed webchat contexts", () => {
const cfg = {
commands: { allowFrom: { whatsapp: ["+15551234567"] } },