Risk acceptance: the exception nobody can explain later
On this page
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:
| It carries | Without it | |
|---|---|---|
| 1 | What it applies to, narrowly | an exception scoped to a rule and a path is a decision. One scoped to a whole control is a switch somebody flipped |
| 2 | A reason a stranger can evaluate | not "false positive" but why it is one. The reader in two years has no context and cannot ask you |
| 3 | Who accepted it | a named person, because accountability that belongs to everybody belongs to nobody |
| 4 | An expiry | risk accepted 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.
expires: 2026-12-31
The scan output says how many findings were suppressed, 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.
The repository records who accepted it. --evidence prints each exclusion that set findings aside
with its file and line and the git history of that line, the last commit to change it, its author
and its date:
DECISIONS
The integration test writes a throwaway key so the secrets control has something real to…
draugr.saga.yaml:13 · covers 1 finding · private-key · expires 2026-12-31
last changed in 9a70069be00c by A. Engineer, 2026-10-30That record is stronger than a name typed into the descriptor, which nothing checks and anyone
editing the file can write. The commit is the repository's own record of the change, and it holds
to whatever the repository enforces before a change merges, signed commits and required review
included. report.json carries the same history for every exclusion, and where a shallow clone
holds too little history to be sure, it says the history is unavailable rather than crediting
whoever made the clone's oldest commit.
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/payments-sdk.openvex.json
ACCEPTED
config.exclude 1 finding suppressed
VEX 1 finding excusedand, under --evidence, whose decision each one was:
ACCEPTED
config.exclude 1 finding suppressed
VEX 1 finding excused · 1 asserted by Example Payments SDK <security@example.com>The VEX row names the supplier who wrote the statement, and the exclusion's entry under DECISIONS
names the commit somebody here made, which is the distinction the whole section turns on.
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.