Risk acceptance: the exception nobody can explain later
In plain terms#
Every security program accepts risk. Not as a failure, as the only way to ship anything. A finding you cannot fix this sprint, a vulnerability in a dependency with no patch, a rule that is wrong about your code: each one ends in somebody deciding not now or not here.
Risk acceptance is that decision, made deliberately and written down. An exception is the record of it.
The distinction that matters is not between accepting risk and not accepting it, but between accepting it on the record and accepting it by making the finding go away.
Why it matters#
An auditor, an acquirer's counsel or your own successor will eventually look at a scan that passed and ask a question you must be able to answer:
This finding was reported in March and is not in the June report. What happened to it?
There are three possible answers, and only one of them is comfortable.
- It was fixed. Fine. The evidence is the diff.
- Somebody accepted it, here is who and why. Also fine. That is a functioning process.
- We do not know. That is the answer that costs you, and it is what happens by default.
The third answer is the normal outcome of most tooling, because most tooling offers you an ignore-list and no memory.
Suppress, don't delete#
The two look identical in a passing report and are opposites in an audit.
Deleting a finding, an ignore file, a baseline, a rule switched off, removes it from the output. What reaches the reader is a report with nothing in it, which is indistinguishable from a scan that found nothing, which is indistinguishable from a scan that did not run. Three very different situations, one output.
Suppressing it keeps the finding in the report, marked, carrying the reason somebody gave. The report says: this was found, this is who decided it was acceptable, this is why. It still passes the gate. It just does not pretend.
The cost of suppressing is that your report is longer. The cost of deleting is that you cannot reconstruct a decision, and the people who ask are the ones you least want to say "we don't know" to.
What a defensible exception carries#
Four things, and the absence of any one is what turns an exception into a hole:
- What it applies to, narrowly. An exception scoped to a rule and a path is a decision. One scoped to "this whole control" is a switch that somebody flipped.
- A reason a stranger can evaluate. Not "false positive". why it is one. The reader in two years has no context and cannot ask you.
- Who accepted it. A named person, because accountability that belongs to everybody belongs to nobody.
- An expiry. Risk accepted today on the basis of "no patch exists" stops being acceptable the week one does. An exception with no end date is a permanent decision made under temporary circumstances.
The fourth is the one most tools omit and most programs need. An accepted risk that nobody revisits is not a decision; it is a decision's fossil.
Where this differs from a bug#
A bug is a thing to fix. An accepted risk is a thing to review. That difference should be visible in your process: the exceptions list is a document somebody reads on a schedule, not a configuration file that grows.
If nobody has ever removed an entry from your exception list, it is not a list of decisions. It is sediment.
How Draugr fits#
Draugr's config.exclude is built on this: an excluded
finding stays in the report, marked suppressed, carrying its reason.
config:
exclude:
- paths: ["test/fixtures/"]
rules: ["private-key"]
reason: >-
The integration test writes a throwaway key so the secrets control has something real to
find. The material is fake and was never valid anywhere.
acceptedBy: "A. Engineer <a.engineer@example.com>"
expires: 2026-12-31
The scan output says how many findings were suppressed and by whom, so a passing verdict never hides the fact that a judgement was made. An exclusion past its expiry stops suppressing and says so, and one that matched nothing is reported too, because a rule doing nothing is usually a typo, a rule id that moved, or a finding somebody already fixed and forgot to stop excusing.
When somebody else made the decision#
Not every acceptance is yours. A supplier ships a component and a document saying which of its vulnerabilities do not affect it, and the tempting move is to copy their conclusion into your own exclusions, where it stops being theirs.
That is the one thing worth avoiding. The question at the top of this page is who decided, and a
retyped not_affected answers it with your name. Draugr reads a supplier's
VEX document directly instead, so the claim keeps its author, its source and the
date it was asserted:
components:
- name: payments-api
vex:
- path: vendor/acme-api.openvex.json
1 finding excused by a supplier's VEX · 1 asserted by Acme Ltd <security@acme.example>Accepting a supplier's analysis is still an acceptance, and you are still the one who chose to accept it. What changes is that the record says whose analysis it was, so the question can go to the party who did the work.
None of that makes the decision for you. It makes the decision legible, which is the part an auditor is asking about.