fix(security): default-deny command execution
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
|
||||
import type { ResolvedZaloAccount } from "./accounts.js";
|
||||
import {
|
||||
hasInlineCommandTokens,
|
||||
isControlCommandMessage,
|
||||
} from "../../../src/auto-reply/command-detection.js";
|
||||
import { finalizeInboundContext } from "../../../src/auto-reply/reply/inbound-context.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../../src/channels/command-gating.js";
|
||||
import {
|
||||
ZaloApiError,
|
||||
deleteWebhook,
|
||||
@@ -437,6 +442,22 @@ async function processMessageWithPipeline(params: {
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
||||
const rawBody = text?.trim() || (mediaPath ? "<media:image>" : "");
|
||||
const shouldComputeCommandAuthorized =
|
||||
isControlCommandMessage(rawBody, config) || hasInlineCommandTokens(rawBody);
|
||||
const storeAllowFrom =
|
||||
!isGroup && (dmPolicy !== "open" || shouldComputeCommandAuthorized)
|
||||
? await deps.readChannelAllowFromStore("zalo").catch(() => [])
|
||||
: [];
|
||||
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
||||
const useAccessGroups = config.commands?.useAccessGroups !== false;
|
||||
const senderAllowedForCommands = isSenderAllowed(senderId, effectiveAllowFrom);
|
||||
const commandAuthorized = shouldComputeCommandAuthorized
|
||||
? resolveCommandAuthorizedFromAuthorizers({
|
||||
useAccessGroups,
|
||||
authorizers: [{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands }],
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (!isGroup) {
|
||||
if (dmPolicy === "disabled") {
|
||||
@@ -445,9 +466,7 @@ async function processMessageWithPipeline(params: {
|
||||
}
|
||||
|
||||
if (dmPolicy !== "open") {
|
||||
const storeAllowFrom = await deps.readChannelAllowFromStore("zalo").catch(() => []);
|
||||
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
||||
const allowed = isSenderAllowed(senderId, effectiveAllowFrom);
|
||||
const allowed = senderAllowedForCommands;
|
||||
|
||||
if (!allowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
@@ -496,7 +515,11 @@ async function processMessageWithPipeline(params: {
|
||||
},
|
||||
});
|
||||
|
||||
const rawBody = text?.trim() || (mediaPath ? "<media:image>" : "");
|
||||
if (isGroup && isControlCommandMessage(rawBody, config) && commandAuthorized !== true) {
|
||||
logVerbose(deps, `zalo: drop control command from unauthorized sender ${senderId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fromLabel = isGroup
|
||||
? `group:${chatId}`
|
||||
: senderName || `user:${senderId}`;
|
||||
@@ -519,6 +542,7 @@ async function processMessageWithPipeline(params: {
|
||||
ConversationLabel: fromLabel,
|
||||
SenderName: senderName || undefined,
|
||||
SenderId: senderId,
|
||||
CommandAuthorized: commandAuthorized,
|
||||
Provider: "zalo",
|
||||
Surface: "zalo",
|
||||
MessageSid: message_id,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
|
||||
import type { RuntimeEnv } from "../../../src/runtime.js";
|
||||
import {
|
||||
hasInlineCommandTokens,
|
||||
isControlCommandMessage,
|
||||
} from "../../../src/auto-reply/command-detection.js";
|
||||
import { finalizeInboundContext } from "../../../src/auto-reply/reply/inbound-context.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../../src/channels/command-gating.js";
|
||||
import { loadCoreChannelDeps, type CoreChannelDeps } from "./core-bridge.js";
|
||||
import { sendMessageZalouser } from "./send.js";
|
||||
import type { CoreConfig, ResolvedZalouserAccount, ZcaMessage } from "./types.js";
|
||||
@@ -105,6 +110,22 @@ async function processMessage(
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
||||
const rawBody = content.trim();
|
||||
const shouldComputeCommandAuthorized =
|
||||
isControlCommandMessage(rawBody, config) || hasInlineCommandTokens(rawBody);
|
||||
const storeAllowFrom =
|
||||
!isGroup && (dmPolicy !== "open" || shouldComputeCommandAuthorized)
|
||||
? await deps.readChannelAllowFromStore("zalouser").catch(() => [])
|
||||
: [];
|
||||
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
||||
const useAccessGroups = config.commands?.useAccessGroups !== false;
|
||||
const senderAllowedForCommands = isSenderAllowed(senderId, effectiveAllowFrom);
|
||||
const commandAuthorized = shouldComputeCommandAuthorized
|
||||
? resolveCommandAuthorizedFromAuthorizers({
|
||||
useAccessGroups,
|
||||
authorizers: [{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands }],
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (!isGroup) {
|
||||
if (dmPolicy === "disabled") {
|
||||
@@ -113,9 +134,7 @@ async function processMessage(
|
||||
}
|
||||
|
||||
if (dmPolicy !== "open") {
|
||||
const storeAllowFrom = await deps.readChannelAllowFromStore("zalouser").catch(() => []);
|
||||
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
||||
const allowed = isSenderAllowed(senderId, effectiveAllowFrom);
|
||||
const allowed = senderAllowedForCommands;
|
||||
|
||||
if (!allowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
@@ -158,6 +177,11 @@ async function processMessage(
|
||||
}
|
||||
}
|
||||
|
||||
if (isGroup && isControlCommandMessage(rawBody, config) && commandAuthorized !== true) {
|
||||
logVerbose(deps, runtime, `zalouser: drop control command from unauthorized sender ${senderId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const peer = isGroup ? { kind: "group" as const, id: chatId } : { kind: "group" as const, id: senderId };
|
||||
|
||||
const route = deps.resolveAgentRoute({
|
||||
@@ -171,7 +195,6 @@ async function processMessage(
|
||||
},
|
||||
});
|
||||
|
||||
const rawBody = content.trim();
|
||||
const fromLabel = isGroup
|
||||
? `group:${chatId}`
|
||||
: senderName || `user:${senderId}`;
|
||||
@@ -194,6 +217,7 @@ async function processMessage(
|
||||
ConversationLabel: fromLabel,
|
||||
SenderName: senderName || undefined,
|
||||
SenderId: senderId,
|
||||
CommandAuthorized: commandAuthorized,
|
||||
Provider: "zalouser",
|
||||
Surface: "zalouser",
|
||||
MessageSid: message.msgId ?? `${timestamp}`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hasControlCommand } from "./command-detection.js";
|
||||
import { hasControlCommand, hasInlineCommandTokens } from "./command-detection.js";
|
||||
import { listChatCommands } from "./commands-registry.js";
|
||||
import { parseActivationCommand } from "./group-activation.js";
|
||||
import { parseSendPolicyCommand } from "./send-policy.js";
|
||||
@@ -72,6 +72,14 @@ describe("control command parsing", () => {
|
||||
expect(hasControlCommand("/send on")).toBe(true);
|
||||
});
|
||||
|
||||
it("detects inline command tokens", () => {
|
||||
expect(hasInlineCommandTokens("hello /status")).toBe(true);
|
||||
expect(hasInlineCommandTokens("hey /think high")).toBe(true);
|
||||
expect(hasInlineCommandTokens("plain text")).toBe(false);
|
||||
expect(hasInlineCommandTokens("http://example.com/path")).toBe(false);
|
||||
expect(hasInlineCommandTokens("stop")).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores telegram commands addressed to other bots", () => {
|
||||
expect(
|
||||
hasControlCommand("/help@otherbot", undefined, {
|
||||
|
||||
@@ -45,3 +45,16 @@ export function isControlCommandMessage(
|
||||
const normalized = normalizeCommandBody(trimmed, options).trim().toLowerCase();
|
||||
return isAbortTrigger(normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coarse detection for inline directives/shortcuts (e.g. "hey /status") so channel monitors
|
||||
* can decide whether to compute CommandAuthorized for a message.
|
||||
*
|
||||
* This intentionally errs on the side of false positives; CommandAuthorized only gates
|
||||
* command/directive execution, not normal chat replies.
|
||||
*/
|
||||
export function hasInlineCommandTokens(text?: string): boolean {
|
||||
const body = text ?? "";
|
||||
if (!body.trim()) return false;
|
||||
return /(?:^|\s)[/!][a-z]/i.test(body);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ describe("directive behavior", () => {
|
||||
Body: "/thinking xhigh",
|
||||
From: "+1004",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -90,7 +91,7 @@ describe("directive behavior", () => {
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: storePath },
|
||||
},
|
||||
);
|
||||
@@ -108,6 +109,7 @@ describe("directive behavior", () => {
|
||||
Body: "/thinking xhigh",
|
||||
From: "+1004",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -117,7 +119,7 @@ describe("directive behavior", () => {
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: storePath },
|
||||
},
|
||||
);
|
||||
@@ -135,6 +137,7 @@ describe("directive behavior", () => {
|
||||
Body: "/thinking xhigh",
|
||||
From: "+1004",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -144,7 +147,7 @@ describe("directive behavior", () => {
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: storePath },
|
||||
},
|
||||
);
|
||||
@@ -164,6 +167,7 @@ describe("directive behavior", () => {
|
||||
Body: "/help",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -201,6 +205,7 @@ describe("directive behavior", () => {
|
||||
Body: "/demo_skill",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -232,6 +237,7 @@ describe("directive behavior", () => {
|
||||
Body: "/queue collect debounce:bogus cap:zero drop:maybe",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -263,6 +269,7 @@ describe("directive behavior", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -300,7 +307,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/think", From: "+1222", To: "+1222" },
|
||||
{ Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/verbose on", From: "+1222", To: "+1222" },
|
||||
{ Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -197,7 +197,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/verbose off", From: "+1222", To: "+1222" },
|
||||
{ Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -223,7 +223,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/think", From: "+1222", To: "+1222" },
|
||||
{ Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -248,7 +248,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/think", From: "+1222", To: "+1222" },
|
||||
{ Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("directive behavior", () => {
|
||||
]);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/think", From: "+1222", To: "+1222" },
|
||||
{ Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -105,7 +105,7 @@ describe("directive behavior", () => {
|
||||
]);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/think", From: "+1222", To: "+1222" },
|
||||
{ Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model list", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model list", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -97,7 +97,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -137,7 +137,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model list", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model list", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -178,7 +178,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model list", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model list", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -205,7 +205,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model openai/gpt-4.1-mini", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model openai/gpt-4.1-mini", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -235,7 +235,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model Opus", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model Opus", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model ki", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model ki", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -135,7 +135,7 @@ describe("directive behavior", () => {
|
||||
);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model Opus@anthropic:work", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model Opus@anthropic:work", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -167,7 +167,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model Opus", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model Opus", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -200,6 +200,7 @@ describe("directive behavior", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -230,6 +231,7 @@ describe("directive behavior", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("directive behavior", () => {
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
SessionKey: "agent:work:main",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -118,6 +119,7 @@ describe("directive behavior", () => {
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1333",
|
||||
SessionKey: "agent:work:main",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -163,6 +165,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -200,6 +203,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -235,6 +239,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -115,6 +116,7 @@ describe("directive behavior", () => {
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
SessionKey: "agent:restricted:main",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -153,7 +155,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/queue interrupt", From: "+1222", To: "+1222" },
|
||||
{ Body: "/queue interrupt", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -185,6 +187,7 @@ describe("directive behavior", () => {
|
||||
Body: "/queue collect debounce:2s cap:5 drop:old",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -219,7 +222,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/queue interrupt", From: "+1222", To: "+1222" },
|
||||
{ Body: "/queue interrupt", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -234,7 +237,7 @@ describe("directive behavior", () => {
|
||||
);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/queue reset", From: "+1222", To: "+1222" },
|
||||
{ Body: "/queue reset", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -99,6 +100,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -153,6 +155,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -164,6 +167,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -176,6 +180,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -203,6 +208,7 @@ describe("directive behavior", () => {
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
SessionKey: "agent:restricted:main",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/verbose", From: "+1222", To: "+1222" },
|
||||
{ Body: "/verbose", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -90,7 +90,7 @@ describe("directive behavior", () => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/reasoning", From: "+1222", To: "+1222" },
|
||||
{ Body: "/reasoning", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -120,6 +120,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -158,6 +159,7 @@ describe("directive behavior", () => {
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model kimi", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model kimi", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -107,7 +107,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model kimi-k2-0905-preview", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model kimi-k2-0905-preview", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -148,7 +148,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model moonshot/kimi", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model moonshot/kimi", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -189,7 +189,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model minimax", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model minimax", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -234,7 +234,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -153,7 +153,7 @@ describe("directive behavior", () => {
|
||||
});
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/verbose on", From: ctx.From, To: ctx.To },
|
||||
{ Body: "/verbose on", From: ctx.From, To: ctx.To, CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -193,7 +193,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
@@ -224,7 +224,7 @@ describe("directive behavior", () => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model status", From: "+1222", To: "+1222" },
|
||||
{ Body: "/model status", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
agents: {
|
||||
|
||||
@@ -54,6 +54,7 @@ describe("RawBody directive parsing", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
ChatType: "group",
|
||||
CommandAuthorized: true,
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -87,6 +88,7 @@ describe("RawBody directive parsing", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
ChatType: "group",
|
||||
CommandAuthorized: true,
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -123,6 +125,7 @@ describe("RawBody directive parsing", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
ChatType: "group",
|
||||
CommandAuthorized: true,
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -160,6 +163,7 @@ describe("RawBody directive parsing", () => {
|
||||
Provider: "whatsapp",
|
||||
Surface: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
CommandAuthorized: true,
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -207,6 +211,7 @@ describe("RawBody directive parsing", () => {
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
ChatType: "group",
|
||||
CommandAuthorized: true,
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
|
||||
@@ -105,6 +105,7 @@ describe("trigger handling", () => {
|
||||
ChatType: "group",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+999",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -179,6 +180,7 @@ describe("trigger handling", () => {
|
||||
Body: "/new",
|
||||
From: "+1003",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -123,6 +123,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -132,6 +132,7 @@ describe("trigger handling", () => {
|
||||
To: "whatsapp:+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
ChatType: "group",
|
||||
WasMentioned: false,
|
||||
},
|
||||
@@ -175,6 +176,7 @@ describe("trigger handling", () => {
|
||||
To: "whatsapp:+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
},
|
||||
@@ -218,6 +220,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -116,6 +116,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
@@ -138,6 +139,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
@@ -162,6 +164,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
@@ -193,6 +196,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1002",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
@@ -217,6 +221,7 @@ describe("trigger handling", () => {
|
||||
Body: "[Dec 5 10:00] stop",
|
||||
From: "+1000",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
@@ -233,6 +238,7 @@ describe("trigger handling", () => {
|
||||
Body: "/stop",
|
||||
From: "+1003",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
|
||||
@@ -108,6 +108,7 @@ describe("trigger handling", () => {
|
||||
Body: "please /commands now",
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
@@ -141,6 +142,7 @@ describe("trigger handling", () => {
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
SenderId: "12345",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
|
||||
@@ -161,6 +161,7 @@ describe("trigger handling", () => {
|
||||
SenderName: "Peter Steinberger",
|
||||
SenderUsername: "steipete",
|
||||
SenderTag: "steipete",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -209,6 +209,7 @@ describe("trigger handling", () => {
|
||||
ChatType: "group",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -184,6 +184,7 @@ describe("trigger handling", () => {
|
||||
Body: "/help",
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
@@ -218,6 +219,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -146,6 +146,7 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1002",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -176,6 +177,7 @@ describe("trigger handling", () => {
|
||||
Provider: "whatsapp",
|
||||
Surface: "whatsapp",
|
||||
SenderE164: "+1002",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
@@ -208,6 +210,7 @@ describe("trigger handling", () => {
|
||||
Body: "please /help now",
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{
|
||||
onBlockReply: async (payload) => {
|
||||
|
||||
@@ -117,6 +117,7 @@ describe("trigger handling", () => {
|
||||
Body: "/compact focus on decisions",
|
||||
From: "+1003",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -109,6 +109,7 @@ describe("trigger handling", () => {
|
||||
Body: "/reset",
|
||||
From: "+1003",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
@@ -173,6 +174,7 @@ describe("trigger handling", () => {
|
||||
Body: "/reset",
|
||||
From: "+1003",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -106,6 +106,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: "telegram:slash:111",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -137,6 +138,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: "telegram:slash:111",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -156,6 +158,7 @@ describe("trigger handling", () => {
|
||||
Body: " [Dec 5] /restart",
|
||||
From: "+1001",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
@@ -173,6 +176,7 @@ describe("trigger handling", () => {
|
||||
Body: "/restart",
|
||||
From: "+1001",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -189,6 +193,7 @@ describe("trigger handling", () => {
|
||||
Body: "/status",
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
@@ -205,6 +210,7 @@ describe("trigger handling", () => {
|
||||
Body: "/usage",
|
||||
From: "+1002",
|
||||
To: "+2000",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeCfg(home),
|
||||
|
||||
@@ -107,6 +107,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: "telegram:slash:111",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -137,6 +138,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: "telegram:slash:111",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -169,6 +171,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: sessionKey,
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -197,6 +200,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: sessionKey,
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -226,6 +230,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: sessionKey,
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -256,6 +261,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: sessionKey,
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
@@ -285,6 +291,7 @@ describe("trigger handling", () => {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
SessionKey: sessionKey,
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
|
||||
@@ -70,6 +70,7 @@ describe("abort detection", () => {
|
||||
ctx: {
|
||||
CommandBody: "/stop",
|
||||
RawBody: "/stop",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: "telegram:123",
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
@@ -132,6 +133,7 @@ describe("abort detection", () => {
|
||||
ctx: {
|
||||
CommandBody: "/stop",
|
||||
RawBody: "/stop",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
@@ -188,6 +190,7 @@ describe("abort detection", () => {
|
||||
ctx: {
|
||||
CommandBody: "/stop",
|
||||
RawBody: "/stop",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
|
||||
@@ -132,7 +132,7 @@ export async function tryFastAbortFromMessage(params: {
|
||||
const abortRequested = normalized === "/stop" || isAbortTrigger(stripped);
|
||||
if (!abortRequested) return { handled: false, aborted: false };
|
||||
|
||||
const commandAuthorized = ctx.CommandAuthorized ?? true;
|
||||
const commandAuthorized = ctx.CommandAuthorized ?? false;
|
||||
const auth = resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function getReplyFromConfig(
|
||||
activeModel: { provider, model },
|
||||
});
|
||||
|
||||
const commandAuthorized = ctx.CommandAuthorized ?? true;
|
||||
const commandAuthorized = ctx.CommandAuthorized ?? false;
|
||||
resolveCommandAuthorization({
|
||||
ctx,
|
||||
cfg,
|
||||
|
||||
@@ -18,6 +18,14 @@ describe("gateway ws log helpers", () => {
|
||||
expect(formatForLog(obj)).toBe("Oops: failed: code=E1");
|
||||
});
|
||||
|
||||
test("formatForLog redacts obvious secrets", () => {
|
||||
const token = "sk-abcdefghijklmnopqrstuvwxyz123456";
|
||||
const out = formatForLog({ token });
|
||||
expect(out).toContain("token");
|
||||
expect(out).not.toContain(token);
|
||||
expect(out).toContain("…");
|
||||
});
|
||||
|
||||
test("summarizeAgentEventForWsLog extracts useful fields", () => {
|
||||
const summary = summarizeAgentEventForWsLog({
|
||||
runId: "12345678-1234-1234-1234-123456789abc",
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import chalk from "chalk";
|
||||
import { isVerbose } from "../globals.js";
|
||||
import { getDefaultRedactPatterns, redactSensitiveText } from "../logging/redact.js";
|
||||
import { shouldLogSubsystemToConsole } from "../logging.js";
|
||||
import { DEFAULT_WS_SLOW_MS, getGatewayWsLogStyle } from "./ws-logging.js";
|
||||
|
||||
const LOG_VALUE_LIMIT = 240;
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const WS_LOG_REDACT_OPTIONS = {
|
||||
mode: "tools" as const,
|
||||
patterns: getDefaultRedactPatterns(),
|
||||
};
|
||||
|
||||
type WsInflightEntry = {
|
||||
ts: number;
|
||||
@@ -61,7 +66,8 @@ export function formatForLog(value: unknown): string {
|
||||
? String(value)
|
||||
: JSON.stringify(value);
|
||||
if (!str) return "";
|
||||
return str.length > LOG_VALUE_LIMIT ? `${str.slice(0, LOG_VALUE_LIMIT)}...` : str;
|
||||
const redacted = redactSensitiveText(str, WS_LOG_REDACT_OPTIONS);
|
||||
return redacted.length > LOG_VALUE_LIMIT ? `${redacted.slice(0, LOG_VALUE_LIMIT)}...` : redacted;
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
@@ -269,6 +269,52 @@ describe("security audit", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not flag Discord slash commands when dm.allowFrom includes a Discord snowflake id", async () => {
|
||||
const prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||
const tmp = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), "clawdbot-security-audit-discord-allowfrom-snowflake-"),
|
||||
);
|
||||
process.env.CLAWDBOT_STATE_DIR = tmp;
|
||||
await fs.mkdir(path.join(tmp, "credentials"), { recursive: true, mode: 0o700 });
|
||||
try {
|
||||
const cfg: ClawdbotConfig = {
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "t",
|
||||
dm: { allowFrom: ["387380367612706819"] },
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"123": {
|
||||
channels: {
|
||||
general: { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const res = await runSecurityAudit({
|
||||
config: cfg,
|
||||
includeFilesystem: false,
|
||||
includeChannelSecurity: true,
|
||||
plugins: [discordPlugin],
|
||||
});
|
||||
|
||||
expect(res.findings).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
checkId: "channels.discord.commands.native.no_allowlists",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
if (prevStateDir == null) delete process.env.CLAWDBOT_STATE_DIR;
|
||||
else process.env.CLAWDBOT_STATE_DIR = prevStateDir;
|
||||
}
|
||||
});
|
||||
|
||||
it("flags Discord slash commands when access-group enforcement is disabled and no users allowlist exists", async () => {
|
||||
const prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-security-audit-discord-open-"));
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../../../auto-reply/reply/provider-dispatcher.js";
|
||||
import type { getReplyFromConfig } from "../../../auto-reply/reply.js";
|
||||
import type { ReplyPayload } from "../../../auto-reply/types.js";
|
||||
import { isControlCommandMessage } from "../../../auto-reply/command-detection.js";
|
||||
import { hasInlineCommandTokens, isControlCommandMessage } from "../../../auto-reply/command-detection.js";
|
||||
import { finalizeInboundContext } from "../../../auto-reply/reply/inbound-context.js";
|
||||
import { toLocationContext } from "../../../channels/location.js";
|
||||
import type { loadConfig } from "../../../config/config.js";
|
||||
@@ -229,7 +229,9 @@ export async function processMessage(params: {
|
||||
const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
|
||||
let didLogHeartbeatStrip = false;
|
||||
let didSendReply = false;
|
||||
const commandAuthorized = isControlCommandMessage(params.msg.body, params.cfg)
|
||||
const shouldComputeCommandAuthorized =
|
||||
isControlCommandMessage(params.msg.body, params.cfg) || hasInlineCommandTokens(params.msg.body);
|
||||
const commandAuthorized = shouldComputeCommandAuthorized
|
||||
? await resolveWhatsAppCommandAuthorized({ cfg: params.cfg, msg: params.msg })
|
||||
: undefined;
|
||||
const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
|
||||
|
||||
Reference in New Issue
Block a user