Article · Practice

Software composition analysis: the part that never finishes

Most teams meet SCA as a task. You add a scan to CI, you work through what it finds, the build goes green, and the ticket closes. That part is real and it is the easy part. The work that decides whether any of it was worth doing starts afterwards — because the report you were so pleased with in March describes a world that stopped being true in April.

26 Aug 2026 · by the depproof team

What SCA actually does

Software composition analysis answers one question — what third-party code is in this product? — and then asks two things of the answer.

  • Is any of it known to be vulnerable? Every component is matched against public advisory data. Not just the libraries you chose: the transitive ones they brought with them, which is the large majority of what you ship.
  • Can you distribute all of it? Each component’s license is identified and classified, so a copyleft or otherwise restrictive license is caught before it is in a release rather than during diligence.

The by-product is an inventory — a CycloneDX SBOM — which is the artifact a customer’s security team asks for, and the thing that makes the next question answerable in minutes instead of days.

That is the whole of it, and none of it is difficult to start. The difficulty is that it is not a state you reach. It is a position you hold.

Your findings move when your code doesn’t

Here is the thing that surprises people who treat a scan as a task: an SCA finding is the product of two inputs, and only one of them is yours.

The first input is your dependency set. That changes when someone edits a manifest — visibly, in a pull request, under review. The second input is everything the world knows about those components: which versions are known vulnerable, how severe, whether anyone is exploiting them, what the package is really licensed under. That input changes daily, on other people’s schedules, with no notification to you and no event in your repository.

So the finding count on an untouched repository is not stable, and a rising count is not evidence that anyone did anything wrong. Nobody introduced the problem. It arrived. Two teams can ship identical artifacts, and the one that re-checks discovers something the other is also carrying and does not know about.

The same asymmetry runs the other way, which is more cheering: findings also resolve without you. Advisories are corrected, affected-version ranges narrow, a license that could not be determined gets classified. The point is not that the number goes up. It is that the number is not yours to hold still.

Four moments worth scanning, and the one everyone skips

  • 1 · On every pull request. The gate. It stops new problems entering, and it is the only moment where fixing is nearly free, because the person who added the dependency is right there and still has the context.
  • 2 · On a schedule, against code nobody has touched. This is the one that gets skipped. A PR-triggered scan only ever runs when someone opens a PR — so the repository in maintenance mode, the service that has been stable for eight months, the internal tool with one owner who left, are exactly the repositories that receive no scans at all. They are also the ones nobody is watching. A weekly cron trigger in your existing workflow closes that gap, and costs a CI minute.
  • 3 · When something big lands. Occasionally a vulnerability arrives that everyone is talking about by lunchtime. The only question that matters is whether it is in your products and where, and the answer needs to take minutes. That is a search across collected inventories, not a fresh scan of every repository in sequence.
  • 4 · When your own decisions come up for renewal. You accepted a finding six months ago for a reason that was good at the time. Whether it is still good is a question somebody has to be made to answer.

The first is a workflow trigger. The second is a cron line next to it. The third is the one that quietly requires having done the first two everywhere, and having kept the results somewhere you can query — which is the point at which per-repository scanning stops being enough and aggregation starts earning its keep. depproof’s hub exists for that search; until you have more repositories than you can hold in your head, you do not need it.

Keeping up without drowning

The failure mode of a healthy SCA practice is not missing things. It is producing more true findings than anyone can act on, until people stop reading them. Four habits do most of the work against that.

  • Order by exploitation, not severity. A severity score says how bad something would be if exploited; it says nothing about whether anyone is exploiting it. Those are different questions, and the second one reorders your queue rather than annotating it. Plenty of confirmed-exploited findings are rated below high, and plenty of highs have never been touched by anyone.
  • Fix where you have leverage. The finding names a package; the fix usually belongs on the dependency that introduced it. Upgrading one direct dependency often clears a page of findings at once, and chasing them individually is how a morning disappears.
  • Record decisions instead of remaking them. When you assess a finding as not affecting you, that judgement is worth more than the assessment itself — write it down as a waiver with an owner and an expiry, and publish VEX if customers scan your artifacts. Muting deletes the finding; a waiver keeps it visible and makes someone look again later.
  • Trust coverage, not the count. The most dangerous report is a short one produced by a scan that read the wrong file. Zero findings and zero coverage look identical unless the tool tells you which it was — depproof fails the build when it could not resolve the whole graph, on the principle that a clean result you did not earn is worse than no result.

