BetteryieldsBetteryields
← Blog

Two years of trust, one backdoor: security checks for libraries

Dr. Aaron Hutzler · 16 August 2026 · 11 min

Zwei Türsteher mit sichtbarem Gesicht stehen rechts und links vor dem Eingang einer Bibliothek und prüfen eine Schlange kleiner Roboter, die einlassen möchten
This image was generated with AI.

The most effective attack on a supply chain needs no break-in. It needs patience. Someone contributes to an open project, delivers good work for years and earns co-maintainer status for it. Only then does the backdoor arrive, hidden in test data that is executed during the software's build. This sequence is not invented: it was documented in 2024 in a compression that sits on almost every Linux system [1].

It came to light by chance. A developer thought a login on his own machine felt half a second too slow. He looked into it.

No selection criterion would have caught it. The project was old, widely used, actively maintained and included in every major distribution. Who stood behind the co-maintainer's name remains unresolved to this day.

That is how honest a post about library selection has to start. No process prevents this one case. Almost everything else can be prevented.

1. The gap before the gate

Until recently, our supply chain had two checks. A license reads the license of every dependency. Only a fixed list of unproblematic licenses gets through, MIT and Apache among them. Licenses with a copyleft obligation fail. A name gate checks every newly added package name against a denylist, against typo distance to known packages and against proof that the name exists at all.

Both only take effect once the library is already in. The decision before that was unchecked. Nobody had to justify why the choice fell on this library. Nobody recorded what had been checked. The step with the biggest lever was the only one without a record.

2. The contradiction

The strongest signs of malicious code need live data. How old is the package really. How many other projects depend on it. Is there an unpatched security advisory. Is there a provenance record for the published file. These values change daily and live in no .

Yet the strongest checks are still not allowed onto the network. The reason is entirely practical. Whoever queries an outside data source while checking becomes dependent on it. If the source is unreachable, the check fails. Whoever can influence its answers can fool the check. And the same line of code gets two different verdicts on two different days. A result that was yesterday and today, with nobody having touched the code, is worthless.

This is exactly where selection has stayed unchecked so far. You need the data from the network. You are not allowed to fetch it while checking.

3. The resolution: two phases with different rules

The way out splits the check into two steps. One is allowed onto the network, the other never is.

Once, with network access, on the way in. A person starts the check as soon as a library is proposed for adoption. It is allowed onto the network. License, security advisories, ratings, user counts and timestamps come from a source that bundles these values [2]. Every value is written into a record together with the moment it was fetched. This record becomes a file in the repository.

From then on, forever without network access. The check on the build path queries nothing anymore. It only compares the record against the actual dependencies: does every newly added library have a valid record. Are the deadlines met. Is the approval signed.

The record is the trick. It keeps a readable account of what stood on the network at a given moment. Two machines with the same state reach the same verdict this way, without network access, indefinitely.

4. What blocks outright

Five findings end the check immediately, regardless of everything else:

  • The license is forbidden or unknown. Unknown counts as a finding; a license that cannot be proven is not a pass.
  • The name is on the denylist or sits suspiciously close to a known package name.
  • The checked version has a known, unpatched high-severity vulnerability.
  • There is no public source repository. What nobody can read, nobody checks either.
  • The package is younger than 90 days.

A signed exception with a reason and a name can lift any one of these findings. Except one: the denylist stays untouchable. Anyone allowed to soften it would not need it.

5. What gets weighed and why it never blocks

This catalog was not invented here. OpenSSF's guide for evaluating software asks the same questions: is the project maintained, are bugs fixed promptly, is the license clear [5]. Microsoft's S2C2F , with OpenSSF since 2022, treats bringing in outside software as its own discipline and names the intake check as the first of eight practices [6]. What gets added here is enforceability: a recommendation becomes a file that a gate reads.

Alongside the exclusion criteria stands a score built from around a dozen signals. It warns and never blocks. Below a threshold, the record only becomes valid with explicit human sign-off, with a name and a reason.

The reason is in another post on this blog: once a metric becomes a target, it stops being a good metric [3]. Reputation is the most purchasable metric in the supply chain. User counts can be bought, so can stars, even more easily.

A score that blocks is a score somebody will attack.

That is why none of the five hard rules asks how popular a library is. The score alone decides nothing.

At Betteryields, reputation is the primary signal in nine cases out of ten. One condition makes the difference: we never read it on the project's own page, always backwards. On its own website, a project writes down whichever users it wants to be known for. The counter-check counts the projects that actually depend on it.

We check four further signals alongside it:

How many people work on it and how spread out. If 90% of commits come from one person, the project is orphaned the moment that person leaves. Many names in the history do not help if one of them writes everything.

When was the last commit. The threshold depends on the kind of project. For a complex, living project, we expect activity within the last week. For a mature library that at most still fixes bugs, three to four months is fine. Years never are. A big vendor's name behind it does not help either.

What is the state of open issues. Old feature requests can sit open. Genuine bugs older than a year are a warning sign.

Who carries the project. An open source foundation behind it is a strong plus. Contributions from company accounts rather than personal accounts likewise. Inclusion in the vetted part of a major Linux distribution too, because someone looks at it regularly there.

6. The expectation value

