Suppressing a finding
The suppression also records who applied it and when. An acceptance nobody owns
is not an acceptance.
The reason is mandatory
It is not paperwork. It is the entire audit value of the action, and it is what the person reviewing this in a year has instead of the person who made the decision. A suppression with no reason is refused with a400 naming the missing reason
specifically, rather than a bare “invalid transition” that gives a user nothing
to act on.
Expiry is optional, and bounded at both ends
An explicit expiry must be at least one minute away and at most two years away. The lower bound exists because below it the suppression expires before the user has finished reading the confirmation, which reads as the feature being broken rather than as the input being silly. The upper bound is the interesting one. Without a ceiling, “expires 31 December 9999” is permanent suppression wearing a costume: it satisfies every policy that requires an expiry while guaranteeing that nobody ever revisits the decision. Two years is long enough for a genuine multi-year remediation plan and short enough that the decision outlives neither the person who made it nor the context they made it in.Permanent suppression is allowed, and usually wrong
The distinction is surfaced in the product rather than enforced, because “this
detection is wrong” is a legitimate permanent statement. Refusing it would
push users into entering a fake far-future date instead, which is worse: it
produces a record that looks like a review date and is not one.
Amending a suppression
suppressed → suppressed is a legal transition. Extending an expiry, shortening
it, or restating the justification is a decision, and it writes an event like
any other.
A request whose terms are byte-for-byte identical to the current suppression is
a no-op: it writes nothing, including no event. A retried request must not
produce a second suppression event for one decision.
Expiry is a sweep, not a query-time rule
The tempting alternative is to treatstatus = 'suppressed' AND expires_at < NOW() as open wherever findings are read. It reads well and is wrong in a way
that surfaces late: the status column would then disagree with what the product
shows, every future query would have to remember the rule, and the finding
would silently rejoin the backlog with no event marking when.
A state that changes has to change in the table, once, with a record.
So a worker sweeps on its own timer, moving due suppressions back to open.
The sweep is bounded and skip-locked, so several workers can run it
concurrently and a tenant who suppressed a hundred thousand findings on one day
cannot produce a single enormous transaction.
It runs on the worker’s own schedule rather than on a scan, because an accepted
risk must come back when the acceptance ends whether or not anything has been
scanned since — and a tenant who suppressed a finding and then stopped scanning
is exactly the case where leaving it suppressed is worst.
An expiry is not a change of mind
Two different facts, and only one of them means a human has looked:
They are recorded as distinct actions in the
timeline for that reason. Only the second means the
finding is back in the backlog with no human judgement behind it, and a queue of
those is a signal a team needs to see.
The expiry event carries no actor at all. Nobody performed it, and a column
asserting otherwise would be a small permanent lie. The lapsed reason is copied
onto the event, because the finding no longer carries it — without that, the
trail would record that an acceptance ended without recording what had been
accepted.
It returns to open, not to what it was before
The status a finding held before it was suppressed is not stored, and inventing
a column to hold it would encode a claim the history already makes better: the
timeline shows exactly what it was and when.
open also has the right effect. An expired acceptance belongs back in the
triage queue, not filed as already assessed.
Suppression does not buy a green build
By default, suppressing a finding does not make a policy pass. If it did, the fastest route to a green build would be to accept the risk, and TRUSTIVAN would be selling a button that turns off security. A policy’s defaultwarn mode still evaluates suppressed findings: a failing
rule that fires only because of suppressed findings is downgraded to a
warning, so the build is not blocked by a risk somebody already accepted — but
the acceptance stays visible in every verdict that touches it. The downgrade
requires that every matching finding be suppressed; one unsuppressed
critical alongside ten suppressed ones is still a failure.
A team that wants suppression to mean silence has to say so explicitly, in a
policy version with an author and a timestamp. See
Policies and verdicts.
Who may suppress
The asymmetry is the point.
For machine credentials the equivalent scope is
findings:suppress, kept
separate from findings:write for the same reason. Folding it in would hand
every CI key the ability to silence its own failures.
Where to go next
- The finding lifecycle — the states
suppression moves between, and why
resolvedcannot be suppressed. - Policies and verdicts — the three suppression modes a policy can choose.
- Tenancy and isolation — roles and API key scopes in full.