The five ways teams fall behind

None of these are exotic. All of them are common, and each one is quiet while it happens.

PatternWhy it goes unnoticed
Scanning only on pull requests Every active repository looks covered. The dormant ones report nothing because nothing runs, which is indistinguishable from nothing being wrong.
Treating the count as the score A number that goes up is read as a failure of the team rather than a change in the world, so the incentive quietly becomes to scan less rather than to fix more.
Muting instead of waiving It looks identical on the day. Six months later nobody can say whether a finding was assessed and accepted or simply silenced, and the reasoning has left with the person who had it.
Waivers with no expiry A temporary exception with no end date is a permanent one that nobody has admitted to. It ages out of relevance without ever being revisited.
An inventory only one repository deep Fine until the day the question is estate-wide. Then the answer takes a week, and the week is the incident.

What keeping up actually looks like

Modest, and mostly boring, which is the point. A gate on pull requests so nothing new arrives unnoticed. A scheduled run so what you already shipped keeps being checked. An inventory per release, kept somewhere you can search when the question is urgent. A triage order based on what is being exploited rather than what scores highest. And a written record of every finding you decided to live with, each with a name against it and a date it comes back.

That is the whole practice. It is not a product decision and it is not a maturity model — most of it is a scheduled job and a habit. depproof exists to make the data underneath it trustworthy, across Maven, Gradle, npm, Python and Go, on your own infrastructure. But the part that determines whether any of it works is the part nobody can buy: continuing to look after the first report stops being interesting.

Frequently asked questions

What is software composition analysis?

Software composition analysis (SCA) identifies every third-party component your project pulls in — direct and transitive — and checks each one against two things: known security vulnerabilities, and the license obligations that come with distributing it. The output is an inventory you can hand someone, a list of findings you can act on, and in CI a pass/fail gate. It covers the code you did not write, which in a modern application is most of the code you ship.

Why do new findings appear when nothing in my code changed?

Because SCA findings are the product of two inputs and only one of them is yours. Your dependency set changes when you edit a manifest. The knowledge about that set — which versions are known vulnerable, how severe, whether anyone is exploiting it, what a package is actually licensed under — changes continuously, on other people’s schedules. A component that was clean in March can be the most urgent item in your queue in April without a single line of your code moving.

How often should I run a dependency scan?

On every pull request, so new problems do not enter, and on a schedule against code that has not changed, so newly disclosed problems in what you already shipped surface without waiting for someone to open a PR. The second one is the one teams skip, and it is the one that matters for anything in maintenance mode: a repository nobody has touched in six months gets no PR-triggered scans at all, which is precisely the repository least likely to be watched.

How do I keep a growing findings list from becoming noise?

Order it by evidence of exploitation rather than severity alone, fix at the dependency you actually control rather than the one the finding names, and record the decisions you make so you do not remake them. A finding you have assessed and consciously accepted should be recorded as a waiver with an owner and an expiry, not muted — muting removes the finding, while a waiver keeps it visible and forces a re-decision later.

Is a clean scan evidence that there is nothing to find?

Not on its own. A short findings list has two possible causes that look identical in a report: there is genuinely nothing known against your components, or the scan did not read the dependencies you think it did. Before treating a clean result as good news, check that the component count is in the range you expect and that transitive dependencies appear at all — a report containing only your declared dependencies is describing a fraction of what you ship.

Does keeping up require a vendor platform?

No. The moving part is public advisory data, not a proprietary feed, so the practice runs on tooling you host yourself: a scan in CI on push and on a schedule, an inventory artifact per release, and somewhere to aggregate results once you have more repositories than one person can hold in their head. depproof takes that approach — it runs on your own infrastructure, and your source and findings never leave it.