mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 08:51:40 +00:00
fix(auth): treat unconfigured-owner sessions as owner for ownerOnly tools (#26331)
Merged via squash.
Prepared head SHA: 1fbe1c7651
Co-authored-by: widingmarcus-cyber <245375637+widingmarcus-cyber@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -714,6 +714,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/Disabled channel startup: skip Slack monitor socket startup entirely when `channels.slack.enabled=false` (including configs that still contain valid tokens), preventing disabled accounts from opening websocket connections. (#30586) Thanks @liuxiaopai-ai.
|
||||
- Onboarding/Custom providers: use Azure OpenAI-specific verification auth/payload shape (`api-key`, deployment-path chat completions payload) when probing Azure endpoints so valid Azure custom-provider setup no longer fails preflight. (#29421) Thanks @kunalk16.
|
||||
- Feishu/Docx editing tools: add `feishu_doc` positional insert, table row/column operations, table-cell merge, and color-text updates; switch markdown write/append/insert to Descendant API insertion with large-document batching; and harden image uploads for data URI/base64/local-path inputs with strict validation and routing-safe upload metadata. (#29411) Thanks @Elarwei001.
|
||||
- Commands/Owner-only tools: treat identified direct-chat senders as owners when no owner allowlist is configured, while preserving internal `operator.admin` owner sessions. (#26331) thanks @widingmarcus-cyber
|
||||
|
||||
## 2026.2.26
|
||||
|
||||
|
||||
155
src/auto-reply/command-auth.owner-default.test.ts
Normal file
155
src/auto-reply/command-auth.owner-default.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { resolveCommandAuthorization } from "./command-auth.js";
|
||||
import type { MsgContext } from "./templating.js";
|
||||
|
||||
const createRegistry = () =>
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "discord",
|
||||
plugin: createOutboundTestPlugin({ id: "discord", outbound: { deliveryMode: "direct" } }),
|
||||
source: "test",
|
||||
},
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(createRegistry());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createRegistry());
|
||||
});
|
||||
|
||||
describe("senderIsOwner defaults to true when no owner allowlist configured (#26319)", () => {
|
||||
it("senderIsOwner is true when no ownerAllowFrom is configured (single-user default)", () => {
|
||||
const cfg = {
|
||||
channels: { discord: {} },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "direct",
|
||||
From: "discord:123",
|
||||
SenderId: "123",
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
// Without an explicit ownerAllowFrom list, the sole authorized user should
|
||||
// be treated as owner so ownerOnly tools (cron, gateway) are available.
|
||||
expect(auth.senderIsOwner).toBe(true);
|
||||
});
|
||||
|
||||
it("senderIsOwner is false when no ownerAllowFrom is configured in a group chat", () => {
|
||||
const cfg = {
|
||||
channels: { discord: {} },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "group",
|
||||
From: "discord:123",
|
||||
SenderId: "123",
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(auth.senderIsOwner).toBe(false);
|
||||
});
|
||||
|
||||
it("senderIsOwner is false when ownerAllowFrom is configured and sender does not match", () => {
|
||||
const cfg = {
|
||||
channels: { discord: {} },
|
||||
commands: { ownerAllowFrom: ["456"] },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
From: "discord:789",
|
||||
SenderId: "789",
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(auth.senderIsOwner).toBe(false);
|
||||
});
|
||||
|
||||
it("senderIsOwner is true when ownerAllowFrom matches sender", () => {
|
||||
const cfg = {
|
||||
channels: { discord: {} },
|
||||
commands: { ownerAllowFrom: ["456"] },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
From: "discord:456",
|
||||
SenderId: "456",
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(auth.senderIsOwner).toBe(true);
|
||||
});
|
||||
|
||||
it("senderIsOwner is true when ownerAllowFrom is wildcard (*)", () => {
|
||||
const cfg = {
|
||||
channels: { discord: {} },
|
||||
commands: { ownerAllowFrom: ["*"] },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
From: "discord:anyone",
|
||||
SenderId: "anyone",
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(auth.senderIsOwner).toBe(true);
|
||||
});
|
||||
|
||||
it("senderIsOwner is true for internal operator.admin sessions", () => {
|
||||
const cfg = {} as OpenClawConfig;
|
||||
|
||||
const ctx = {
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
GatewayClientScopes: ["operator.admin"],
|
||||
} as MsgContext;
|
||||
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(auth.senderIsOwner).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -350,8 +350,15 @@ export function resolveCommandAuthorization(params: {
|
||||
isInternalMessageChannel(ctx.Provider) &&
|
||||
Array.isArray(ctx.GatewayClientScopes) &&
|
||||
ctx.GatewayClientScopes.includes("operator.admin");
|
||||
const senderIsOwner = senderIsOwnerByIdentity || senderIsOwnerByScope;
|
||||
const ownerAllowlistConfigured = ownerAllowAll || explicitOwners.length > 0;
|
||||
const isDirectChat = (ctx.ChatType ?? "").trim().toLowerCase() === "direct";
|
||||
// In the default single-user direct-chat setup, allow an identified sender to
|
||||
// keep ownerOnly tools even without an explicit owner allowlist.
|
||||
const senderIsOwner =
|
||||
senderIsOwnerByIdentity ||
|
||||
senderIsOwnerByScope ||
|
||||
ownerAllowAll ||
|
||||
(!ownerAllowlistConfigured && isDirectChat && Boolean(senderId));
|
||||
const requireOwner = enforceOwner || ownerAllowlistConfigured;
|
||||
const isOwnerForCommands = !requireOwner
|
||||
? true
|
||||
|
||||
Reference in New Issue
Block a user