findings:suppress, which is admin-level for a human and deliberately
separate from findings:write.
The request
reason is mandatory and capped at 1000 characters. It is the entire audit
value of the action — an acceptance nobody wrote down is indistinguishable, six
months later, from somebody clicking a button to make a number go down.
expires_at is optional. Omitted means permanent; supplied, it is bounded at
two years. Without a ceiling, “expires 31 December 9999” is permanent
suppression wearing a costume: it satisfies every policy that requires an
expiry while guaranteeing nobody revisits the decision.
Which one to use
The distinction is surfaced rather than enforced, because “this detection is
wrong” is a legitimate permanent statement, and refusing it would push people
into entering a fake far-future date instead.
Why the scope is separate from findings:write
findings:write covers triage, un-triage and unsuppress — actions that move a
finding around a queue everyone can still see, or make it more visible.
Suppression makes a real, confirmed vulnerability stop appearing in the
backlog, the severity counts, and eventually the gate that blocks a release. It
is the one action in TRUSTIVAN whose misuse is invisible by design.
A CI runner that can queue scans has no business being able to silence its own
failures, so grant scans:create and findings:write to pipelines and keep
findings:suppress for people.
What suppression does to a verdict
Not, by default, “pass”. The policy version decides, via itssuppression
mode:
The default is
warn because the alternative sells a button that turns off
security: if suppression automatically meant a green build, the fastest route
to shipping would be to accept the risk. The downgrade also requires that
every matching finding be suppressed — one unsuppressed critical alongside
ten suppressed ones is still a fail.
Lifting it
findings:write. The asymmetry is deliberate: anyone working
the backlog should be able to put something back onto it.
Unsuppressing and a suppression expiring are different facts, and only one
of them means a human has looked. An expiry is swept by a background task,
recorded as suppression_expired rather than unsuppressed, and carries a
copy of the lapsed reason — because the finding no longer holds it, and without
that copy the trail would record that an acceptance ended without recording
what was accepted. The sweep runs on its own timer rather than on a scan: an
accepted risk must return when the acceptance ends whether or not anything has
been scanned since.
Gotchas
- A
resolvedfinding cannot be suppressed. There is nothing to suppress; it is already closed. Pre-emptively accepting a risk that may return is a statement about an issue class, not about this row. - You cannot set
resolvedyourself. Resolution means a scan that would have found it did not. Suppress what you decided not to fix; rescan what you fixed. reopenedcannot be downgraded toopen. It records a regression, which is the most interesting fact a vulnerability programme produces.409means the finding moved underneath you — a scan reopened it, or another session changed it. Re-read and decide again; do not retry blindly.- Requesting the state it is already in is a no-op, writes nothing, and records no event.
- One suppression is in force at a time. Suppressing an already-suppressed finding with a new reason does not stack.
See also
- Suppression — the full model, including what the timeline records.
- The finding lifecycle — the five states and every legal transition.
- Triage in bulk — the same action across many findings.
- Scopes — what each scope permits.

