mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
106 lines
3.0 KiB
JavaScript
Executable File
106 lines
3.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Render a human-readable security audit report from openclaw JSON.
|
|
*
|
|
* Usage:
|
|
* node render_report.mjs --audit audit.json --deep deep.json --label "host label"
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
|
|
function readJsonSafe(p, label) {
|
|
if (!p) return { findings: [], summary: {}, error: `${label} missing` };
|
|
try {
|
|
const s = fs.readFileSync(p, "utf8");
|
|
return JSON.parse(s);
|
|
} catch (e) {
|
|
return { findings: [], summary: {}, error: `${label} parse failed: ${e?.message || String(e)}` };
|
|
}
|
|
}
|
|
|
|
function pickFindings(report) {
|
|
const findings = Array.isArray(report?.findings) ? report.findings : [];
|
|
const bySev = (sev) => findings.filter((f) => f?.severity === sev);
|
|
return {
|
|
critical: bySev("critical"),
|
|
warn: bySev("warn"),
|
|
info: bySev("info"),
|
|
summary: report?.summary ?? null,
|
|
};
|
|
}
|
|
|
|
function lineForFinding(f) {
|
|
const id = f?.checkId ?? "(no-checkId)";
|
|
const title = f?.title ?? "(no-title)";
|
|
const fix = (f?.remediation ?? "").trim();
|
|
const fixLine = fix ? `Fix: ${fix}` : "";
|
|
return `- ${id} ${title}${fixLine ? `\n ${fixLine}` : ""}`;
|
|
}
|
|
|
|
function render({ audit, deep, label }) {
|
|
const now = new Date().toISOString();
|
|
const a = pickFindings(audit);
|
|
const d = pickFindings(deep);
|
|
|
|
const summary = a.summary || d.summary || { critical: 0, warn: 0, info: 0 };
|
|
|
|
const lines = [];
|
|
lines.push(`openclaw security audit report${label ? ` -- ${label}` : ""}`);
|
|
lines.push(`Time: ${now}`);
|
|
lines.push(`Summary: ${summary.critical ?? 0} critical · ${summary.warn ?? 0} warn · ${summary.info ?? 0} info`);
|
|
|
|
const top = [];
|
|
top.push(...a.critical, ...a.warn);
|
|
const seen = new Set();
|
|
const deduped = [];
|
|
for (const f of top) {
|
|
const key = `${f?.severity}:${f?.checkId}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
deduped.push(f);
|
|
}
|
|
|
|
if (deduped.length) {
|
|
lines.push("");
|
|
lines.push("Findings (critical/warn):");
|
|
for (const f of deduped.slice(0, 25)) lines.push(lineForFinding(f));
|
|
if (deduped.length > 25) lines.push(`…${deduped.length - 25} more`);
|
|
}
|
|
|
|
// Surface deep probe failure if present
|
|
const deepProbe = Array.isArray(deep?.findings)
|
|
? deep.findings.find((f) => f?.checkId === "gateway.probe_failed")
|
|
: null;
|
|
if (deepProbe) {
|
|
lines.push("");
|
|
lines.push("Deep probe:");
|
|
lines.push(lineForFinding(deepProbe));
|
|
}
|
|
|
|
const errors = [audit?.error, deep?.error].filter(Boolean);
|
|
if (errors.length) {
|
|
lines.push("");
|
|
lines.push("Errors:");
|
|
for (const e of errors) lines.push(`- ${e}`);
|
|
}
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const out = {};
|
|
for (let i = 0; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === "--audit") out.audit = argv[++i];
|
|
else if (a === "--deep") out.deep = argv[++i];
|
|
else if (a === "--label") out.label = argv[++i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const audit = readJsonSafe(args.audit, "audit");
|
|
const deep = readJsonSafe(args.deep, "deep");
|
|
const report = render({ audit, deep, label: args.label });
|
|
process.stdout.write(report + "\n");
|