feat(security): gate deploys on exploitation risk, not just severity (#1432)

Scan-policy deploy gates can now block on a known-exploited CVE (CISA KEV)
and on a fixable Critical/High finding, in addition to an optional severity
threshold. New policies default risk-first (KEV and fixable on, severity off);
existing policies keep their severity-only behavior. CVSS stays captured for
context but is never the sole basis for a block, and a finding whose
exploitability cannot be confirmed is treated as risky rather than safe
(incomplete scan detail fails closed on KEV/fixable inputs).

The decision logic is shared between the pre-deploy gate and the informational
post-scan banner via a pure helper, so the two never disagree. Block messages
and the block dialog now name the conditions an image matched. Backend and
frontend gates move together, the new inputs replicate across the fleet, and a
blocking policy with no active input is rejected on both sides.
This commit is contained in:
Anso
2026-06-24 20:05:17 -04:00
committed by GitHub
parent bb4ddde35a
commit 6527bc971b
31 changed files with 1259 additions and 127 deletions
+12 -3
View File
@@ -9,7 +9,7 @@ All endpoints require [Bearer token authentication](/api-reference/overview#auth
## Scan policies
Scan policies define severity thresholds that Sencho evaluates on every post-deploy and scheduled scan. When a policy has `block_on_deploy=1`, a matching deploy is rejected at the pre-flight stage with an HTTP 409 response. See [Deploy Enforcement](/features/deploy-enforcement) for the full flow.
Scan policies define risk conditions that Sencho evaluates on every post-deploy and scheduled scan: a known-exploited CVE (CISA KEV), a fixable Critical/High finding, and an optional severity threshold. When a policy has `block_on_deploy=1`, a deploy whose images match any enabled condition is rejected at the pre-flight stage with an HTTP 409 response. See [Deploy Enforcement](/features/deploy-enforcement) for the full flow.
Writes are admin-only and rejected on replica nodes (policies are managed on the control instance and replicate fleet-wide).
@@ -37,6 +37,9 @@ curl -H "Authorization: Bearer YOUR_API_TOKEN" \
"max_severity": "HIGH",
"block_on_deploy": 1,
"enabled": 1,
"block_on_severity": 1,
"block_on_kev": 0,
"block_on_fixable": 0,
"replicated_from_control": 0,
"created_at": 1745107200000,
"updated_at": 1745107200000
@@ -54,11 +57,16 @@ curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|-------|------|:--------:|-------------|
| `name` | string | yes | Human-readable name shown in the UI. |
| `stack_pattern` | string or `null` | no | Glob against stack names (e.g. `prod-*`). `null` matches every stack. |
| `max_severity` | string | yes | One of `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`. A scan whose highest finding meets or exceeds this severity triggers the policy. |
| `block_on_deploy` | `0` or `1` | yes | When `1`, pre-flight violations reject deploys with HTTP 409. When `0`, post-deploy and scheduled scans dispatch warning alerts instead. |
| `max_severity` | string | yes | One of `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`. The threshold used when `block_on_severity` is on. Always stored for context. |
| `block_on_deploy` | `0` or `1` | yes | When `1`, pre-flight matches reject deploys with HTTP 409. When `0`, post-deploy and scheduled scans dispatch warning alerts instead. |
| `enabled` | `0` or `1` | no | Defaults to `1`. Disabled policies are never evaluated. |
| `block_on_severity` | `0` or `1` | no | Block when an image's highest non-suppressed finding meets or exceeds `max_severity`. Defaults to `0`. |
| `block_on_kev` | `0` or `1` | no | Block when an image carries a CVE on the CISA known-exploited (KEV) list. Defaults to `1`. |
| `block_on_fixable` | `0` or `1` | no | Block when an image has a Critical/High finding with a fix available. Defaults to `1`. |
| `node_id` | number or `null` | no | Scope the policy to one node. `null` applies the policy fleet-wide. |
A blocking policy (`block_on_deploy=1`) must enable at least one of `block_on_severity`, `block_on_kev`, or `block_on_fixable`. When the three input fields are omitted, they default risk-first (`block_on_kev=1`, `block_on_fixable=1`, `block_on_severity=0`).
```bash
curl -X POST https://your-sencho-instance:1852/api/security/policies \
-H "Authorization: Bearer YOUR_API_TOKEN" \
@@ -78,6 +86,7 @@ curl -X POST https://your-sencho-instance:1852/api/security/policies \
- `400` `max_severity must be CRITICAL, HIGH, MEDIUM, or LOW`
- `400` `Policy name is required`
- `400` `A blocking policy must enable at least one of: severity threshold, KEV, or fixable.`
- `403` when called on a replica node
### Update policy