feat: add security audit + onboarding checkpoint
This commit is contained in:
91
src/cli/security-cli.ts
Normal file
91
src/cli/security-cli.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import chalk from "chalk";
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { runSecurityAudit } from "../security/audit.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
|
||||
type SecurityAuditOptions = {
|
||||
json?: boolean;
|
||||
deep?: boolean;
|
||||
};
|
||||
|
||||
function formatSummary(summary: { critical: number; warn: number; info: number }): string {
|
||||
const rich = isRich();
|
||||
const c = summary.critical;
|
||||
const w = summary.warn;
|
||||
const i = summary.info;
|
||||
const parts: string[] = [];
|
||||
parts.push(rich ? theme.error(`${c} critical`) : `${c} critical`);
|
||||
parts.push(rich ? theme.warn(`${w} warn`) : `${w} warn`);
|
||||
parts.push(rich ? theme.muted(`${i} info`) : `${i} info`);
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
export function registerSecurityCli(program: Command) {
|
||||
const security = program.command("security").description("Security tools (audit)");
|
||||
|
||||
security
|
||||
.command("audit")
|
||||
.description("Audit config + local state for common security foot-guns")
|
||||
.option("--deep", "Attempt live Gateway probe (best-effort)", false)
|
||||
.option("--json", "Print JSON", false)
|
||||
.action(async (opts: SecurityAuditOptions) => {
|
||||
const cfg = loadConfig();
|
||||
const report = await runSecurityAudit({
|
||||
config: cfg,
|
||||
deep: Boolean(opts.deep),
|
||||
includeFilesystem: true,
|
||||
includeChannelSecurity: true,
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(report, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const rich = isRich();
|
||||
const heading = (text: string) => (rich ? theme.heading(text) : text);
|
||||
const muted = (text: string) => (rich ? theme.muted(text) : text);
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(heading("Clawdbot security audit"));
|
||||
lines.push(muted(`Summary: ${formatSummary(report.summary)}`));
|
||||
lines.push(muted(`Run deeper: clawdbot security audit --deep`));
|
||||
|
||||
const bySeverity = (sev: "critical" | "warn" | "info") =>
|
||||
report.findings.filter((f) => f.severity === sev);
|
||||
|
||||
const render = (sev: "critical" | "warn" | "info") => {
|
||||
const list = bySeverity(sev);
|
||||
if (list.length === 0) return;
|
||||
const label =
|
||||
sev === "critical"
|
||||
? rich
|
||||
? theme.error("CRITICAL")
|
||||
: "CRITICAL"
|
||||
: sev === "warn"
|
||||
? rich
|
||||
? theme.warn("WARN")
|
||||
: "WARN"
|
||||
: rich
|
||||
? theme.muted("INFO")
|
||||
: "INFO";
|
||||
lines.push("");
|
||||
lines.push(heading(label));
|
||||
for (const f of list) {
|
||||
lines.push(`${chalk.gray(f.checkId)} ${f.title}`);
|
||||
lines.push(` ${f.detail}`);
|
||||
if (f.remediation?.trim()) lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`);
|
||||
}
|
||||
};
|
||||
|
||||
render("critical");
|
||||
render("warn");
|
||||
render("info");
|
||||
|
||||
defaultRuntime.log(lines.join("\n"));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user