Files
sencho/docs/features/cve-suppressions.mdx
T
Anso 2a4955f56d feat: add dedicated Security page and policy-pack foundation (#1362)
* feat: add dedicated Security page and policy-pack foundation

Bring vulnerability scanning, scan history, suppressions, Compose risks,
secrets, policy packs, and scanner setup into one node-scoped Security
command center instead of scattering them across Resources and Settings.

- New top-level Security view with Overview, Images, Compose risks,
  Secrets, Policies, Suppressions, History, and Scanner setup tabs
  (status masthead + signal rail; controlled tabs with deep-link support).
- Backend: GET /security/overview rollup and GET /security/policy-packs
  static catalog (auth-only, Community). DatabaseService gains an uncapped
  scan-status count and a node-eligible block-policy count, and
  getImageScanSummaries now projects secret and misconfig counts.
- Reuse existing surfaces: the scan-history sheet, the control-governed
  suppression and acknowledgement panels, and the scan-detail sheet (now
  with an initial-tab prop so it opens on the matching finding type).
- Extract a shared SeverityBadge (from Resources) and a TrivyManager
  (from Settings) so both surfaces render identical controls.
- Resources "Scan history" now links into the Security page History tab.
- Docs for the new Security surface and tests for the new endpoints,
  helpers, nav wiring, and tabs.

* refactor: consolidate scanner and policy management onto the Security page

Remove the Settings "Vulnerability Scanning" section now that the Security
page covers the same ground, with every option preserved:

- Scanner install / update / uninstall / auto-update live on the Scanner setup
  tab (TrivyManager).
- Scan policies, the honor-suppressions toggle, and the replica
  managed-by-control / demote controls move into a new ScanPolicyManager on the
  Policies tab (paid; Community sees only the policy-pack catalog).
- CVE suppressions and acknowledgements remain on the Suppressions tab.

Wiring removed: the registry section and the now-empty Security settings group,
the SectionId, the SettingsSectionContent case and the isPaid prop it was the
sole consumer of, and SecuritySection itself. The dashboard configuration-status
"Vulnerability scanning" row now navigates to the Security page Policies tab.

Docs that pointed at "Settings -> Security -> Vulnerability Scanning" are swept
to the relevant Security page tabs.

* fix: harden Security page scanner refresh, policy-load errors, and secret-only badges

Address independent-review findings on the Security page:

- Scanner setup now refreshes Trivy state when the active node changes, so the
  displayed scanner status matches the node TrivyManager's actions target (both
  follow x-node-id). Previously, switching nodes on the tab left stale state.
- ScanPolicyManager surfaces an explicit error state on a failed policy fetch
  instead of falling through to a false "No scan policies configured".
- The shared SeverityBadge and the Images findings column no longer label a scan
  "clean" when it has secrets or misconfigurations but no CVE severity
  (highest_severity is derived from vulnerabilities only); they show a "Findings"
  state and the secret/misconfig counts instead.
- The Overview enforcement note points to the Policies tab, not the removed
  Settings section.
- The History tab auto-opens the scan-history sheet only on a deep-link (mount
  with the History tab active), not on every manual tab selection.

Adds tests for the badge secret/misconfig state and the policy-load error state.
2026-06-12 10:41:39 -04:00

125 lines
11 KiB
Plaintext