One signal differs from all the others: it counts nothing, it reads instead.

The question is: does the library do exactly what it should. A library for reading images has to access the file system, that is expected. If the same library opens a network connection, that does not fit its purpose. So the first look always goes to the imports. The next look goes to runtime loading, because dynamically loaded code escapes the check.

At millions of lines, this look does not pay off. At small libraries, it does. A language model speeds it up. So the review only runs below a line-count threshold, inside a sandboxed environment. Its result lands as text in the record.

This method has a limit. It lies in its imprecision: whoever is good enough fools it. The case from the beginning proves that.

7. The waiting period

Newly published versions arrive 14 days late into the project, on purpose.

The reason lies in the timing of the attacks. In the takeover of the npm package axios, the malicious versions were discovered roughly three hours after publication [4]. In the attack on s1ngularity, the infected packages were available for roughly four hours [4]. A twelve-hour waiting period would have fully blocked both attacks, according to the analysis [4]. As a rule of thumb, it recommends a week.

Whoever waits lets the world check for them. The price deserves an honest name: even an urgent security patch arrives 14 days late. That is what the signed exception is for, releasing exactly one version.

8. The question of origin

One criterion is deliberately not automated.

Who stands behind the project. Some libraries belong to a company that itself is barely more than an address, while the work happens somewhere else entirely. That alone is no reason for exclusion, only a reason to look closer. The extended check matters. It often turns out reassuring. For one such library, it showed that a major vendor uses it in its own products. For another, a full source-code review found nothing critical.

No automatic verdict comes out of this. Least of all by origin. The record has a field for this research, filled in by a person. The gate only enforces that the field is not empty. An empty field costs points. It never determines the verdict. For individual, pre-named cases, a condition follows instead of a ban: adoption only with recurring code review.

The restraint has a sober reason. The quality of a line of code depends on its content, not its origin. Whoever judges by the map is not checking, only guessing.

9. Four outcomes instead of two

The check ends in one of four outcomes.

Adopt. The default case.

Reject. The record stays on file. If the name turns up again in the dependencies later, that is a finding. A rejection that gets forgotten becomes an adoption the next time around.

Exception. Adoption despite a finding, with a reason and a name in the record.

Fork and maintain it yourself. The most interesting outcome. Whoever has checked a small library themselves can copy it and take responsibility for it from then on. This is not the same as building it from scratch: the wheel stays invented, only the maintenance moves in-house. The record then requires two additional entries: where the fork lives and who maintains it.

10. An honest limit

Above everything stands a sentence we say to ourselves: despite every criterion, it will get us sooner or later.

The process lowers the odds of a hit and does not remove them. It catches the typo attack, the freshly created package, the orphaned library, the forbidden license and the known vulnerability. It does not catch someone who builds trust for years.

What it delivers beyond that is less visible and, if anything, more important. Every adopted library now has a record with a date, values and a name underneath. If a similar case comes up again, it will show what was known at the time of the decision and who made it.

A list of names has become a file.

11. Sources

[1] National Vulnerability Database, "CVE-2024-3094", 2024. A backdoor in a widely used compression library, publicly reported on March 29, 2024.

[2] Google Open Source Insights, "Announcing the deps.dev : critical dependency data for secure supply chains", Google Online Security Blog, 2023.

[3] Betteryields, ": why metrics tip over and what needs instead", agentic-gates blog, 2026.

[4] K. Toomey, "The case for dependency cooldowns in a post-axios world", Datadog Security Labs, 2026.

[5] Open Source Security Foundation, "Concise Guide for Evaluating Open Source Software", OpenSSF Best Practices Working Group, 2022.

[6] Open Source Security Foundation, "Secure Supply Chain Consumption Framework (S2C2F)", 2022.

12. Download: the release checklist

The release of a new third-party library, top to bottom. The first five points end the check on the spot, the soft criteria below only feed the verdict.

Hard, any one point ends the check

Licence. Forbidden or unknown counts as a finding. A licence you cannot establish is not a release.

Name. On the blocklist or suspiciously close to a known package name: it stops here.

Known vulnerability. The checked version carries an unpatched flaw of high severity.

Source. There is no public source repository. What nobody can read, nobody checks.

Age. The package is younger than 90 days.

A signed exception with a reason and a name lifts any of these findings. One it does not: the blocklist.

Soft, every point feeds the verdict

Who writes it. If nine in ten commits come from one person, the project is orphaned the day that person leaves.

Last commit. For a living project we expect activity within the last week. For a finished library three to four months is fine. Years never.

Open reports. Old feature requests may sit. Real defects older than a year are a warning sign.

Who carries it. A foundation behind it counts. Work under company accounts counts. Inclusion in the vetted part of a major distribution counts.

Before it goes in

Waiting period. New versions arrive 14 days late, on purpose. In two analysed attacks on package registries the poisoned versions were available for hours only.

Record. Every fetched value goes into a file in the repository with the time it was fetched. After that the build asks nothing, it only compares.

Outcome. Accept, reject, signed exception, or fork and maintain it yourself. A rejection you forget becomes an acceptance next time.

To take awayPDF

Download the sheet as a PDF

Download

Betteryields

Put quality gates into your build that an agent cannot fake