Article · Maintenance risk

End-of-life dependencies: the risk with no CVE

Two questions about a dependency look like the same question. Is there a known problem with this? is about the past — somebody investigated, somebody reported, a feed carries the answer. If a problem appears, will anyone fix it? is about the future, and no feed carries it at all. The first question is tracked, tooled and reported on daily. The second is rarely asked at all — and it is the one that decides what your next bad day costs.

Updated 31 Aug 2026 · by the depproof team

The clock, not the count

On 5 August 2015, the Apache Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. Nothing happened that day. No build broke, no report changed colour, no alert fired. The bytes in log4j-1.2.17.jar were identical on 6 August to what they had been on 4 August.

Look at that same page today. It lists seven vulnerabilities — including a high-severity SQL injection flaw in JDBCAppender (CVE-2022-23305) and a critical-severity issue in the bundled Chainsaw component (CVE-2022-23307). Every one of them was published at least four years after the project was declared dead, the earliest in 2019 and the latest in 2022. Apache states the consequence in one sentence: “Since Log4j 1 is no longer maintained none of the issues listed will be fixed.”

This is the whole argument in one artifact. A team checking that jar in 2016 got a clean result and a correct one — the vulnerabilities genuinely were not known yet. The finding count told them about the state of the world’s knowledge on the day they asked. The end-of-life date, already eight months old, told them something the count could not: that when the answer changed, as it did four times over the following three years, nobody would be coming. One of those two numbers was a lagging indicator and the other was a leading one, and only the lagging one had a dashboard.

Why nothing tells you

The absence is structural, not an oversight anyone can patch. A vulnerability record exists because somebody spent effort on a component — investigated it, wrote it up, filed it, usually with a fix in hand. Advisory data is a by-product of attention being paid.

Abandonment is precisely the condition in which nobody is paying attention. The signal you want is missing exactly where you most need it, and for the same reason the risk exists. Worse, the signals that do exist are all voluntary acts by a maintainer who has, by definition, disengaged:

  • Deprecation flags are opt-in and reversible. npm deprecate updates the registry entry so that a warning appears for anyone installing the package. It requires the package owner to log in and run a command — the one thing a departed maintainer is not doing. There is an npm undeprecate to match it.
  • Archiving a repository is the same act in a different place. It is a deliberate, considerate signal, which means it correlates with maintainers who are still conscientious. A project abandoned through burnout or circumstance sits there looking open for business.
  • Activity heuristics infer rather than report. The OpenSSF Scorecard Maintained check looks for at least one commit per week over the previous 90 days and scores an archived project lowest, rating the risk High — possibly unpatched vulnerabilities. It is a good proxy. It is still a proxy, and it cannot distinguish “finished” from “forgotten”.

So maintenance status is knowable — but it is assembled from evidence rather than handed to you as a finding. That is why it falls off the end of every sprint: there is no queue for it, and nothing turns red.

Three places to look, in order of difficulty

The work is not evenly hard. One of these you can settle this afternoon with a calendar.

  • 1. Runtimes — dated, published, checkable in minutes. Language and platform versions are the easy win because their support windows are announced years ahead. Python’s release schedule puts 3.9 past end of life since 31 October 2025, with 3.10 scheduled to follow in October 2026 — next month, if you are reading this when it was written. The Node.js release schedule ended support for Node 20 on 30 April 2026 and for Node 18 a year before that; Node 22 runs to April 2027, Node 24 to April 2028. These are facts on a calendar. Every one of them is a deadline you can put in a roadmap instead of discovering in an incident.
  • 2. Direct dependencies — evidence, but you chose them. For the libraries you deliberately added, the checks are manual and quick: is the repository archived, does the registry show a deprecation notice, when was the last release, and does the project state a support policy at all? You have a shortlist, you know what each one does, and you can judge whether dormancy is a problem here.
  • 3. Transitive dependencies — the actual problem. The components most likely to be quietly abandoned are the ones nobody selected: a small utility pulled in four levels down by a framework, doing something unglamorous, with a single maintainer who moved on in 2021. You did not evaluate it, you cannot name it, and it ships in your product. See transitive dependencies for why this layer behaves differently from the one above it.

The regulation already makes the distinction

If you sell software into the EU, this stops being good practice and starts being paperwork. The Cyber Resilience Act (Regulation (EU) 2024/2847) requires in Article 13(5) that manufacturers “exercise due diligence when integrating components sourced from third parties”, explicitly including free and open-source components.

Recital 34 then sets out what that due diligence can consist of — and the list is worth reading closely, because it names two separate actions: verifying that a component “receives regular security updates, such as by checking its security updates history”, and verifying that a component “is free from vulnerabilities registered in” a public vulnerability database. The drafters put maintenance status and vulnerability-database checks side by side as distinct actions, which is the same distinction this article opens with, arriving from the direction of law rather than engineering.

Article 13(8) closes the loop and is the part worth taking to a planning meeting. Manufacturers must set a support period reflecting how long the product is expected to be in use, and it “shall be at least five years” unless the product’s expected life is shorter. The article also says, in as many words, that when determining it a manufacturer may take into account “the support periods of integrated components that provide core functions and are sourced from third parties”. You are being asked to make a five-year promise about software assembled from components whose own maintenance horizons you may never have looked up. The main obligations apply from 11 December 2027; the reporting obligations in Article 14 land earlier, on 11 September 2026.

