fix: name matched risk inputs in policy block messages (#1471)

The auto-update, bulk-label, scheduler, and blueprint deploy block
messages hardcoded "image(s) exceed <max_severity>", which is wrong
under the risk-first policy model: a block can be driven by a
known-exploited (KEV) or fixable Critical/High input while the severity
threshold was never the trigger. In those cases the message named a
severity ceiling the policy did not enforce.

Route all four message paths through a shared summarizeBlockReasons
helper (the same reason text the deploy-gate 409 response and the block
dialog already use), so every surface names the inputs that actually
matched. Falls back to a generic phrase when no reason was recorded.
This commit is contained in:
Anso
2026-06-26 15:34:24 -04:00
committed by GitHub
parent 5e2194f4a3
commit ca496c89dc
10 changed files with 104 additions and 17 deletions
+2 -1
View File
@@ -15,6 +15,7 @@ import { HealthGateService } from '../services/HealthGateService';
import { authMiddleware } from '../middleware/auth';
import { requireAdmin } from '../middleware/tierGates';
import { buildPolicyGateOptions } from '../helpers/policyGate';
import { summarizeBlockReasons } from '../utils/policy-risk';
import { isValidStackName } from '../utils/validation';
import { sanitizeForLog } from '../utils/safeLog';
import { getErrorMessage } from '../utils/errors';
@@ -365,7 +366,7 @@ autoUpdateRouter.post('/execute', authMiddleware, async (req: Request, res: Resp
);
if (!autoUpdateGate.ok) {
const blockedImages = autoUpdateGate.violations.map((v) => v.imageRef).join(', ');
const blockedMsg = `Policy "${autoUpdateGate.policy?.name}" blocked auto-update: ${autoUpdateGate.violations.length} image(s) exceed ${autoUpdateGate.policy?.max_severity}${blockedImages ? ` (${blockedImages})` : ''}`;
const blockedMsg = `Policy "${autoUpdateGate.policy?.name}" blocked auto-update: ${autoUpdateGate.violations.length} image(s) matched ${summarizeBlockReasons(autoUpdateGate.violations)}${blockedImages ? ` (${blockedImages})` : ''}`;
NotificationService.getInstance().dispatchAlert('warning', 'scan_finding', blockedMsg, { stackName, actor: 'system:image-update' });
results.push(`Stack "${stackName}": ${blockedMsg}`);
continue;
+2 -2
View File
@@ -8,7 +8,7 @@ import { enforcePolicyPreDeploy } from '../services/PolicyEnforcement';
import { authMiddleware } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { requireAdmin, requireBody } from '../middleware/tierGates';
import { buildPolicyGateOptions } from '../helpers/policyGate';
import { buildPolicyGateOptions, describePolicyBlock } from '../helpers/policyGate';
import { invalidateNodeCaches } from '../helpers/cacheInvalidation';
import { VALID_LABEL_COLORS, MAX_LABELS_PER_NODE } from '../helpers/constants';
import { isValidStackName } from '../utils/validation';
@@ -227,7 +227,7 @@ labelsRouter.post('/:id/action', authMiddleware, async (req: Request, res: Respo
buildPolicyGateOptions(req),
);
if (!gate.ok) {
const blockedMsg = `Policy "${gate.policy?.name}" blocked deploy: ${gate.violations.length} image(s) exceed ${gate.policy?.max_severity}`;
const blockedMsg = describePolicyBlock(gate.policy, gate.violations);
results.push({ stackName, success: false, error: blockedMsg, ...(isDryRun ? { dryRun: true } : {}) });
continue;
}