Dev Tool Experiences
All articles

· 7 min read

Your Security Scanner Found the Vulnerability. Now Find Its Owner.

By A. Taylor

  • tools
  • humor

Security scanning has achieved a kind of operational perfection: run one command, receive 184 findings, and immediately discover that the organization’s entire remediation plan is a Slack thread called #security-triage containing a message from February that says “circling back.” We can find more vulnerabilities than ever because dependency graphs, container layers, SBOMs, and static analysis have all become wonderfully legible. The hard part remains converting a red row in a dashboard into a changed deployable artifact. That is inconvenient because changed deployable artifacts have owners, regression risk, release calendars, and occasionally a Java application that has not been successfully restarted since 2017.

A scanner is an inventory system, not a security program

Start with what your tool actually knows. OSV-Scanner extracts package information from a project or image and matches it against advisory data. Trivy identifies known vulnerable components in a filesystem or image. Neither tool can reliably tell you whether a vulnerable function is reachable through your production request path, whether a compensating control makes exploitation impractical, or whether the container it scanned is running anywhere that matters. They have found a match, not conducted a deposition.

That distinction is not an argument for turning scanners off and returning to the reassuring mystery of not knowing. It is an argument for keeping the output narrow enough that someone will read it. For dependency work, a local scan can be as unromantic as this:

osv-scanner scan -r . --format json > osv.json
trivy filesystem --scanners vuln --severity HIGH,CRITICAL \
  --ignore-unfixed --format json --output trivy.json .

Run that in CI on a lockfile-changing pull request and on a scheduled default-branch job. The pull-request scan answers, “What did this change introduce?” The scheduled scan answers, “What became known after we shipped?” Conflating those two questions is how teams arrive at a build that fails on Tuesday because a vulnerability database learned something new about a base-image package nobody touched. Technically defensible; socially equivalent to a smoke alarm that waits until the meeting starts.

Use filters, but understand which mess they are hiding

The Trivy command above uses --ignore-unfixed, which shows only findings with a fix available. That can turn a report from a haunted-house guest list into work a developer can plausibly complete. It can also hide a serious finding for which your real options are upgrading a parent component, removing the feature, changing exposure, or documenting an accepted risk. “No patch exists” is not the same sentence as “no work exists.” Use that filter for a merge gate if you must, but keep a separate scheduled report of unfixed findings and make somebody look at the small, unpleasant remainder.

Trivy also documents an important limit that gets lost during dashboard procurement: it does not support third-party or self-compiled packages and binaries in its vulnerability scanner. If your production secret sauce is a custom binary copied into an otherwise immaculate image, the scanner’s calm expression is not evidence that the binary has become morally pure. Scan the source and dependencies too, maintain an SBOM, and know what is actually deployed.

Prioritize by exploitation and exposure, not by the loudest decimal

A CVSS score is useful information, but an all-CVSS queue is how every team eventually schedules a 9.8 in an optional local development package ahead of an actively exploited flaw on a public endpoint. CISA’s Known Exploited Vulnerabilities catalog is a much better first cut: it is specifically a catalog of vulnerabilities with evidence of exploitation in the wild. Treat a KEV match on an internet-facing or production-reachable asset as an interruption, not an item for next sprint planning.

For everything that is not already in KEV, add EPSS as a prioritization input. EPSS provides a probability score and percentile that help distinguish the immense CVE universe from the portion with higher expected exploitation activity. The useful query is wonderfully small:

curl -s 'https://api.first.org/data/v1/epss?cve=CVE-2025-12345' | jq .data[0]

Do not build a policy around one magic EPSS cutoff and then declare the risk model complete, preferably in a slide with a gradient. Combine it with whether the affected package is in production, whether the vulnerable capability is enabled, whether the service is exposed, whether credentials or tenant data are at stake, and whether a fixed version is operationally safe to adopt. Security is allowed to have a queue; it is not allowed to have a queue with no ordering principle beyond “Critical, alphabetically.”

Make a finding end in one of four verbs

The remediation loop should be brutally boring. Every finding that survives initial filtering needs one outcome: fix, mitigate, accept temporarily, or dismiss as inapplicable. “Investigate” is not an outcome. It is a polite way to put a CVE in a cardboard box labeled “later,” where it can form a support group with every TODO in the repository.

  • Fix: upgrade, replace, or remove the vulnerable dependency; verify the resulting artifact, then close the finding from the merged change.
  • Mitigate: disable the affected feature, restrict network access, rotate exposed credentials, or add a compensating control while a patch is being tested.
  • Accept temporarily: name an owner, write why the risk is accepted, set an expiry date, and create the review event now rather than trusting future-you to remember.
  • Dismiss as inapplicable: record the evidence. “Not reachable because the feature is disabled in production” is evidence. “Seems fine” is atmospheric writing.

GitHub’s own alert APIs make the organizational reality unusually explicit: Dependabot dismissals include reasons such as not_used, tolerable_risk, and no_bandwidth. The last one is refreshingly honest, but it should create visible debt rather than a clean dashboard. A dismissed alert with no expiry, no owner, and no comment is not triage. It is a database-shaped filing cabinet with a trapdoor.

Measure the part after detection

Stop presenting “findings discovered” as the main security metric. That measures scanner enthusiasm. Track median time from finding to a real decision, time from KEV match to mitigation on production assets, count of overdue temporary acceptances, and the percentage of new critical or high findings introduced by a pull request. Keep the raw total too, mostly as a reminder that software contains other software, which contains additional software, all of which has opinions about OpenSSL.

The goal is not a zero-finding dashboard. That usually means someone discovered suppression syntax. The goal is a small enough live queue that every item has an owner, a reason for its priority, and a next action. Scanners can now find the needles at industrial scale. The grown-up tool choice is the one that makes your team pick them up.

Sources & citations

  1. [1]CISA Known Exploited Vulnerabilities Catalog
  2. [2]FIRST EPSS User Guide
  3. [3]FIRST EPSS API documentation
  4. [4]Trivy filesystem command reference
  5. [5]Trivy vulnerability-scanning documentation
  6. [6]OSV-Scanner usage documentation
  7. [7]GitHub Docs: Dependabot alerts REST API
  8. [8]GitHub Docs: Resolving code scanning alerts
Your Security Scanner Found the Vulnerability. Now Find Its Owner. | Dev Tool Experiences