What end-of-life does and does not mean

The failure mode here is over-correction — flagging every dormant package as a defect, generating a backlog nobody can action, and teaching the team to ignore a second stream of alerts. Scorecard’s own documentation makes the point plainly: a lack of active maintenance “is not necessarily always a problem”, and it offers the example of a library that determines whether an integer is even. Some code is finished. Quiet is not always decay.

So be precise about what changes. An end-of-life component with no known vulnerabilities is not exposing you to anything today; your current state is genuinely fine. What end-of-life alters is your response time when the state changes. With a maintained dependency, a published vulnerability means waiting for a release and bumping a version — an afternoon. With an unmaintained one, the same event means replacing the component, forking and patching it yourself, or formally accepting the risk. That is weeks to months, planned under pressure, in whatever week it happens to land.

End-of-life is not a measure of how dangerous a dependency is. It is a measure of how expensive its next vulnerability will be — which is why it belongs on a roadmap rather than in an alert queue.

Making it a practice

  • Put runtime end-of-life dates on the roadmap, not the backlog. They are known years ahead. A runtime upgrade planned twelve months out is routine work; the same upgrade attempted during an incident, on a version that stopped receiving fixes six months ago, is not.
  • Review maintenance status when you adopt, not only when you audit. The cheapest moment to reject an unmaintained library is before it is load-bearing. Last release date and repository status take thirty seconds to check in a pull request review.
  • Record the judgement, not just the finding. “This is dormant and that is fine, because it is forty lines that parse a date format that has not changed since 1988” is a legitimate answer — and it is only worth anything if it is written down with a name against it. It is also, not coincidentally, the shape of evidence an assessor accepts. The same machinery that records waivers and VEX statements works here.
  • Escalate on coincidence. The combination that deserves immediate attention is not end-of-life alone, nor a finding alone, but an open finding in an unmaintained component. There, the normal remediation path does not exist, and the sooner you know that the more options you have.

Everything here depends on the inventory

Every check in this article rests on the same prerequisite: a complete and accurate inventory of what you actually ship. You cannot look up the support horizon of a component you do not know is there, and — as the third item on that list makes clear — the components most likely to be abandoned are the transitive ones you never chose and could not name.

That inventory has to come from a lockfile or resolver output rather than a manifest, or the list you are reviewing is a list of your intentions rather than of your software. Zero findings is not zero risk covers how the inventory step goes wrong, and the four checks that tell you whether yours is sound.

Get that right and both questions have something to work from — what is known about each component today, and how long anyone has promised to keep looking.

Frequently asked questions

What is an end-of-life dependency?

A dependency whose maintainers have stated, or demonstrated, that they will no longer publish fixes for it. That covers three different things in practice: a language runtime past the end-of-life date on its published release schedule, such as a Python or Node.js version; a library whose project has been formally declared end-of-life or whose repository has been archived; and a package that is simply dormant, with no release and no maintainer response for years. Only the first has a date you can look up in advance. The other two you infer from evidence.

Is an end-of-life dependency a vulnerability?

No, and treating it as one produces noise that teams learn to ignore. An end-of-life component with no known vulnerabilities is not currently exposing you to anything. What end-of-life changes is not your present state but your future response time: if a vulnerability is published against that component tomorrow, no upstream fix is coming, so your only remedies are to replace the component, fork and patch it yourself, or accept the risk formally. Those take weeks to months. An upgrade takes an afternoon. End-of-life is a measure of how expensive your next bad day will be.

Why is there no advisory feed for unmaintained dependencies?

Because advisory data is a by-product of attention being paid. A vulnerability enters a database when somebody investigates a component and reports what they found — the record exists as a by-product of effort spent on that project. Abandonment is precisely the state in which nobody is spending that effort. The signals that do exist are all voluntary acts by a maintainer who has, by definition, disengaged: npm deprecation flags, repository archiving, an end-of-life notice on a project page. Maintenance status is knowable, but it is assembled from evidence rather than delivered by a feed.

Does the EU Cyber Resilience Act require checking whether dependencies are maintained?

It points directly at it. Article 13(5) of Regulation (EU) 2024/2847 requires manufacturers to exercise due diligence when integrating third-party components, including free and open-source components. Recital 34 sets out what that due diligence can consist of, and lists verifying that a component receives regular security updates — by checking its security-update history — as an action distinct from checking that component against vulnerability databases. The regulation treats "is there a known problem?" and "is anyone maintaining this?" as two separate questions. The main obligations apply from 11 December 2027.

How do I find end-of-life dependencies in my project?

Start with your runtimes, because they are the only part with published dates: check your language and framework versions against their official release schedules. Then take your resolved dependency list and check the largest and most deeply embedded components for an archived repository, a deprecation flag or a last release date measured in years. The prerequisite for both is a complete inventory taken from a lockfile or resolver output rather than a manifest — you cannot assess the maintenance status of components you do not know you ship, and the components most likely to be abandoned are transitive ones you never chose.

Keep reading

General information for engineering teams, not legal advice. Product and project names are used nominatively; depproof is not affiliated with them.