mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-06 23:01:08 +00:00
218 lines
6.2 KiB
TypeScript
218 lines
6.2 KiB
TypeScript
import { Command } from "commander";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { registerSecurityCli } from "./security-cli.js";
|
|
|
|
const mocks = vi.hoisted(() => {
|
|
const runtimeLogs: string[] = [];
|
|
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
|
|
const defaultRuntime = {
|
|
log: vi.fn((...args: unknown[]) => {
|
|
runtimeLogs.push(stringifyArgs(args));
|
|
}),
|
|
error: vi.fn(),
|
|
writeStdout: vi.fn((value: string) => {
|
|
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
|
|
}),
|
|
writeJson: vi.fn((value: unknown, space = 2) => {
|
|
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
|
|
}),
|
|
exit: vi.fn((code: number) => {
|
|
throw new Error(`__exit__:${code}`);
|
|
}),
|
|
};
|
|
return {
|
|
loadConfig: vi.fn(),
|
|
runSecurityAudit: vi.fn(),
|
|
fixSecurityFootguns: vi.fn(),
|
|
resolveCommandSecretRefsViaGateway: vi.fn(),
|
|
getSecurityAuditCommandSecretTargetIds: vi.fn(
|
|
() => new Set(["gateway.auth.token", "gateway.auth.password"]),
|
|
),
|
|
defaultRuntime,
|
|
runtimeLogs,
|
|
};
|
|
});
|
|
|
|
const {
|
|
loadConfig,
|
|
runSecurityAudit,
|
|
fixSecurityFootguns,
|
|
resolveCommandSecretRefsViaGateway,
|
|
getSecurityAuditCommandSecretTargetIds,
|
|
runtimeLogs,
|
|
} = mocks;
|
|
|
|
vi.mock("../config/config.js", () => ({
|
|
loadConfig: () => mocks.loadConfig(),
|
|
}));
|
|
|
|
vi.mock("../runtime.js", () => ({
|
|
defaultRuntime: mocks.defaultRuntime,
|
|
}));
|
|
|
|
vi.mock("../security/audit.js", () => ({
|
|
runSecurityAudit: (opts: unknown) => mocks.runSecurityAudit(opts),
|
|
}));
|
|
|
|
vi.mock("../security/fix.js", () => ({
|
|
fixSecurityFootguns: () => mocks.fixSecurityFootguns(),
|
|
}));
|
|
|
|
vi.mock("./command-secret-gateway.js", () => ({
|
|
resolveCommandSecretRefsViaGateway: (opts: unknown) =>
|
|
mocks.resolveCommandSecretRefsViaGateway(opts),
|
|
}));
|
|
|
|
vi.mock("./command-secret-targets.js", () => ({
|
|
getSecurityAuditCommandSecretTargetIds: () => mocks.getSecurityAuditCommandSecretTargetIds(),
|
|
}));
|
|
|
|
function createProgram() {
|
|
const program = new Command();
|
|
program.exitOverride();
|
|
registerSecurityCli(program);
|
|
return program;
|
|
}
|
|
|
|
function primeDeepAuditConfig(sourceConfig = { gateway: { mode: "local" } }) {
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
|
resolvedConfig: sourceConfig,
|
|
diagnostics: [],
|
|
targetStatesByPath: {},
|
|
hadUnresolvedTargets: false,
|
|
});
|
|
runSecurityAudit.mockResolvedValue({
|
|
ts: 0,
|
|
summary: { critical: 0, warn: 0, info: 0 },
|
|
findings: [],
|
|
});
|
|
return sourceConfig;
|
|
}
|
|
|
|
describe("security CLI", () => {
|
|
beforeEach(() => {
|
|
runtimeLogs.length = 0;
|
|
loadConfig.mockReset();
|
|
runSecurityAudit.mockReset();
|
|
fixSecurityFootguns.mockReset();
|
|
resolveCommandSecretRefsViaGateway.mockReset();
|
|
getSecurityAuditCommandSecretTargetIds.mockClear();
|
|
fixSecurityFootguns.mockResolvedValue({
|
|
changes: [],
|
|
actions: [],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
it("runs audit with read-only SecretRef resolution and prints JSON diagnostics", async () => {
|
|
const sourceConfig = {
|
|
gateway: {
|
|
auth: {
|
|
mode: "token",
|
|
token: { source: "env", provider: "default", id: "OPENCLAW_GATEWAY_TOKEN" },
|
|
},
|
|
},
|
|
secrets: {
|
|
providers: {
|
|
default: { source: "env" },
|
|
},
|
|
},
|
|
};
|
|
const resolvedConfig = {
|
|
...sourceConfig,
|
|
gateway: {
|
|
...sourceConfig.gateway,
|
|
auth: {
|
|
...sourceConfig.gateway.auth,
|
|
token: "resolved-token",
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
|
resolvedConfig,
|
|
diagnostics: [
|
|
"security audit: gateway secrets.resolve unavailable (gateway closed); resolved command secrets locally.",
|
|
],
|
|
targetStatesByPath: {},
|
|
hadUnresolvedTargets: false,
|
|
});
|
|
runSecurityAudit.mockResolvedValue({
|
|
ts: 0,
|
|
summary: { critical: 0, warn: 1, info: 0 },
|
|
findings: [
|
|
{
|
|
checkId: "gateway.probe_failed",
|
|
severity: "warn",
|
|
title: "Gateway probe failed (deep)",
|
|
detail: "connect failed: connect ECONNREFUSED 127.0.0.1:18789",
|
|
},
|
|
],
|
|
});
|
|
|
|
await createProgram().parseAsync(["security", "audit", "--json"], { from: "user" });
|
|
|
|
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
config: sourceConfig,
|
|
commandName: "security audit",
|
|
mode: "read_only_status",
|
|
targetIds: expect.any(Set),
|
|
}),
|
|
);
|
|
expect(runSecurityAudit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
config: resolvedConfig,
|
|
sourceConfig,
|
|
deep: false,
|
|
includeFilesystem: true,
|
|
includeChannelSecurity: true,
|
|
}),
|
|
);
|
|
const payload = JSON.parse(String(runtimeLogs.at(-1)));
|
|
expect(payload.secretDiagnostics).toEqual([
|
|
"security audit: gateway secrets.resolve unavailable (gateway closed); resolved command secrets locally.",
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
title: "forwards --token to deep probe auth without altering command-level resolver mode",
|
|
argv: ["--token", "explicit-token"],
|
|
deepProbeAuth: { token: "explicit-token" },
|
|
},
|
|
{
|
|
title: "forwards --password to deep probe auth without altering command-level resolver mode",
|
|
argv: ["--password", "explicit-password"],
|
|
deepProbeAuth: { password: "explicit-password" },
|
|
},
|
|
{
|
|
title: "forwards both --token and --password to deep probe auth",
|
|
argv: ["--token", "explicit-token", "--password", "explicit-password"],
|
|
deepProbeAuth: {
|
|
token: "explicit-token",
|
|
password: "explicit-password",
|
|
},
|
|
},
|
|
])("$title", async ({ argv, deepProbeAuth }) => {
|
|
primeDeepAuditConfig();
|
|
|
|
await createProgram().parseAsync(["security", "audit", "--deep", ...argv, "--json"], {
|
|
from: "user",
|
|
});
|
|
|
|
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
mode: "read_only_status",
|
|
}),
|
|
);
|
|
expect(runSecurityAudit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
deep: true,
|
|
deepProbeAuth,
|
|
}),
|
|
);
|
|
});
|
|
});
|