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 -7
View File
@@ -9,7 +9,7 @@ import { LicenseService } from '../services/LicenseService';
import { effectiveTier } from '../middleware/tierGates';
import { getErrorMessage } from '../utils/errors';
import { sanitizeForLog } from '../utils/safeLog';
import { describeReason } from '../utils/policy-risk';
import { summarizeBlockReasons } from '../utils/policy-risk';
type BlockableAction = 'deploy' | 'update' | 'rollback';
@@ -24,12 +24,7 @@ export function describePolicyBlock(
violations: PolicyViolation[],
action: BlockableAction = 'deploy',
): string {
const reasons = new Set<string>();
for (const v of violations) {
for (const r of v.reasons) reasons.add(describeReason(r));
}
const reasonText = reasons.size > 0 ? [...reasons].join(' + ') : 'scan policy conditions';
return `Policy "${policy?.name ?? 'policy'}" blocked ${action}: ${violations.length} image(s) matched ${reasonText}`;
return `Policy "${policy?.name ?? 'policy'}" blocked ${action}: ${violations.length} image(s) matched ${summarizeBlockReasons(violations)}`;
}
// Bypass requires `?ignorePolicy=true` AND `req.user.role === 'admin'`. The