feat(security): expand audit and safe --fix

This commit is contained in:
Peter Steinberger
2026-01-15 05:31:35 +00:00
parent f11a89031b
commit ca1902fb4e
9 changed files with 1077 additions and 71 deletions

View File

@@ -2,8 +2,32 @@ import { describe, expect, it } from "vitest";
import type { ClawdbotConfig } from "../config/config.js";
import { runSecurityAudit } from "./audit.js";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
describe("security audit", () => {
it("includes an attack surface summary (info)", async () => {
const cfg: ClawdbotConfig = {
channels: { whatsapp: { groupPolicy: "open" }, telegram: { groupPolicy: "allowlist" } },
tools: { elevated: { enabled: true, allowFrom: { whatsapp: ["+1"] } } },
hooks: { enabled: true },
browser: { enabled: true },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({ checkId: "summary.attack_surface", severity: "info" }),
]),
);
});
it("flags non-loopback bind without auth as critical", async () => {
const cfg: ClawdbotConfig = {
gateway: {
@@ -175,4 +199,124 @@ describe("security audit", () => {
]),
);
});
it("warns on legacy model configuration", async () => {
const cfg: ClawdbotConfig = {
agents: { defaults: { model: { primary: "openai/gpt-3.5-turbo" } } },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([expect.objectContaining({ checkId: "models.legacy", severity: "warn" })]),
);
});
it("warns when hooks token looks short", async () => {
const cfg: ClawdbotConfig = {
hooks: { enabled: true, token: "short" },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([expect.objectContaining({ checkId: "hooks.token_too_short", severity: "warn" })]),
);
});
it("warns when state/config look like a synced folder", async () => {
const cfg: ClawdbotConfig = {};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
stateDir: "/Users/test/Dropbox/.clawdbot",
configPath: "/Users/test/Dropbox/.clawdbot/clawdbot.json",
});
expect(res.findings).toEqual(
expect.arrayContaining([expect.objectContaining({ checkId: "fs.synced_dir", severity: "warn" })]),
);
});
it("flags group/world-readable config include files", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-security-audit-"));
const stateDir = path.join(tmp, "state");
await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });
const includePath = path.join(stateDir, "extra.json5");
await fs.writeFile(includePath, "{ logging: { redactSensitive: 'off' } }\n", "utf-8");
await fs.chmod(includePath, 0o644);
const configPath = path.join(stateDir, "clawdbot.json");
await fs.writeFile(configPath, `{ "$include": "./extra.json5" }\n`, "utf-8");
await fs.chmod(configPath, 0o600);
const cfg: ClawdbotConfig = { logging: { redactSensitive: "off" } };
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: true,
includeChannelSecurity: false,
stateDir,
configPath,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({ checkId: "fs.config_include.perms_world_readable", severity: "critical" }),
]),
);
});
it("flags extensions without plugins.allow", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-security-audit-"));
const stateDir = path.join(tmp, "state");
await fs.mkdir(path.join(stateDir, "extensions", "some-plugin"), { recursive: true, mode: 0o700 });
const cfg: ClawdbotConfig = {};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: true,
includeChannelSecurity: false,
stateDir,
configPath: path.join(stateDir, "clawdbot.json"),
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({ checkId: "plugins.extensions_no_allowlist", severity: "warn" }),
]),
);
});
it("flags open groupPolicy when tools.elevated is enabled", async () => {
const cfg: ClawdbotConfig = {
tools: { elevated: { enabled: true, allowFrom: { whatsapp: ["+1"] } } },
channels: { whatsapp: { groupPolicy: "open" } },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({
checkId: "security.exposure.open_groups_with_elevated",
severity: "critical",
}),
]),
);
});
});