mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
f9a7565d6f
* auto-claude: subtask-1-1 - Create skill.json with SBOM, OpenClaw config, and required binaries Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-2 - Create SKILL.md with YAML frontmatter and documentation * auto-claude: subtask-1-3 - Create CHANGELOG.md starting at version 0.1.0 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-4 - Create directory structure (scripts/, lib/, hooks/, test/) * auto-claude: subtask-2-1 - Create lib/types.ts with Vulnerability and ScanReport interfaces - Defined VulnerabilitySource type with 7 possible sources (npm-audit, pip-audit, osv, nvd, github, sast, dast) - Defined SeverityLevel type with 5 severity levels (critical, high, medium, low, info) - Created Vulnerability interface with all required fields: id, source, severity, package, version, title, description, references, discovered_at, and optional fixed_version - Created ScanReport interface with scan_id, timestamp, target, vulnerabilities array, and summary counts - Added HookEvent and HookContext types for OpenClaw hook integration - Follows patterns from clawsec-suite advisory-guardian types Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-2 - Create lib/utils.mjs with subprocess execution and JSON parsing helpers Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-3 - Create lib/report.mjs for unified vulnerability re Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-3-1 - Create scripts/scan_dependencies.mjs for npm audit and pip-audit integration - Implements npm audit JSON output parsing with non-zero exit handling - Implements pip-audit JSON output parsing with -f json flag - Handles missing package-lock.json/requirements.txt gracefully - Checks for command availability (npm, pip-audit) before running - Converts audit outputs to unified Vulnerability schema - Generates ScanReport with UUID scan_id and timestamp - Supports --target and --format (json|text) CLI flags - Edge cases: missing files, unavailable commands, malformed JSON - Verification passes: UUID scan_id matches pattern ^[0-9a-f-]{36}$ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-1 - Create scripts/query_cve_databases.mjs with OSV pr Implemented CVE database integration with: - queryOSV(): Primary CVE source using OSV API (free, no auth) - queryNVD(): Fallback NVD API with 6s rate limiting (gated by CLAWSEC_NVD_API_KEY) - queryGitHub(): Placeholder for future GitHub Advisory Database integration - enrichVulnerability(): Multi-database enrichment pipeline - Normalization to unified Vulnerability schema with severity, references, fixed versions - Graceful error handling for network failures and API errors Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-5-1 - Create scripts/sast_analyzer.mjs to run Semgrep and Bandit Implemented static analysis engine following scan_dependencies.mjs pattern: - Runs Semgrep for JS/TS with --config auto and --json output - Runs Bandit for Python with -r <path> -f json -c pyproject.toml - Handles non-zero exit codes gracefully (tools exit 1 on findings) - Parses JSON output and converts to unified Vulnerability schema - Supports --target and --format CLI flags - Gracefully handles missing tools (semgrep, bandit) - Generates ScanReport with UUID scan_id and severity summary Verification passed: JSON output with valid vulnerabilities array Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-6-1 - Create scripts/dast_runner.mjs with basic security test framework - Implemented DAST framework with 4 security test cases: - DAST-001: Hook handler malicious input test (XSS, command injection, path traversal) - DAST-002: Hook handler timeout enforcement (30s default) - DAST-003: Hook handler resource limits (memory/CPU) - DAST-004: Hook handler event mutation safety - Supports --target, --format (json|text), --timeout CLI flags - Returns unified ScanReport with vulnerability schema - Executes all test cases with configurable timeout - Tests malicious input patterns: XSS, SQL injection, command injection, path traversal, null bytes, large payloads - v1 scope: basic test framework for hook security testing (full agent workflow DAST is future work) Verification: - ✅ Framework loads and executes 4 test cases - ✅ Timeout enforcement working (30s default, configurable via --timeout) - ✅ JSON output with valid scan_id - ✅ Text format output working - ✅ Help output displays usage information * auto-claude: subtask-7-1 - Create scripts/runner.sh as main entry point with CLI flag parsing - Orchestrates all scanning engines (dependency, SAST, DAST, CVE) - Supports --target (required), --output, --format flags - Merges reports from all scanners using jq - Provides --help documentation - Follows openclaw-audit-watchdog/scripts/runner.sh pattern - Includes skip flags for selective scanning - Verification: --help shows --target flag * auto-claude: subtask-8-1 - Create hooks/clawsec-scanner-hook/HOOK.md with hook metadata - Added YAML frontmatter with hook name, description, and OpenClaw events - Documented hook purpose: periodic vulnerability scanning on agent:bootstrap and command:new - Described four scanning engines: dependency, SAST, DAST, CVE lookup - Added safety contract (non-blocking, read-only, configurable interval) - Documented all environment variables (core config, CVE integration, selective scanning, advanced options) - Listed required binaries (node, npm, python3, pip-audit, semgrep, bandit, jq, curl) - Follows clawsec-advisory-guardian/HOOK.md pattern Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-8-2 - Create hooks/clawsec-scanner-hook/handler.ts with event.messages mutation - Implement hook handler following clawsec-advisory-guardian pattern - Add rate-limited scanning with configurable interval (default 24h) - Support event types: agent:bootstrap and command:new - Integrate with runner.sh for vulnerability scanning - Deduplicate vulnerabilities using state file persistence - Filter findings by minimum severity (default: medium) - Push scan results to event.messages array - Support selective scanning via environment variables - Handle failures gracefully with partial results Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-8-3 - Create scripts/setup_scanner_hook.mjs for hook installation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-9-1 - Create test/dependency_scanner.test.mjs for dependency scanning tests - Created test harness (test/lib/test_harness.mjs) with test utilities - Created comprehensive test suite with 20 tests covering: - normalizeSeverity function (all severity levels) - safeJsonParse function (valid, invalid, empty inputs) - getTimestamp and generateUuid functions - commandExists function (found and not found cases) - generateReport function (empty and with vulnerabilities) - formatReportJson and formatReportText functions - Report structure validation - Temp directory creation and cleanup - All tests pass successfully (20/20) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-9-2 - Create test/cve_integration.test.mjs for CVE database API tests Added comprehensive CVE integration tests covering: - OSV API query and normalization - NVD API query with rate limiting - GitHub Advisory Database placeholder - Multi-source enrichment - Error handling and network failures - Vulnerability structure validation - Multiple ecosystem support (npm, PyPI) Tests gracefully handle network unavailability and skip API key-dependent tests. All 20 tests passing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-9-3 - Create test/sast_engine.test.mjs for static analysis tests - Added comprehensive test suite for SAST engine functionality - Tests cover Semgrep and Bandit output parsing - Validates severity normalization and vulnerability data structures - Includes edge case handling for malformed JSON and missing fields - All 16 tests passing Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-10-2 - Run ESLint with zero warnings - Add no-unused-vars rule with argsIgnorePattern to .mjs files in ESLint config - Prefix unused parameters with underscore in handler.ts, dast_runner.mjs, query_cve_databases.mjs - Remove unused error binding in handler.ts catch block - Remove unused result variable in cve_integration.test.mjs - Remove unused SAMPLE_OSV_VULN and SAMPLE_NVD_CVE constants - Remove unused safeJsonParse import from query_cve_databases.mjs Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(clawsec-scanner): resolve baz logical scanner findings * fix(clawsec-scanner): make scanner state parsing type-safe * chore(clawsec-scanner): bump version to 0.0.1 --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
252 lines
8.0 KiB
JavaScript
252 lines
8.0 KiB
JavaScript
import { generateUuid, getTimestamp } from "./utils.mjs";
|
|
|
|
/**
|
|
* @typedef {import('./types.ts').Vulnerability} Vulnerability
|
|
* @typedef {import('./types.ts').ScanReport} ScanReport
|
|
* @typedef {import('./types.ts').SeverityLevel} SeverityLevel
|
|
*/
|
|
|
|
/**
|
|
* Generate a unified vulnerability report from scan results.
|
|
*
|
|
* @param {Vulnerability[]} vulnerabilities - Array of detected vulnerabilities
|
|
* @param {string} target - Target path that was scanned
|
|
* @returns {ScanReport}
|
|
*/
|
|
export function generateReport(vulnerabilities, target = ".") {
|
|
const summary = {
|
|
critical: 0,
|
|
high: 0,
|
|
medium: 0,
|
|
low: 0,
|
|
info: 0,
|
|
};
|
|
|
|
// Count vulnerabilities by severity
|
|
for (const vuln of vulnerabilities) {
|
|
const severity = vuln.severity;
|
|
if (severity in summary) {
|
|
summary[severity]++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
scan_id: generateUuid(),
|
|
timestamp: getTimestamp(),
|
|
target,
|
|
vulnerabilities,
|
|
summary,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format a scan report as JSON string.
|
|
*
|
|
* @param {ScanReport} report - Scan report to format
|
|
* @param {boolean} pretty - Whether to pretty-print JSON
|
|
* @returns {string}
|
|
*/
|
|
export function formatReportJson(report, pretty = true) {
|
|
return pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
}
|
|
|
|
/**
|
|
* Format a scan report as human-readable text.
|
|
*
|
|
* @param {ScanReport} report - Scan report to format
|
|
* @returns {string}
|
|
*/
|
|
export function formatReportText(report) {
|
|
const lines = [];
|
|
|
|
// Header
|
|
lines.push("═══════════════════════════════════════════════════════════════");
|
|
lines.push(" VULNERABILITY SCAN REPORT");
|
|
lines.push("═══════════════════════════════════════════════════════════════");
|
|
lines.push("");
|
|
lines.push(`Scan ID: ${report.scan_id}`);
|
|
lines.push(`Timestamp: ${report.timestamp}`);
|
|
lines.push(`Target: ${report.target}`);
|
|
lines.push("");
|
|
|
|
// Summary
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("SUMMARY");
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("");
|
|
|
|
const total = report.vulnerabilities.length;
|
|
const { critical, high, medium, low, info } = report.summary;
|
|
|
|
lines.push(`Total Vulnerabilities: ${total}`);
|
|
lines.push("");
|
|
|
|
if (critical > 0) {
|
|
lines.push(` 🔴 Critical: ${critical}`);
|
|
}
|
|
if (high > 0) {
|
|
lines.push(` 🟠 High: ${high}`);
|
|
}
|
|
if (medium > 0) {
|
|
lines.push(` 🟡 Medium: ${medium}`);
|
|
}
|
|
if (low > 0) {
|
|
lines.push(` 🔵 Low: ${low}`);
|
|
}
|
|
if (info > 0) {
|
|
lines.push(` ⚪ Info: ${info}`);
|
|
}
|
|
|
|
if (total === 0) {
|
|
lines.push(" ✓ No vulnerabilities detected");
|
|
}
|
|
|
|
lines.push("");
|
|
|
|
// Detailed findings
|
|
if (report.vulnerabilities.length > 0) {
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("DETAILED FINDINGS");
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("");
|
|
|
|
// Group vulnerabilities by severity
|
|
const bySeverity = {
|
|
critical: [],
|
|
high: [],
|
|
medium: [],
|
|
low: [],
|
|
info: [],
|
|
};
|
|
|
|
for (const vuln of report.vulnerabilities) {
|
|
bySeverity[vuln.severity].push(vuln);
|
|
}
|
|
|
|
// Display in order: critical -> high -> medium -> low -> info
|
|
const severityOrder = ["critical", "high", "medium", "low", "info"];
|
|
|
|
for (const severity of severityOrder) {
|
|
const vulns = bySeverity[severity];
|
|
if (vulns.length === 0) continue;
|
|
|
|
const severityIcon = getSeverityIcon(severity);
|
|
lines.push(`${severityIcon} ${severity.toUpperCase()}`);
|
|
lines.push("");
|
|
|
|
for (const vuln of vulns) {
|
|
lines.push(` ID: ${vuln.id}`);
|
|
lines.push(` Package: ${vuln.package} @ ${vuln.version}`);
|
|
if (vuln.fixed_version) {
|
|
lines.push(` Fix: ${vuln.fixed_version}`);
|
|
}
|
|
lines.push(` Source: ${vuln.source}`);
|
|
lines.push(` Title: ${vuln.title}`);
|
|
|
|
// Wrap description at 60 chars
|
|
const descLines = wrapText(vuln.description, 60);
|
|
lines.push(" Description:");
|
|
for (const line of descLines) {
|
|
lines.push(` ${line}`);
|
|
}
|
|
|
|
if (vuln.references.length > 0) {
|
|
lines.push(" References:");
|
|
for (const ref of vuln.references.slice(0, 3)) {
|
|
lines.push(` - ${ref}`);
|
|
}
|
|
if (vuln.references.length > 3) {
|
|
lines.push(` ... and ${vuln.references.length - 3} more`);
|
|
}
|
|
}
|
|
|
|
lines.push("");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recommendations
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("RECOMMENDATIONS");
|
|
lines.push("───────────────────────────────────────────────────────────────");
|
|
lines.push("");
|
|
|
|
if (critical > 0 || high > 0) {
|
|
lines.push("⚠️ URGENT: Critical or high severity vulnerabilities detected!");
|
|
lines.push("");
|
|
lines.push("Recommended actions:");
|
|
lines.push(" 1. Review all critical and high severity findings immediately");
|
|
lines.push(" 2. Update vulnerable dependencies to fixed versions");
|
|
lines.push(" 3. Run scanner again to verify remediation");
|
|
lines.push("");
|
|
} else if (medium > 0) {
|
|
lines.push("⚠️ Medium severity vulnerabilities detected.");
|
|
lines.push("");
|
|
lines.push("Recommended actions:");
|
|
lines.push(" 1. Review findings and assess impact on your use case");
|
|
lines.push(" 2. Plan updates during next maintenance window");
|
|
lines.push("");
|
|
} else if (low > 0 || info > 0) {
|
|
lines.push("✓ No critical or high severity vulnerabilities detected.");
|
|
lines.push("");
|
|
lines.push("Recommended actions:");
|
|
lines.push(" 1. Review low/info findings for awareness");
|
|
lines.push(" 2. Consider updates when convenient");
|
|
lines.push("");
|
|
} else {
|
|
lines.push("✓ No vulnerabilities detected. Your code is clean!");
|
|
lines.push("");
|
|
}
|
|
|
|
lines.push("═══════════════════════════════════════════════════════════════");
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
/**
|
|
* Get emoji icon for severity level.
|
|
*
|
|
* @param {SeverityLevel} severity - Severity level
|
|
* @returns {string}
|
|
*/
|
|
function getSeverityIcon(severity) {
|
|
const icons = {
|
|
critical: "🔴",
|
|
high: "🟠",
|
|
medium: "🟡",
|
|
low: "🔵",
|
|
info: "⚪",
|
|
};
|
|
return icons[severity] || "⚪";
|
|
}
|
|
|
|
/**
|
|
* Wrap text to specified width.
|
|
*
|
|
* @param {string} text - Text to wrap
|
|
* @param {number} width - Maximum line width
|
|
* @returns {string[]}
|
|
*/
|
|
function wrapText(text, width) {
|
|
const words = text.split(/\s+/);
|
|
const lines = [];
|
|
let currentLine = "";
|
|
|
|
for (const word of words) {
|
|
if (currentLine.length + word.length + 1 <= width) {
|
|
currentLine += (currentLine ? " " : "") + word;
|
|
} else {
|
|
if (currentLine) {
|
|
lines.push(currentLine);
|
|
}
|
|
currentLine = word;
|
|
}
|
|
}
|
|
|
|
if (currentLine) {
|
|
lines.push(currentLine);
|
|
}
|
|
|
|
return lines.length > 0 ? lines : [""];
|
|
}
|