mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
63de5ce08d
* auto-claude: subtask-1-1 - Create config loading utility with multi-path fallback Created load_suppression_config.mjs with: - Multi-path fallback: ~/.openclaw/security-audit.json -> .clawsec/allowlist.json - Environment variable support (OPENCLAW_AUDIT_CONFIG) - Custom path support via CLI argument - Schema validation (checkId, skill, reason, suppressedAt required) - Malformed JSON error handling - Graceful fallback to empty suppressions when no config exists - ISO 8601 date format validation with warnings Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-2 - Create example config file template - Added security-audit-config.example.json with two suppression examples - Included examples for clawsec-suite and openclaw-audit-watchdog - Created comprehensive README.md explaining configuration format - All required fields documented (checkId, skill, reason, suppressedAt) - ISO 8601 date format demonstrated - JSON validated successfully * auto-claude: subtask-1-3 - Add unit tests for config loading Added comprehensive unit tests for suppression config loading: - Valid config with all required fields - Malformed date warning (non-blocking) - Missing required field validation - Malformed JSON error handling - File not found graceful fallback - Custom path priority - Environment variable override - Missing/empty suppressions array handling All 10 tests passing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-1 - Add suppression filtering to render_report.mjs Implements suppression filtering logic for security audit findings: - Import loadSuppressionConfig for config loading - Add --config CLI argument for custom config paths - Create extractSkillName() to extract skill names from findings (tries multiple fields) - Create filterFindings() to split findings into active/suppressed - Match suppressions by BOTH checkId AND skill name (exact match required) - Attach suppression metadata (reason, suppressedAt) to suppressed findings - Modify render() to accept suppressedFindings parameter - Apply filtering in main execution before rendering Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-2 - Add INFO-SUPPRESSED section to report output - Added lineForSuppressedFinding() to format suppressed findings - Added INFO-SUPPRESSED section showing suppressed findings with reason and date - Suppressed findings are not counted in summary (already filtered) - Follows existing code patterns for report sections Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-3-1 - Add --config flag to run_audit_and_format.sh - Added --config flag to accept path to config file - Added --help flag with usage documentation - Config flag is passed to openclaw audit commands when provided - Follows existing pattern for --label flag * auto-claude: subtask-4-1 - Create integration tests for render_report with suppressions Created comprehensive integration tests covering: - Suppressed findings appear in INFO-SUPPRESSED section - Active findings appear in CRITICAL/WARN section - Summary counts exclude suppressed findings - Backward compatibility (no config) - Partial matches don't suppress (checkId or skill alone) - Multiple suppressions work correctly - Skill name extraction from path field - Skill name extraction from title field - Empty suppressions array behaves like no config Bug fix in render_report.mjs: - Summary counts now recalculated after filtering suppressed findings - Previously summary showed original counts instead of filtered counts All 10 tests passing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-2 - Manual E2E test with real openclaw audit - Fixed run_audit_and_format.sh to pass --config flag to render_report.mjs - Enhanced lineForFinding() to display skill names for better clarity - Enhanced lineForSuppressedFinding() to display skill names consistently - Created comprehensive E2E test documentation in E2E-TEST-RESULTS.md - All E2E verification points passed: * Config loading from custom paths * Suppression matching by checkId + skill name * INFO-SUPPRESSED section display * Suppression reason and date display * Summary count accuracy (excludes suppressed findings) * Non-suppressed findings preservation * Skill name display in all findings - All integration tests still passing (10/10) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * auto-claude: subtask-5-1 - Update README.md with suppression feature * auto-claude: subtask-5-2 - Update SKILL.md with usage examples * - Add backslash escaping before quote escaping in oneline() function - Prevents incomplete string escaping vulnerability - Resolves CodeQL alert: https://github.com/prompt-security/clawsec/security/code-scanning/16 * Fix regex in extractSkillName function and simplify error handling in suppression config tests * Enhance suppression mechanism in OpenClaw Audit Watchdog - Updated README.md to clarify suppression configuration and activation requirements. - Improved SKILL.md with examples for suppressing known findings. - Refactored load_suppression_config.mjs to implement opt-in gating for suppressions. - Modified render_report.mjs to support suppression flag in report generation. - Enhanced run_audit_and_format.sh and runner.sh scripts to accept --enable-suppressions flag. - Added test cases for suppression configuration, including validation for enabledFor sentinel and opt-in behavior. - Introduced new test files for empty and invalid suppression configurations. * Fix type assertion for checksums file entries in Checksums component * Update ESLint configuration and dependencies to pin @eslint/js to version 9.28.0 * Update CHANGELOG.md for advisory suppression module and OpenClaw Audit Watchdog enhancements * Refactor finding comparison logic in render_report.mjs to simplify equality checks * chore(clawsec-suite): bump version to 0.1.2 * chore(openclaw-audit-watchdog): bump version to 0.1.0 * Remove suppressed matches tracking from state to prevent re-evaluation alerts --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
124 lines
2.9 KiB
JavaScript
124 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
const JOB_NAME = process.env.CLAWSEC_ADVISORY_CRON_NAME?.trim() || "ClawSec Advisory Scan";
|
|
const JOB_EVERY = process.env.CLAWSEC_ADVISORY_CRON_EVERY?.trim() || "6h";
|
|
const JOB_DESCRIPTION =
|
|
"Trigger a periodic ClawSec advisory scan in the main session and ask for approval before removing flagged skills.";
|
|
const SYSTEM_EVENT =
|
|
"Run ClawSec advisory scan. If installed skills are flagged as malicious or removal is recommended, notify the user and request explicit approval before any removal.";
|
|
|
|
function sh(cmd, args) {
|
|
const result = spawnSync(cmd, args, {
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
if (result.status !== 0) {
|
|
const details = (result.stderr || result.stdout || "").trim();
|
|
throw new Error(`${cmd} ${args.join(" ")} failed${details ? `: ${details}` : ""}`);
|
|
}
|
|
|
|
return result.stdout;
|
|
}
|
|
|
|
function requireOpenClawCli() {
|
|
try {
|
|
sh("openclaw", ["--version"]);
|
|
} catch (error) {
|
|
throw new Error(
|
|
"openclaw CLI is required. Install OpenClaw and ensure `openclaw` is available in PATH. " +
|
|
`Original error: ${String(error)}`,
|
|
{ cause: error },
|
|
);
|
|
}
|
|
}
|
|
|
|
function findExistingJobId(jobsPayload) {
|
|
if (!jobsPayload || !Array.isArray(jobsPayload.jobs)) return null;
|
|
const existing = jobsPayload.jobs.find((job) => job && job.name === JOB_NAME);
|
|
return existing?.id ?? null;
|
|
}
|
|
|
|
function addJob() {
|
|
const out = sh("openclaw", [
|
|
"cron",
|
|
"add",
|
|
"--name",
|
|
JOB_NAME,
|
|
"--description",
|
|
JOB_DESCRIPTION,
|
|
"--every",
|
|
JOB_EVERY,
|
|
"--session",
|
|
"main",
|
|
"--system-event",
|
|
SYSTEM_EVENT,
|
|
"--wake",
|
|
"now",
|
|
"--json",
|
|
]);
|
|
|
|
try {
|
|
const payload = JSON.parse(out);
|
|
return payload?.id ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function editJob(jobId) {
|
|
sh("openclaw", [
|
|
"cron",
|
|
"edit",
|
|
jobId,
|
|
"--name",
|
|
JOB_NAME,
|
|
"--description",
|
|
JOB_DESCRIPTION,
|
|
"--enable",
|
|
"--every",
|
|
JOB_EVERY,
|
|
"--session",
|
|
"main",
|
|
"--system-event",
|
|
SYSTEM_EVENT,
|
|
"--wake",
|
|
"now",
|
|
]);
|
|
}
|
|
|
|
function main() {
|
|
requireOpenClawCli();
|
|
|
|
const jobsOut = sh("openclaw", ["cron", "list", "--json"]);
|
|
const jobsPayload = JSON.parse(jobsOut);
|
|
const existingJobId = findExistingJobId(jobsPayload);
|
|
|
|
if (existingJobId) {
|
|
editJob(existingJobId);
|
|
process.stdout.write(`Updated cron job ${existingJobId}: ${JOB_NAME}\n`);
|
|
} else {
|
|
const createdId = addJob();
|
|
if (createdId) {
|
|
process.stdout.write(`Created cron job ${createdId}: ${JOB_NAME}\n`);
|
|
} else {
|
|
process.stdout.write(`Created cron job: ${JOB_NAME}\n`);
|
|
}
|
|
}
|
|
|
|
process.stdout.write(`Schedule: every ${JOB_EVERY}\n`);
|
|
process.stdout.write("Session target: main (system event + wake now)\n");
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
process.stderr.write(`${String(error)}\n`);
|
|
process.exit(1);
|
|
}
|