mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
test(core): guard security audit boundaries
This commit is contained in:
64
src/security/audit-channel-account-metadata.test.ts
Normal file
64
src/security/audit-channel-account-metadata.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { collectChannelSecurityFindings } from "./audit-channel.js";
|
||||
|
||||
function stubChannelPlugin(): ChannelPlugin {
|
||||
return {
|
||||
id: "discord",
|
||||
meta: {
|
||||
id: "discord",
|
||||
label: "Discord",
|
||||
selectionLabel: "Discord",
|
||||
docsPath: "/docs/testing",
|
||||
blurb: "test stub",
|
||||
},
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group"],
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
defaultAccountId: () => "toString",
|
||||
inspectAccount: () => ({
|
||||
accountId: "toString",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
config: { dangerouslyAllowNameMatching: true },
|
||||
}),
|
||||
resolveAccount: () => ({
|
||||
accountId: "toString",
|
||||
enabled: true,
|
||||
config: { dangerouslyAllowNameMatching: true },
|
||||
}),
|
||||
isEnabled: () => true,
|
||||
isConfigured: () => true,
|
||||
},
|
||||
security: {},
|
||||
};
|
||||
}
|
||||
|
||||
describe("security audit channel account metadata", () => {
|
||||
it("does not treat prototype properties as explicit account config paths", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "t",
|
||||
dangerouslyAllowNameMatching: true,
|
||||
accounts: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const findings = await collectChannelSecurityFindings({
|
||||
cfg,
|
||||
plugins: [stubChannelPlugin()],
|
||||
});
|
||||
|
||||
const dangerousMatchingFinding = findings.find(
|
||||
(entry) => entry.checkId === "channels.discord.allowFrom.dangerous_name_matching_enabled",
|
||||
);
|
||||
expect(dangerousMatchingFinding).toBeDefined();
|
||||
expect(dangerousMatchingFinding?.title).not.toContain("(account: toString)");
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { collectDiscordSecurityAuditFindings } from "../../test/helpers/channels/security-audit-contract.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { collectChannelSecurityFindings } from "./audit-channel.js";
|
||||
|
||||
const { readChannelAllowFromStoreMock } = vi.hoisted(() => ({
|
||||
readChannelAllowFromStoreMock: vi.fn(async () => [] as string[]),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
|
||||
readChannelAllowFromStore: readChannelAllowFromStoreMock,
|
||||
}));
|
||||
|
||||
function stubDiscordPlugin(params: {
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown;
|
||||
inspectAccount?: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown;
|
||||
@@ -34,7 +25,24 @@ function stubDiscordPlugin(params: {
|
||||
nativeSkillsAutoEnabled: true,
|
||||
},
|
||||
security: {
|
||||
collectAuditFindings: collectDiscordSecurityAuditFindings,
|
||||
collectAuditFindings: ({ account }) => {
|
||||
const config = (account as { config?: { guilds?: unknown } }).config ?? {};
|
||||
const guilds =
|
||||
config.guilds && typeof config.guilds === "object" && !Array.isArray(config.guilds)
|
||||
? config.guilds
|
||||
: {};
|
||||
if (Object.keys(guilds).length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
checkId: "channels.discord.commands.native.no_allowlists",
|
||||
severity: "warn" as const,
|
||||
title: "Discord slash commands have no allowlists",
|
||||
detail: "test stub",
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
@@ -96,7 +104,6 @@ describe("security audit channel source-config fallback discord", () => {
|
||||
},
|
||||
};
|
||||
|
||||
readChannelAllowFromStoreMock.mockResolvedValue([]);
|
||||
const findings = await collectChannelSecurityFindings({
|
||||
cfg: resolvedConfig,
|
||||
sourceConfig,
|
||||
|
||||
@@ -171,4 +171,18 @@ describe("non-extension test boundaries", () => {
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps bundled channel security collector coverage under extension tests", () => {
|
||||
const files = [...walk(path.join(repoRoot, "src")), ...walk(path.join(repoRoot, "test"))]
|
||||
.filter((file) => !file.startsWith(BUNDLED_PLUGIN_PATH_PREFIX))
|
||||
.filter((file) => !file.startsWith("test/helpers/"))
|
||||
.filter((file) => file !== "test/extension-test-boundary.test.ts");
|
||||
|
||||
const offenders = files.filter((file) => {
|
||||
const source = fs.readFileSync(path.join(repoRoot, file), "utf8");
|
||||
return source.includes("test/helpers/channels/security-audit-contract.js");
|
||||
});
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user