Skip to main content

13.8 The findings you are not going to fix

Every course teaches you to find vulnerabilities. Almost none teach you what to do about the ones you cannot fix, which is most of the job after the first month.

Go back to your image scan and ask a question you have not asked yet:

sqlite3 -header -column ~/vuln.db \
"SELECT status, COUNT(*) AS n FROM findings GROUP BY status ORDER BY n DESC;"
status n
------------ ---
fixed 435
affected 213
fix_deferred 90
will_not_fix 34

Four hundred and thirty-five of those findings have a fix. Three hundred and thirty-seven do not. And some of those are serious:

sqlite3 -header -column ~/vuln.db \
"SELECT severity, COUNT(*) AS n FROM findings
WHERE status IN ('will_not_fix','fix_deferred','affected')
GROUP BY severity ORDER BY n DESC;"
severity n
-------- ---
LOW 159
MEDIUM 118
HIGH 42
UNKNOWN 9
CRITICAL 9

Nine critical findings with nothing to install. You cannot patch your way out of this, and no amount of diligence changes that. This is the normal state of every real system, and how you handle it is what separates someone doing vulnerability management from someone running a scanner.

What those statuses actually mean​

Trivy is telling you something specific, and the vocabulary is worth having because every tool has its own words for the same four situations.

  • fixed: a patched version exists. This is work. Do it.
  • affected: the maintainers acknowledge it, no fix is available yet. This is a waiting game, and the useful action is tracking it.
  • fix_deferred: the maintainers have looked and decided not to fix it soon. Usually because the impact in this distribution is low.
  • will_not_fix: they have decided not to fix it at all. Common when a release is near end of life, or the flaw only matters in a configuration the distribution does not ship.

will_not_fix on a CRITICAL is not the emergency it looks like. Look at one of yours:

sqlite3 -header -column ~/vuln.db \
"SELECT cve, package, severity FROM findings
WHERE status='will_not_fix' AND severity IN ('HIGH','CRITICAL') LIMIT 4;"

When I ran that I got several curl and libcurl4 findings, one of them CRITICAL. Debian's maintainers had assessed those and decided the affected code path was not reachable in the way they ship it. Their judgement is better informed than the scanner's, because they can read the source and the scanner is matching version numbers.

That is the single most useful realisation in this lesson: a scanner's severity is a starting position for a conversation, not a verdict.

The four ways a finding ends​

Lesson 13.1 said every finding ends up in one of four states. Here they are properly, because you now have real examples of each.

Fixed. You patched it, and a rescan proves it is gone. The only one that requires nothing else from you.

False positive. The tool is wrong. The version-banner problem from 13.5 is the usual cause. Record why you concluded it was wrong, because otherwise the next scan raises it again and the next person re-investigates it from scratch.

Accepted. It is real, you understand it, and you have decided to live with it. This is a legitimate, extremely common, professionally respectable outcome. It is also the one people do badly, by which I mean silently.

Mitigated. You could not remove the vulnerability, so you removed the exposure. The vulnerable service is now firewalled off, or the feature is disabled, or it only listens on localhost. The flaw is still there and it can no longer be reached.

Mitigation is underrated and it is often the fastest real answer. You have already built the tools for it: ufw from lesson 6.3, firewall rules on FW01 from Module 4, and the reverse proxy from Module 6 that means adding a service does not open a port.

Accepting a risk properly​

An accepted risk that lives in somebody's head is not accepted, it is forgotten. The difference is written down, and the written form is nearly identical everywhere:

  1. What the finding is, with its identifier so it can be matched to future scan results automatically.
  2. Why you are not fixing it. No fix exists, the fix breaks something that matters more, the effort is disproportionate to the exposure.
  3. What reduces the risk in the meantime, if anything. This is where you record the mitigation.
  4. Who decided. Risk acceptance is a business decision, not a technical one. In a job this is a named person with the authority to carry it. In your lab it is you, and writing your own name teaches the shape.
  5. When it gets looked at again. An acceptance with no review date is a permanent decision made on temporary information.

That fifth point is the one people skip and the one auditors ask about first. Circumstances change: a fix appears, or the machine gets moved somewhere more exposed, and nobody revisits the decision because nothing triggers a revisit.

Write one now, properly, in your journal, for one real finding from your own scan. Use those five headings. It will take five minutes, and it is the document Module 16 asks you to produce at scale.

In GRC terms

What you just wrote is a risk acceptance, and in a company it lives in a risk register with the others. Module 16 builds one.

Frameworks care about this more than they care about your patch count, because a risk register is evidence of a decision-making process. An organisation with fifty documented accepted risks and clear review dates is in far better shape than one with none, which usually means nobody is looking.

The supply chain, finally explicit​

Lesson 1.2 owes you something. When you were choosing Obsidian plugins I said that was "your first supply chain decision in the course and it will not be your last", that the term means "the chain of other people's code that ends up running on your machine", and that Module 13 makes it explicit, when you meet a scanner and have to work out what it is really telling you.

Here is the explicit version.

You installed one thing in lesson 13.2: nginx:1.20. One image, one service, chosen from an official repository. The scanner found 500 distinct vulnerabilities in it.

You did not write any of that code. You did not choose most of it. You chose nginx, and nginx brought a Debian base image, which brought curl and zlib and libwebp and two hundred other packages, each maintained by different people on different schedules with different opinions about what is worth fixing.

That is the supply chain, and the scanner is the first tool in this course that makes it visible. The four questions from lesson 1.2 (how many people use it, when was it last updated, who maintains it, can you see how it was built) were you assessing a supply chain by hand, on one plugin. This is the same assessment, mechanised, across five hundred components you never individually chose.

Three habits fall out of that, and they are the actual answer:

  • Fewer components is a security control. A smaller base image has less in it to be vulnerable. This is why -slim and -alpine image variants exist, and why "what does this dependency pull in" is a fair question in a code review.
  • Currency beats vigilance. You demonstrated this in 13.3: one base image upgrade closed four actively-exploited vulnerabilities. Staying current is more effective than triaging brilliantly on a stale foundation.
  • Know what you are running. You now have a database of exactly what is inside one image. The industry name for that inventory is a software bill of materials, an SBOM, and the reason it became a requirement in a lot of contracts is that when the next widely-used library has an emergency, the only question that matters is "are we running it?" Organisations that could not answer that spent the Log4j weekend finding out by hand.

And the blind spot you built​

One last thread. In lesson 12.6 you wrote a rule that suppressed alerts from KALI01, and I told you it created a blind spot on purpose. In 13.6 you tested whether that blind spot was as narrow as you meant it to be.

Notice what that exception actually is: a documented, deliberate acceptance of a known weakness, with reasoning, in version control. It is a risk acceptance. You wrote your first one in Module 12 without it having a name.

The name matters because it makes the practice portable. Every deliberate weakness in every system you ever run should look like that: written down, reasoned, findable, and attached to somebody who decided.

What you take from this​

Most findings do not end in a patch. They end in a decision, and a decision that was never written down is indistinguishable from negligence when someone asks about it later.

You can now say what a supply chain is with a number attached, and you have written one real risk acceptance in the format the rest of the industry uses.