---
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 from Settings

Open **Settings → Security** and scroll to **CVE Suppressions**, then click **Add Suppression**.

<Frame>
  <img src="/images/cve-suppressions/settings-panel.png" alt="Settings Security page 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 **Settings → Security** 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, **Settings → Security** 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).

## 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 and policy evaluation. 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 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>
