sbom-watchdog

Project Scope

Daily SBOM Generation & CVE Drift Detection

Category

Supply chain security

Project Type

Personal Project

A typical application is mostly other people's code. You choose eight libraries and those libraries pull in four hundred more, and nobody on the team has a complete list of what actually ships. The second half of the problem is timing: vulnerabilities are disclosed against libraries that are already installed and already working, so a repository that has not changed in eight months can start shipping a known flaw on a Tuesday morning. A scan run once at build time cannot catch that. The check has to keep running. sbom-watchdog generates a software bill of materials daily inside GitHub Actions, scans it, compares the result against the previous run committed to the repository, and files an issue only for findings that were not there before.

The project is a composite GitHub Action with no runtime dependencies, installed by pasting six lines into a workflow file. It pins Syft 1.51.0 and Grype 0.117.0 by tag with checksum verification, holds findings state as committed JSON that doubles as an audit trail, and files severity-labelled issues through the GitHub API using only the standard library. Deciding what counts as the same finding across runs is the whole of the work: everything else is one command and proves nothing. Reporting the twelve findings that are new rather than the four hundred that already existed is the difference between a tool a team keeps enabled and one they mute after two mornings.

The design question the whole project rests on is what makes two findings the same finding. The obvious answer, the vulnerability identifier plus the package name, is wrong in two directions and both of them file duplicate issues at people. A package upgraded from 1.2.0 to 1.3.0 that remains vulnerable to the same CVE has not become a new problem, so version is deliberately excluded from the key. Package type is included, because requests on PyPI and requests on npm are unrelated packages that happen to share a name.

The harder half is that the identifier itself is not stable. Grype usually reports a GitHub advisory identifier and carries the CVE alongside it, and an advisory published before a CVE has been assigned gains one later. The key therefore changes on a day when nothing about the vulnerability changed. Reconciliation handles this by matching an unrecognised finding against previous ones on package name, package type and an overlap in the identifiers each has ever carried, and carrying the original key forward so the issue number and the date first seen survive. All three conditions are load-bearing, which I only established by deleting one: removing the package type check left every test passing, and it took a fixture built specifically to expose it, where an npm package inherits a Python package's CVE, to make the omission fail.

Two rules follow from thinking about the first run rather than the steady state. A repository with no prior state records its baseline and files nothing, because a first run against a real project would otherwise open several hundred issues at once. And severity is recorded for every finding but filtered only at the point of filing, so a medium-rated flaw later re-rated critical is visibly an escalation rather than appearing as something newly discovered. The diff engine that implements all of this imports nothing from the filesystem, the network, the clock or the environment. A test parses its imports and fails if any of those appear, which is the constraint that kept it testable while five phases of integration were built on top of it.

The diff engine passed its tests early and never needed changing again. Every defect after that came from somewhere else, and the useful ones were not caught by the test suite at all.

The clearest example was a branch that had unit tests, full coverage, and had never been read by a person. Across two test repositories and fifty-three real findings, every vulnerability Grype reported had a fix available, so the code path for a package with no published fix had never rendered. When I finally forced it, it produced a bold heading reading Fixed in: followed by the sentence no fixed version has been published yet. A label negating itself reads as a template that failed to interpolate, so the reader's first conclusion is that the tool is broken rather than that the vulnerability is unfixable, and the issue ended without telling them the two things they could actually do. It now leads with the situation instead of a field it cannot fill, and names the decision: replace the dependency, or accept the risk and record why.

Worse than that was the mixed case, where a package has some findings with fixes and some without. That branch was not unhandled, it was confidently wrong. It took the wording written for fixable findings and told the reader the highest fix version among them, implying that upgrading cleared the package. It did not. A reader could do the work, believe they were finished, and still be carrying an unfixable flaw. The package that exposed it was already sitting in a repository I had been scanning for days.

The third was structural. Committing the audit trail was gated on whether the vulnerability state had changed, which is correct for a repository that has findings and silently wrong for one that does not. A clean, well-maintained project produces an identical state file every day, so the gate never opens and the dated inventory it is supposed to accumulate stays empty. The repository whose compliance story should be easiest to tell would have produced no evidence it had ever been checked. That one was invisible in both existing test repositories, because both had findings, and it took building a third with deliberately current dependencies to see it.

What I took from this is narrower than a green test suite proving a system works. Tests proved these functions returned a string. Nobody had checked it was a string worth reading, and no test can. The parts I now trust most are the ones where I broke the code on purpose and confirmed something failed: deleting a match condition to see whether any test noticed, forcing an API error midway through filing to confirm the issue numbers collected before it still reached the state file, poisoning the socket module so that tests claiming not to touch the network would fail loudly if they did.

The tool is deliberately narrow. It handles one repository and one ecosystem, files issues for new findings only, and does no reachability analysis, which is why the generated inventory states plainly that severity is the published rating and not an assessment of this project's exposure. A high severity flaw in a component that is never reached may present little practical risk, and judging that is a human decision the tool does not make. Writing that sentence into a compliance document mattered more to me than any feature in it.

Available for work

Back to top

Back to top

Let’s connect and explore future opportunities.

Let’s make an impact

Avatar of the website author

John D. Nyagah

Computer Science student pursuing cybersecurity

Contact me

johndenisnyagah@gmail.com

Feel free to reach out if you’d like to connect about opportunities in cybersecurity, technology, or professional growth.

John D. Nyagah

Copyright © 2026 John D. Nyagah. All rights reserved.

Available for work

Back to top

Back to top

Let’s connect and explore future opportunities.

Let’s make an impact

Avatar of the website author

John D. Nyagah

Computer Science student pursuing cybersecurity

Contact me

johndenisnyagah@gmail.com

Feel free to reach out if you’d like to connect about opportunities in cybersecurity, technology, or professional growth.

John D. Nyagah

Copyright © 2026 John D. Nyagah. All rights reserved.

Available for work

Back to top

Back to top

Let’s connect and explore future opportunities.

Let’s make an impact

Avatar of the website author

John D. Nyagah

Computer Science student pursuing cybersecurity

Contact me

johndenisnyagah@gmail.com

Feel free to reach out if you’d like to connect about opportunities in cybersecurity, technology, or professional growth.

John D. Nyagah

Copyright © 2026 John D. Nyagah. All rights reserved.

Create a free website with Framer, the website builder loved by startups, designers and agencies.