From e6cac070da9a3208f6b05e48fe4db74ff12aadc3 Mon Sep 17 00:00:00 2001 From: Ronen Slavin Date: Mon, 18 May 2026 12:08:53 +0300 Subject: [PATCH] fail step when cimon prevent-mode detected a risk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a 'fail-on-detected-risk' input (default true). When set, the post step parses the cimon agent stop output for 'detectedRisks' and fails the step if anything other than 'NoRisk' is reported. This closes a gap where a hardening rule fires and the SIGKILL is issued, but the offending process has already exited before the signal can land. cimon logs the rule trigger and 'Failed to kill violating process (may have already exited)', but the workflow step exits successfully and the job goes green — even though the rule caught the attack. With this change, the workflow surface accurately reflects what cimon prevented in prevent mode, regardless of whether the kill landed in time. Detect-mode behavior is unchanged (the check is skipped when prevent: false). Co-Authored-By: Claude Opus 4.7 (1M context) --- action.yml | 10 ++++++++++ dist/post/index.js | 40 ++++++++++++++++++++++++++++++++++++++++ src/post/index.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/action.yml b/action.yml index b8b9b63..b61468c 100644 --- a/action.yml +++ b/action.yml @@ -97,6 +97,16 @@ inputs: hardening-disabled-rules: description: Comma-separated list of hardening rule IDs to disable required: false + fail-on-detected-risk: + description: | + When true (the default in prevent mode), fail the step if the cimon agent + reported a detected risk on shutdown — including cases where the rule + fired but the SIGKILL did not land in time (short-lived processes). This + ensures the workflow surface reflects what cimon prevented, instead of + relying solely on the offending process exiting non-zero. + Has no effect in detect mode (prevent=false). + required: false + default: 'true' runs: using: node24 diff --git a/dist/post/index.js b/dist/post/index.js index e29987b..a754d98 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -127871,6 +127871,46 @@ async function run(config) { if (retval !== 0) { throw new Error(`Failed stopping Cimon process: ${retval}`); } + + // If the agent reported a detected risk in prevent mode, surface it as + // a step failure. The offending process may have already exited + // non-zero (in which case the workflow is already red), but a + // sufficiently short-lived process can complete before the kill lands, + // leaving the workflow green even though the rule fired. This check + // closes that gap so the workflow always reflects what cimon prevented. + const failOnDetectedRisk = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('fail-on-detected-risk'); + const preventMode = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('prevent'); + if (failOnDetectedRisk && preventMode) { + const risk = parseDetectedRisks(stopOutput); + if (risk && risk !== 'NoRisk') { + throw new Error( + `Cimon detected a risk in prevent mode (${risk}). See the ` + + `cimon log output and the job summary for the specific rule ` + + `and evidence. Set fail-on-detected-risk: false to disable ` + + `this check.` + ); + } + } +} + +/** + * Extract the latest `detectedRisks` value from the cimon agent's + * structured-JSON stop output. Returns null if not present. + * + * The agent emits one log line near shutdown shaped like: + * {"level":"info",...,"detectedRisks":"PreventedRisk","message":"Terminating execution"} + * + * In rare cases multiple such lines appear (e.g. multiple executions); + * we take the last one so the most-recent state wins. + */ +function parseDetectedRisks(text) { + const re = /"detectedRisks"\s*:\s*"([^"]+)"/g; + let match; + let last = null; + while ((match = re.exec(text)) !== null) { + last = match[1]; + } + return last; } /** diff --git a/src/post/index.js b/src/post/index.js index 0fb0d38..6a5ffec 100644 --- a/src/post/index.js +++ b/src/post/index.js @@ -140,6 +140,46 @@ async function run(config) { if (retval !== 0) { throw new Error(`Failed stopping Cimon process: ${retval}`); } + + // If the agent reported a detected risk in prevent mode, surface it as + // a step failure. The offending process may have already exited + // non-zero (in which case the workflow is already red), but a + // sufficiently short-lived process can complete before the kill lands, + // leaving the workflow green even though the rule fired. This check + // closes that gap so the workflow always reflects what cimon prevented. + const failOnDetectedRisk = core.getBooleanInput('fail-on-detected-risk'); + const preventMode = core.getBooleanInput('prevent'); + if (failOnDetectedRisk && preventMode) { + const risk = parseDetectedRisks(stopOutput); + if (risk && risk !== 'NoRisk') { + throw new Error( + `Cimon detected a risk in prevent mode (${risk}). See the ` + + `cimon log output and the job summary for the specific rule ` + + `and evidence. Set fail-on-detected-risk: false to disable ` + + `this check.` + ); + } + } +} + +/** + * Extract the latest `detectedRisks` value from the cimon agent's + * structured-JSON stop output. Returns null if not present. + * + * The agent emits one log line near shutdown shaped like: + * {"level":"info",...,"detectedRisks":"PreventedRisk","message":"Terminating execution"} + * + * In rare cases multiple such lines appear (e.g. multiple executions); + * we take the last one so the most-recent state wins. + */ +function parseDetectedRisks(text) { + const re = /"detectedRisks"\s*:\s*"([^"]+)"/g; + let match; + let last = null; + while ((match = re.exec(text)) !== null) { + last = match[1]; + } + return last; } /**