Requires 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 its suppression 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

This needs only 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 resolved finding 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 resolved yourself. Resolution means a scan that would have found it did not. Suppress what you decided not to fix; rescan what you fixed.
  • reopened cannot be downgraded to open. It records a regression, which is the most interesting fact a vulnerability programme produces.
  • 409 means 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