---
title: "CVE Suppressions"
description: "Accept known-benign CVEs once and have them dimmed across the fleet at read time, without ever modifying stored scan results."
---
Not every CVE that Trivy reports needs a response. Some are false positives on your base image, some have been accepted after security review, and some are waiting on an upstream patch. CVE suppressions let you annotate those findings once so they stop competing for attention in every scan, comparison, and alert.
<Note>
CVE suppressions are available on every tier. The **admin** role is required to add, edit, or remove them. Suppressions created on the control Sencho instance replicate to every remote at fleet scope.
</Note>
## What suppressions do
A suppression is a rule that says "this CVE is acknowledged." When a scan's findings are read back for display, export, or comparison, Sencho checks each finding against the active suppression list:
- Suppressed findings remain in the database and in the stored severity counts. Nothing is deleted.
- In the scan drawer and the comparison sheet, suppressed rows render dimmed and carry a small shield-off icon next to the CVE ID.
- Hovering the package column on a suppressed row surfaces the recorded reason so reviewers can see why it was accepted.
Badge counts in the Resources Hub continue to reflect the raw findings. Suppressions are a visual filter, not an accounting change. Remove a suppression and the underlying finding resurfaces on the next read, without rescanning.
## Creating a suppression
Open the **Security** page → **Suppressions** tab, then click **Add Suppression**.
<Frame>
<img src="/images/cve-suppressions/settings-panel.png" alt="Security page Suppressions tab with the CVE Suppressions panel listing two accepted CVEs, each showing the CVE ID, an outlined package badge, a clamped reason line, the author and expiry metadata, and a trash icon for removal" />
</Frame>
The dialog has the following fields:
| Field | Description |
|-------|-------------|
| **CVE or advisory ID** | Required. Accepts both `CVE-YYYY-NNNN` and `GHSA-xxxx-xxxx-xxxx`. |
| **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. |
| **Reason** | Required. A short note explaining why the CVE is accepted. Surfaced on every suppressed row and on the hover title in scan results. Do not paste credentials, tokens, or vendor secrets, since reasons replicate fleet-wide. |
| **Expires in (days, optional)** | Number of days after which the suppression stops applying. Useful for "patched in the next release" entries. Leave blank for an indefinite suppression. |
<Frame>
<img src="/images/cve-suppressions/create-dialog.png" alt="New suppression dialog with kicker SUPPRESSIONS · NEW, title New suppression, and all five fields populated with example values for a Git CVE scoped to LinuxServer images" />
</Frame>
### 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 add a Reason, an optional Image pattern, and an optional Expiry. 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.
### How specificity is resolved
When more than one suppression matches the same finding, the most specific one wins. Specificity is scored as:
1. Both **Package** and **Image pattern** set: score 3 (most specific).
2. **Package** only: score 2.
3. **Image pattern** only: score 1.
4. Neither (the broadest fleet-wide rule): score 0.
The winning rule's Reason field is the one displayed on the row.
## Viewing suppressed findings
Open any scan drawer and look at the Vulnerabilities table. Suppressed rows are dimmed and carry a shield-off icon next to the CVE ID:
<Frame>
<img src="/images/cve-suppressions/suppressed-row.png" alt="Vulnerabilities tab of a Sencho image scan showing two dimmed suppressed rows for Docker CVEs at the top with the shield-off icon, and one un-suppressed GHSA row below at full brightness with an inline Suppress button on the right" />
</Frame>
The same dim-and-icon treatment carries through to the **Compare** sheet, so a CVE you've already accepted does not look like a new regression when comparing against an older baseline. Hovering the package column of a suppressed row reveals the recorded Reason without expanding the row.
## Fleet-wide replication
Suppressions are managed on the **control** Sencho instance and replicate automatically to every remote you've registered:
- Creating, editing, or removing a suppression on the control pushes the full list to every remote.
- A Sencho instance that has received at least one push from a control is a **replica**. On a replica, the **Security** page → **Suppressions** tab shows the suppression list read-only, and replicated rows carry a small `replicated` badge so they are easy to tell apart from any locally-created entries.
- When you're signed into a control and have a **remote node selected** from the node switcher, the CVE Suppressions panel itself is hidden and a "Scanner is per-node" banner explains that scanning runs on the remote while rules live on the control.
The full replication, retry, and reanchor flow (including the API call to re-bind a replica to a new control) is documented in [Fleet Sync](/features/fleet-sync).
## Suppressions and deploy blocking
By default, suppressions do not affect [block-on-deploy policies](/features/vulnerability-scanning#honoring-suppressions-in-deploy-blocks). A policy evaluates the raw scan result, so a CVE you have suppressed still blocks a deploy that violates the threshold. Suppressions silence the noise; the gate stays strict.
To have an accepted CVE stop counting toward the gate, an admin can enable **Honor suppressions in deploy blocks** on the **Security** page → **Policies** tab. With it on, a block-on-deploy policy re-derives each image's severity from the findings that remain after suppressions are applied, so a deploy whose only blocking findings are all suppressed proceeds without a manual bypass. Sencho records each such suppression-driven pass in the audit log.
The toggle is off by default and is set per Sencho instance, because the gate runs on whichever instance performs the deploy.
## Removing a suppression
Click the trash icon on any row in the panel. A confirmation dialog ("Remove suppression", kicker `SUPPRESSIONS · REMOVE · IRREVERSIBLE`) warns that future scan results will surface the CVE again wherever it applies.
To change a suppression's scope (for example, to narrow an image pattern or extend the expiry), remove the existing rule and create a fresh one with the updated fields.
## Export awareness
Suppressed findings carry through to the [SARIF export](/features/vulnerability-scanning#sarif-export) with a SARIF `suppressions` entry of `kind: external` and `status: accepted`. Code-scanning dashboards that respect SARIF suppressions dismiss those findings with the Reason you recorded.
## Troubleshooting
<AccordionGroup>
<Accordion title="I suppressed a CVE but the count on the badge is unchanged">
Badge counts reflect the raw findings so they remain meaningful for alerting. Suppressions apply when results are read for display (the scan drawer, the Compare sheet, the SARIF export), not when they are stored. Open the scan drawer to confirm the row is dimmed with a shield-off icon.
</Accordion>
<Accordion title="A suppressed CVE still blocks my deploy">
Block-on-deploy policies evaluate the raw scan result by default, so a suppressed CVE still counts toward the block. If you want accepted CVEs to stop counting, enable **Honor suppressions in deploy blocks** on the **Security** page → **Policies** tab on the instance that runs the deploy. See [Suppressions and deploy blocking](#suppressions-and-deploy-blocking) for the full behavior.
</Accordion>
<Accordion title="A suppression I added on the control is not visible on a replica">
Replication runs on every write. If the push failed (network blip, replica restart), the control retries every 5 minutes for 24 hours and the replica picks up the latest state on the next successful push. See [Fleet Sync](/features/fleet-sync) for how to investigate persistent push failures.
</Accordion>
<Accordion title="I see suppressions on a replica but cannot edit them">
Replicas are read-only for security rules. Sign in to the control instance to add, edit, or delete suppressions; changes sync automatically. Replicated rows show a `replicated` badge and no trash icon.
</Accordion>
<Accordion title="A suppression does not match a finding I expect it to">
Two common causes:
- **Image pattern mismatch.** The pattern uses glob syntax (case-sensitive) where `*` matches any sequence. `nginx*` matches `nginx:1.25` but not `docker.io/library/nginx:1.25`; use `*nginx*` for a broader match.
- **Expired rule.** If **Expires in** was set, the suppression stops applying after the deadline. The panel shows an `expired` badge on that row; remove it and create a fresh rule.
</Accordion>
<Accordion title="A replica refuses pushes from my control with 'control identity mismatch'">
Each replica anchors to the first control fingerprint it receives. If a different control later tries to push (for example, after rebuilding the control from a snapshot or migrating it to a new server), the replica rejects the push until it is reanchored. The reanchor procedure (admin API call on the replica) is documented in [Fleet Sync](/features/fleet-sync#control-anchor); the next push from any control after a reanchor becomes the new anchor.
</Accordion>
<Accordion title="My suppression list is large and the fleet sync warns about truncation">
The fleet sync wire protocol caps a single push at 5,000 rows so a misconfigured control cannot wedge a replica with an unbounded payload. If your local list exceeds that cap, the warning appears in the control logs and only the first 5,000 rows are pushed. Trim expired or unused entries from the suppressions panel, or split scopes across distinct rules, so each remains meaningful before relying on fleet replication for the full set.
</Accordion>
</AccordionGroup>