Dan Lorenc, co-founder and CEO of Chainguard, argues that average time to exploit has gone negative. He is explicit that the figure is not his data. He points to independent reports from Mandiant, CrowdStrike, and other vulnerability detection and response vendors. The glossary defines the calculation as exploitation date minus a chosen reference date (Mandiant's 2023 analysis uses patch release), so a negative average places observed exploitation before the patch release, on average. It is an average over observed exploited vulnerabilities, not a promise about your patching window.
Dan's theory of the distribution is a separate claim. Dan notes that exploitation before disclosure, the zero-day case, has always existed. He thinks the long tail got squeezed: flaws disclosed and never exploited, or exploited years later. Weaponizing a write-up, in his account, is now cheap.
Anybody with a cheap model, anybody with an open model can take the knowledge of a vulnerability and build a POC or build an exploit for it in minutes.
Dan Lorenc, co-founder and CEO of Chainguard, on Chain of Thought ep 68
He adds that you are only limited by token cost. He describes the old pattern, people crawling a backlog and exploiting a chosen bug 180 or 270 days later, as over if the bug will be exploited at all. In that case he expects weeks.
I treat that as a dependency problem you can work before a report arrives. If a known bug can become an exploit in minutes, every library you cannot name, rebuild, or delete is time you do not have.
What should you record before an upgrade has to happen tomorrow?
Dan's advice to a maintainer of small open source projects and to an enterprise security leader is the same practice at different scale. He wants you to be able to upgrade any dependency if a patch lands tomorrow, and he uses Log4j as the drill.
Just imagine what happened during Log4j and then go make a list of every dependency you have and kind of game plan it out.
Dan Lorenc, co-founder and CEO of Chainguard, on Chain of Thought ep 68
I would keep that list in version control. For each direct dependency, record the pinned version, the date you pinned it, whether you can rebuild it from source you have, whether a maintainer will take a patch, and what breaks if you remove it. Then repeat the pass for indirect dependencies: Dan points out that the newest release of a library you chose can still depend on something five years old.
He puts open source at anywhere from 90 to 95 percent of a modern application by line of code, with many applications sharing that same code. When models are aimed at a running app, he says they find issues in your own code, in third-party code, and often in a chain across both. He also says vulnerability databases mostly deal with flaws still present in the latest version. If the bug is gone upstream and nobody filed it, a scanner that only trusts those signals will not tell you to move. Some dangerous code was removed upstream on purpose with no vulnerability ID, or disappeared in a refactor, so there is no patch to copy.
Two more columns belong on the list for that reason: "present in the latest version?" and "can we reach a maintainer?" Dan's view is that a maintainer has no duty to handle a security report. Say whether you will, and how. If you will not, expect a fork. He thinks the cost of keeping a fork has dropped because agents are good at rebasing and merge conflicts, the work that used to make long-lived forks a bad trade.
This checker is a sketch of that rule. It is not a scanner Dan or Chainguard runs.
# Sketch only. Illustrates an inventory rule.
from datetime import date
MAX_AGE_DAYS = 365 * 2
def flag(dep):
age = (date.today() - dep["pinned_on"]).days
reasons = []
if dep["indirect"] and age > MAX_AGE_DAYS:
reasons.append("old indirect pin")
if not dep["rebuildable_from_source"]:
reasons.append("cannot rebuild from source")
if dep["unused"]:
reasons.append("candidate to remove")
if dep["we_ship_it"] and not dep["maintainer_reachable"]:
reasons.append("plan a fork or a replacement")
return reasons
When do you trim, rebuild, or remove a package?
I would start by cutting packages, then by rebuilding what remains from source into a smaller image. Dan asks whether you need 17 database drivers or one shared choice. Fewer libraries means fewer things that can page the whole team. For what you keep, I want the artifact to contain only packages you can reproduce from source you have read, not a base image full of tools the process never calls. When a fix would change behavior so much that an in-place patch is the wrong move, Dan says it is often easier to upgrade away from the component or remove it.
He expects more projects to delete legacy behavior on purpose. After Log4j, he says, a lot of Java projects removed remote JNDI code even without a filed vulnerability in that specific tree. On Log4j itself, he calls the upstream response an open source success, with fixes shipped in days; knowing where it ran and applying the updates took months to years.
He calls patching a treadmill you never finish. I would rather shrink the treadmill: pay down the dependencies you cannot upgrade, and delete classes of behavior you do not need.
What should you fuzz in code you own?
Fuzzing is Dan's older case of discovery running ahead of fixes. You point generated or mutated inputs at a program, often a parser, and see what crashes. A finding is a starting point: you still reproduce it, find the cause, judge the security impact, and test a fix.
But in the Linux kernel and a bunch of other critical projects, fuzzing has been outpacing remediation for a decade already.
Dan Lorenc, co-founder and CEO of Chainguard, on Chain of Thought ep 68
He describes Google's OSS-Fuzz program as constantly sending random inputs into programs and looking for crashes. Once you have a crash, you can usually learn why. The bottleneck he names is the fix, not the search. He thinks AI is the first real chance to speed up the fixing.
I would fuzz the parsers and decoders in code I maintain, including the small libraries I have decided to keep, and I would schedule the triage in the same week as the run. A crash with no owner is another row in a queue. Dan's point about model reports is similar: one run against a sandboxed app, with source and debugging tools and a goal like "break this app," can return many paths. Fixing a single link may not stop the next run from finding another chain. Split the report into patchable pieces, de-duplicate, and decide which bugs are yours.
When does a package change need a compliance check?
A dependency diff can also be a data-flow diff. Continuous compliance swaps a point-in-time audit for live monitoring, because fields that trip no rule on their own can become identifiable when combined, long after someone signed off. I would run that check on each dependency change: what the package reads, where it sends that data, and whether the new combination is identifiable. If you cannot answer, do not ship the bump.
Checklist
- Keep a diffable inventory of direct and indirect dependencies: version, pin date, rebuild-from-source, maintainer reach, and whether the issue exists in the latest upstream.
- For each package, choose remove, rebuild from source into a smaller image, or keep with an upgrade you could run tomorrow.
- Fuzz parsers you own, then reproduce and judge security impact before you call the crash done.
- On each dependency or data-join change, check whether new combinations create a privacy obligation after the last audit.
- When you cite a time-to-exploit number, write down the reference date, and do not treat one vendor average as your patch window.
More on this topic, with the related episodes, is on Chain of Thought. It draws on this episode.
Subscribe to the Chain of Thought newsletter for new episodes and write-ups like this one.
Drafted with AI assistance from the episode transcripts.
Top comments (0)