Files
clawsec/skills/clawsec-suite/scripts/setup_advisory_cron.mjs
T
davida-ps 63de5ce08d Security Audit Suppression Mechanism (fulfills https://github.com/prompt-security/clawsec/issues/25) (#40)
* 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>
2026-02-16 18:55:06 +02:00

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);
}