fix(security): tie fixable CVE posture to image-update evidence (#1815)

* fix(security): tie fixable CVE posture to image-update evidence

Stop treating Trivy fixed_version alone as an Update affected images CTA. Reuse persisted ImageUpdateService status so Security only offers Review update when an applicable image update is confirmed, and otherwise surfaces waiting or uncertain remediation with truthful affordances.

* fix(security): move image-update recheck helper out of OverviewTab

Satisfy react-refresh/only-export-components so Frontend lint passes.

* fix(security): preserve posture reason image targets in Images drill-down

Carry affected image refs on overview reasons so public exposure and related CTAs open a clearable targeted Images list instead of an unfiltered hunt.

* fix(security): attach Networking exposure intent to posture targets

Preserve stack/service context and intentional classification on network-exposed Security reasons without suppressing risk or claiming Internet reachability.

* fix(security): persist Images exposure intent and triage scope

Standing image summaries carry Networking intent context with cap-safe aggregates, Anatomy Networking links, and scan-sheet triage that defaults to the current image.

* fix(security): clear CI lint errors for exposure helpers

* fix(security): stop intentional exposure from forcing Action needed

Separate exposure fact, intent correctness, and vulnerability drivers so
package fixed_version cannot recreate a permanent public_exposure blocker.

* fix(security): define Monitoring residual-risk narrative

* fix(security): define Secure via residual Crit/High triage

Replace triage-blind raw Crit/High Secure gating with residual material
risk so accepted and ignored stay Monitoring, while not_affected, false
positive, and fixed can clear residual without claiming no detections.

* fix(security): exclude rollback-hold images from Security scans

Hold-only sencho-rb tags are recovery state; keep them out of Trivy node scans, Security inventory, and Overview posture while dual-tagged images remain under their registry tag.

* fix(security): keep authoritative no-update rows after preview

Opening a stack page must not delete ok+false stack_update_status evidence; Security treats a missing row as uncertain and would flip waiting-upstream to unknown.
This commit is contained in:
Anso
2026-08-12 15:02:14 -04:00
committed by GitHub
parent c47b8eb8e9
commit fcd44f5693
49 changed files with 5596 additions and 308 deletions
+9 -2
View File
@@ -32,7 +32,7 @@ The dialog has the following fields:
| Field | Description |
|-------|-------------|
| **CVE or advisory ID** | Required. Accepts both `CVE-YYYY-NNNN` and `GHSA-xxxx-xxxx-xxxx`. |
| **Triage decision** | How the finding was triaged: accepted risk (default), not affected, false positive, affected, fixed, ignored, or needs review. A decided state (accepted, not affected, false positive, fixed, ignored) stops the finding from driving the action posture; "needs review" and "affected" keep it counted but still actionable. |
| **Triage decision** | How the finding was triaged: accepted risk (default), not affected, false positive, affected, fixed, ignored, or needs review. Not affected, false positive, and fixed clear residual Crit/High so Secure can become reachable. Accepted and ignored residual risk stay on Monitoring and do not turn Secure green. Needs review and affected stay actionable. |
| **OpenVEX justification** | Required when the triage decision is not affected or false positive. Explains why the vulnerable code is not exploitable, for example vulnerable code not present, vulnerable code not in the execute path, component not present, or inline mitigations already exist. Carried into the OpenVEX export for that decision. |
| **Package (optional)** | Leave blank to suppress every occurrence of this CVE across every package, or pin a specific package name (e.g. `openssl`) to narrow the scope. |
| **Image pattern (optional)** | Glob applied to image references (`*` matches any sequence, case-sensitive). For example, `lscr.io/linuxserver/*` matches every LinuxServer image, and `*alpine*` matches anything containing `alpine`. Leave blank to apply fleet-wide. |
@@ -45,7 +45,14 @@ The dialog has the following fields:
### Suppressing directly from a scan result
The panel's empty state hints at the faster path: from any vulnerability scan, click the small shield icon at the right edge of a finding's row. The dialog opens pre-filled with the CVE ID and the package name from that row (both read-only in this flow), leaving you to set the Triage decision (accepted risk by default), add a Reason, an optional Image pattern, and an optional Expiry. Choosing not affected or false positive also requires an OpenVEX justification. This is the recommended workflow for everyday triage, because it keeps the scope as narrow as the originating finding. To broaden the scope (for example, to suppress across every package), create the rule from the **Security** page → **Suppressions** tab instead.
The panel's empty state hints at the faster path: from any vulnerability scan, click **Triage finding**
on a finding's row. The dialog opens pre-filled with the CVE ID, the package name, and the **Image
pattern** set to the current image reference so the decision stays scoped to that image by default.
You set the Triage decision (accepted risk by default), add a Reason, and an optional Expiry. You can
clear or broaden the image pattern if you intend a wider match. Choosing not affected or false
positive also requires an OpenVEX justification. This is the recommended workflow for everyday
triage. To start from a blank image pattern for fleet-wide matching, create the rule from the
**Security** page → **Suppressions** tab instead.
### How specificity is resolved
+1 -1
View File
@@ -146,7 +146,7 @@ When a deploy or update fails, Sencho classifies the failure from the compose ou
The sidebar's per-stack **Update** action runs the same path as the editor toolbar, so it shows the same readiness dialog and deploy progress. One click on **Update now** proceeds. On nodes that do not advertise the capability, updates run directly without the dialog.
</Accordion>
<Accordion title="Why do I see sencho-rb/... images in docker images on the host">
Those are automatic rollback images: an opaque copy of a service's prior image, held so Sencho can restore it if a full-stack update fails. They are not leftovers. Sencho keeps them out of **Resources → Images** on purpose (they are recovery state, not image inventory) and lists them in **Resources → Rollback** instead, showing which stack and generation each one belongs to and how soon it clears on its own. If one still carries a normal registry tag too, it also stays visible in the Images tab with a **Rollback protected** badge.
Those are automatic rollback images: an opaque copy of a service's prior image, held so Sencho can restore it if a full-stack update fails. They are not leftovers. Sencho keeps them out of **Resources → Images** and **Security** on purpose (they are recovery state, not image inventory or scan targets) and lists them in **Resources → Rollback** instead, showing which stack and generation each one belongs to and how soon it clears on its own. If one still carries a normal registry tag too, it also stays visible in the Images tab with a **Rollback protected** badge, and Security continues to scan that registry tag.
</Accordion>
<Accordion title="Deleting a rollback-protected image fails">
That failure is intentional: the image is protected by an active or recently superseded rollback generation. Open **Resources → Rollback**, find the matching generation, and use **Release** there if you are sure you do not need it. Releasing the current generation means Sencho cannot automatically roll that stack back until its next successful full-stack update.
+62 -21
View File
@@ -9,7 +9,7 @@ center, so you can answer "what should I look at first?" without hunting through
scoped to the active node: the findings and scanner status you see reflect whichever node is selected.
<Frame>
<img src="/images/security/overview.png" alt="Security page Overview tab with an Action needed masthead reading '3 actions: fixable findings, detected secrets' and CRITICAL/HIGH/LAST SCAN stat tiles, a Why Action needed review-queue card, a 30-day risk trend chart, an Action posture breakdown, and a Scan this node button." />
<img src="/images/security/overview.png" alt="Security page Overview tab with an Action needed masthead, CRITICAL/HIGH/LAST SCAN stat tiles, a Why Action needed review-queue card, a 30-day risk trend chart, an Action posture breakdown, and a Scan this node button." />
</Frame>
The page is organized into tabs: Overview, Images, Compose risks, Secrets, Policies, Suppressions,
@@ -20,17 +20,35 @@ History, and Scanner setup.
The overview opens with a status masthead that reads your **action posture** at a glance, the answer
to "what can and should I do right now?":
- **Action needed**: something concrete to act on, such as a fixable Critical or High finding, a
detected secret, a dangerous Compose setting, or a known-exploited (CISA KEV) CVE.
- **Monitoring**: Critical or High findings exist, but none are currently actionable (no fix
available, or already triaged).
- **Secure**: nothing actionable right now. This is never a claim that no vulnerabilities exist.
- **Action needed**: something concrete to act on that Sencho can support right now, such as a
newer image available to review for Critical or High findings, a detected secret, a dangerous
Compose setting, a known-exploited (CISA KEV) CVE, elevated exploit risk on a network-exposed
workload, or exposure that conflicts with declared Networking intent.
- **Monitoring**: no Action needed blocker, but material residual risk, a pending review, or
relevant uncertainty remains. Residual Critical/High include undecided findings and accepted or
ignored residual risk. Package fixes may exist without an applicable container-image update,
workloads may be intentionally network-exposed without an independent Security driver, and
waiting-upstream or update-check-uncertain rows appear under **Why Monitoring** without a red
masthead.
- **Secure**: no Action needed blocker and no residual material Critical/High or pending
security-review condition under current evidence. Not affected, false positive, and fixed can
clear residual risk for those findings; accepting residual risk does not. This is never a claim
that no scanner detections exist.
- **Unknown**: the scanner is not installed, or no scan has completed yet.
Monitoring keeps residual risk visible (raw detections, accepted risk, waiting-upstream rows,
intentional exposure context). Secure is stricter: residual Critical/High and review reasons must
be cleared, not merely the absence of an immediate action.
Raw Critical and High counts stay visible next to the posture as **scanner detections**, not as the
posture itself: a vulnerable component being present is not the same as a reachable, exploitable risk.
The masthead carries a standing note to that effect, and posture weighs fix availability, exploit
intelligence, and triage decisions rather than raw severity alone.
The masthead carries a standing note to that effect, and posture weighs package-fix evidence,
image-update availability, exploit intelligence, exposure, and triage decisions rather than raw
severity alone. A Trivy package fix does not by itself mean Sencho can update the image; when a
newer image is confirmed available the review queue offers **Review update**, and when no applicable
update is identified it surfaces **Waiting for upstream image** instead of an impossible update
instruction. Deploy policies remain orthogonal: they may still block admission on package-fix
Critical/High even when Security posture is Monitoring.
An admin on a node with a ready scanner sees a **Scan this node** button above the review queue. It
opens a popover to pick any combination of image vulnerabilities, image secrets, and Compose
@@ -40,11 +58,18 @@ clean.
Below the masthead, a **Review queue** card leads the overview when actions or review items exist.
When the posture is Action needed the card is titled **Why Action needed** and lists each concrete
action with a count and a tab-shortcut button: fixable findings, known-exploited CVEs, detected
secrets, unacknowledged Compose risks, and publicly exposed affected images. When only monitoring
items remain (exposed images with no fix or known exploit, findings awaiting triage, stale or failed
scans) the card is titled **Review queue** and lists them without the red masthead, so the operator
can see what to keep an eye on without a permanent alarm.
action with a count and a tab-shortcut button: newer images available to review, known-exploited
CVEs, elevated exploit risk on network-exposed workloads, detected secrets, unacknowledged Compose
risks, and exposure that conflicts with declared Networking intent. Intentional network exposure is
Security context, not an independent Action needed reason by itself. When only monitoring items
remain (waiting for an upstream image, update availability unknown, network-exposed images not yet
classified, findings awaiting triage, stale or failed scans) the card is titled **Why Monitoring**
and lists them with View findings shortcuts (and Check again when image update availability could
not be established and you can manage the node), so the operator can see what to keep an eye on
without a permanent alarm. Shortcuts that carry affected image identities open Images already
narrowed to those images, with a clearable banner so you can return to the full list. When the
reason includes exact driving findings, opening an image filters the scan report to that contributing
set.
The charts then lead with prioritization rather than raw severity: a **risk trend** for context,
an **action posture** breakdown (fixable, known-exploited, needs-review, accepted, not-affected), a
@@ -64,11 +89,26 @@ clear "overview unavailable" state and the other tabs keep working.
<img src="/images/security/images-tab.png" alt="Security page Images tab listing scanned images with Image, Findings, Last scan, Severity, and Actions columns; findings show critical/high counts and fixable totals, and one row shows a Clean badge." />
</Frame>
Image findings list every scanned image on the active node with its highest severity. Selecting an
image opens the full scan report, where you can review vulnerabilities, triage a CVE, compare against
another scan, and export a CSV, a SARIF file, or an SBOM. Each finding carries evidence tags so you can
tell scary from exploitable at a glance: known-exploited (KEV), EPSS exploitation probability, the
CVSS score, and vendor "will not fix" status, alongside whether a fix is available.
Image findings list every scanned image on the active node with its highest severity. Sencho
rollback-hold tags (`sencho-rb/...:hold`) are recovery state, not Security inventory: node-wide
scans skip them, and existing hold-only scan rows stay out of this list and the Overview posture.
When you arrive
from a Security posture action that named specific images, the list opens already filtered to those
images and shows a clearable banner with the reason and count. Images configured beyond loopback (or
with host networking) carry a **Network exposed** evidence label on the row during ordinary browsing,
not only after a posture drill-down. That label means Compose declares non-loopback reachability; it
is not a claim that the service is reachable from the Internet. When Networking exposure intent is
available, the row also shows compact intent evidence (for example Intent: public, Intent mismatch,
or Intent: not classified). Open Networking from that evidence to review or correct classification
for the matching stack. Intentional exposure raises the security relevance of vulnerable findings
but does not by itself keep the masthead on Action needed. Open the image, remediate, or triage
individual findings (Accepted risk and related decisions). There is no image-level "accept residual
risk" control. Compose Doctor acknowledgements are a separate triage surface and do not clear
Security exposure context. Selecting
an image opens the full scan report, where you can review vulnerabilities, triage a finding, compare
against another scan, and export a CSV, a SARIF file, or an SBOM. Each finding carries evidence tags
so you can tell scary from exploitable at a glance: known-exploited (KEV), EPSS exploitation
probability, the CVSS score, and vendor "will not fix" status, alongside whether a fix is available.
<Frame>
<img src="/images/security/scan-report.png" alt="Vulnerability scan report sheet for nginx:latest showing 340 vulnerabilities, a Critical/High/Medium/Low summary, Compare/CSV/SARIF/SBOM export buttons, and a findings table with CVE, package, severity, installed version, and per-row EPSS and CVSS evidence tags." />
@@ -99,7 +139,7 @@ the secret findings for a scan.
## Policies
<Frame>
<img src="/images/security/add-policy.png" alt="New policy dialog with a Name field, an optional glob-style Stack pattern field, Block conditions toggles for Severity threshold, Known-exploited (KEV), and Fixable Critical/High, a Block on deploy toggle, and an Enabled toggle." />
<img src="/images/security/add-policy.png" alt="New policy dialog with a Name field, an optional glob-style Stack pattern field, Block conditions toggles for Severity threshold, Known-exploited (KEV), and Package fix available (Critical/High), a Block on deploy toggle, and an Enabled toggle." />
</Frame>
The Policies tab manages deploy-enforcement scan policies: a policy names one or more block
@@ -121,8 +161,9 @@ in Settings. These are governed by the local instance, so this tab is shown when
node; switch to the local node to manage them.
Suppressing a CVE records a **triage decision**: accepted risk, not affected, false positive, fixed,
ignored, or needs review, with an optional OpenVEX justification. Decided findings stop driving the
action posture; a "needs review" decision stays counted but keeps the finding actionable. You can
ignored, or needs review, with an optional OpenVEX justification. Not affected, false positive, and
fixed clear residual Crit/High for Secure. Accepted and ignored residual risk stay on Monitoring and
do not turn the page green. Needs review and affected stay actionable. You can
export the fleet's triage decisions as an OpenVEX document for use with other tooling.
## History