Compare commits
2 Commits
main
...
codex/outb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c51af21281 | ||
|
|
92a1ba88bd |
@ -143,6 +143,24 @@ describe("resolveMessageChannelSelection", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("skips configured-channel scanning when includeConfigured is false", async () => {
|
||||||
|
const isConfigured = vi.fn(async () => true);
|
||||||
|
mocks.listChannelPlugins.mockReturnValue([makePlugin({ id: "whatsapp", isConfigured })]);
|
||||||
|
|
||||||
|
const selection = await resolveMessageChannelSelection({
|
||||||
|
cfg: {} as never,
|
||||||
|
channel: "telegram",
|
||||||
|
includeConfigured: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(selection).toEqual({
|
||||||
|
channel: "telegram",
|
||||||
|
configured: [],
|
||||||
|
source: "explicit",
|
||||||
|
});
|
||||||
|
expect(isConfigured).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("falls back to tool context channel when explicit channel is unknown", async () => {
|
it("falls back to tool context channel when explicit channel is unknown", async () => {
|
||||||
const selection = await resolveMessageChannelSelection({
|
const selection = await resolveMessageChannelSelection({
|
||||||
cfg: {} as never,
|
cfg: {} as never,
|
||||||
|
|||||||
@ -146,11 +146,15 @@ export async function resolveMessageChannelSelection(params: {
|
|||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
channel?: string | null;
|
channel?: string | null;
|
||||||
fallbackChannel?: string | null;
|
fallbackChannel?: string | null;
|
||||||
|
includeConfigured?: boolean;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
channel: MessageChannelId;
|
channel: MessageChannelId;
|
||||||
configured: MessageChannelId[];
|
configured: MessageChannelId[];
|
||||||
source: MessageChannelSelectionSource;
|
source: MessageChannelSelectionSource;
|
||||||
}> {
|
}> {
|
||||||
|
const includeConfigured = params.includeConfigured !== false;
|
||||||
|
const resolveConfigured = async () =>
|
||||||
|
includeConfigured ? await listConfiguredMessageChannels(params.cfg) : [];
|
||||||
const normalized = normalizeMessageChannel(params.channel);
|
const normalized = normalizeMessageChannel(params.channel);
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
const availableExplicit = resolveAvailableKnownChannel({
|
const availableExplicit = resolveAvailableKnownChannel({
|
||||||
@ -165,7 +169,7 @@ export async function resolveMessageChannelSelection(params: {
|
|||||||
if (fallback) {
|
if (fallback) {
|
||||||
return {
|
return {
|
||||||
channel: fallback,
|
channel: fallback,
|
||||||
configured: await listConfiguredMessageChannels(params.cfg),
|
configured: await resolveConfigured(),
|
||||||
source: "tool-context-fallback",
|
source: "tool-context-fallback",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -176,7 +180,7 @@ export async function resolveMessageChannelSelection(params: {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
channel: availableExplicit,
|
channel: availableExplicit,
|
||||||
configured: await listConfiguredMessageChannels(params.cfg),
|
configured: await resolveConfigured(),
|
||||||
source: "explicit",
|
source: "explicit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -188,12 +192,12 @@ export async function resolveMessageChannelSelection(params: {
|
|||||||
if (fallback) {
|
if (fallback) {
|
||||||
return {
|
return {
|
||||||
channel: fallback,
|
channel: fallback,
|
||||||
configured: await listConfiguredMessageChannels(params.cfg),
|
configured: await resolveConfigured(),
|
||||||
source: "tool-context-fallback",
|
source: "tool-context-fallback",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const configured = await listConfiguredMessageChannels(params.cfg);
|
const configured = await resolveConfigured();
|
||||||
if (configured.length === 1) {
|
if (configured.length === 1) {
|
||||||
return { channel: configured[0], configured, source: "single-configured" };
|
return { channel: configured[0], configured, source: "single-configured" };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,6 +226,7 @@ async function resolveChannel(
|
|||||||
cfg,
|
cfg,
|
||||||
channel: readStringParam(params, "channel"),
|
channel: readStringParam(params, "channel"),
|
||||||
fallbackChannel: toolContext?.currentChannelProvider,
|
fallbackChannel: toolContext?.currentChannelProvider,
|
||||||
|
includeConfigured: false,
|
||||||
});
|
});
|
||||||
if (selection.source === "tool-context-fallback") {
|
if (selection.source === "tool-context-fallback") {
|
||||||
params.channel = selection.channel;
|
params.channel = selection.channel;
|
||||||
@ -318,14 +319,13 @@ async function handleBroadcastAction(
|
|||||||
throw new Error("Broadcast requires at least one target in --targets.");
|
throw new Error("Broadcast requires at least one target in --targets.");
|
||||||
}
|
}
|
||||||
const channelHint = readStringParam(params, "channel");
|
const channelHint = readStringParam(params, "channel");
|
||||||
const configured = await listConfiguredMessageChannels(input.cfg);
|
|
||||||
if (configured.length === 0) {
|
|
||||||
throw new Error("Broadcast requires at least one configured channel.");
|
|
||||||
}
|
|
||||||
const targetChannels =
|
const targetChannels =
|
||||||
channelHint && channelHint.trim().toLowerCase() !== "all"
|
channelHint && channelHint.trim().toLowerCase() !== "all"
|
||||||
? [await resolveChannel(input.cfg, { channel: channelHint }, input.toolContext)]
|
? [await resolveChannel(input.cfg, { channel: channelHint }, input.toolContext)]
|
||||||
: configured;
|
: await listConfiguredMessageChannels(input.cfg);
|
||||||
|
if (targetChannels.length === 0) {
|
||||||
|
throw new Error("Broadcast requires at least one configured channel.");
|
||||||
|
}
|
||||||
const results: Array<{
|
const results: Array<{
|
||||||
channel: ChannelId;
|
channel: ChannelId;
|
||||||
to: string;
|
to: string;
|
||||||
|
|||||||
@ -136,6 +136,7 @@ async function resolveRequiredChannel(params: {
|
|||||||
await resolveMessageChannelSelection({
|
await resolveMessageChannelSelection({
|
||||||
cfg: params.cfg,
|
cfg: params.cfg,
|
||||||
channel: params.channel,
|
channel: params.channel,
|
||||||
|
includeConfigured: false,
|
||||||
})
|
})
|
||||||
).channel;
|
).channel;
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/security/scan-paths.test.ts
Normal file
29
src/security/scan-paths.test.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const originalPlatform = process.platform;
|
||||||
|
|
||||||
|
function setPlatform(value: NodeJS.Platform): void {
|
||||||
|
Object.defineProperty(process, "platform", {
|
||||||
|
configurable: true,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
setPlatform(originalPlatform);
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("security scan path guards", () => {
|
||||||
|
it("uses Windows-aware containment checks for differently normalized paths", async () => {
|
||||||
|
setPlatform("win32");
|
||||||
|
const { isPathInside } = await import("./scan-paths.js");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
isPathInside(String.raw`C:\Workspace\Root`, String.raw`c:\workspace\root\hooks\hook`),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
isPathInside(String.raw`\\?\C:\Workspace\Root`, String.raw`C:\workspace\root\hooks\hook`),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,11 +1,8 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import { isPathInside as isBoundaryPathInside } from "../infra/path-guards.js";
|
||||||
|
|
||||||
export function isPathInside(basePath: string, candidatePath: string): boolean {
|
export function isPathInside(basePath: string, candidatePath: string): boolean {
|
||||||
const base = path.resolve(basePath);
|
return isBoundaryPathInside(basePath, candidatePath);
|
||||||
const candidate = path.resolve(candidatePath);
|
|
||||||
const rel = path.relative(base, candidate);
|
|
||||||
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeRealpathSync(filePath: string): string | null {
|
function safeRealpathSync(filePath: string): string | null {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user