<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Supply-Chain-Protection on Brennenstuhl on Security</title><link>https://www.janbrennenstuhl.eu/tags/supply-chain-protection/</link><description>Recent content in Supply-Chain-Protection on Brennenstuhl on Security</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><lastBuildDate>Fri, 15 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.janbrennenstuhl.eu/tags/supply-chain-protection/index.xml" rel="self" type="application/rss+xml"/><item><title>Dependency Updates: latest Is Not a Security Strategy</title><link>https://www.janbrennenstuhl.eu/automatic-dependency-updates/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://www.janbrennenstuhl.eu/automatic-dependency-updates/</guid><description>&lt;img src="https://www.janbrennenstuhl.eu/automatic-dependency-updates/automatic-dependency-updates.webp" alt="Featured image of post Dependency Updates: latest Is Not a Security Strategy" /&gt;&lt;p&gt;On March 31, 2026, &lt;a class="link" href="https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/" title="Microsoft: Mitigating the Axios npm supply chain compromise"
 target="_blank" rel="noopener"
 &gt;North Korean state actors compromised Axios&lt;/a&gt;, an npm package with over 70 million weekly downloads. Any project declaring &lt;code&gt;axios@^1.14.0&lt;/code&gt; connected to attacker C2 infrastructure the next time &lt;code&gt;npm update&lt;/code&gt; ran. Six weeks later, a &lt;a class="link" href="https://snyk.io/blog/tanstack-npm-packages-compromised/" title="Snyk: TanStack npm packages compromised"
 target="_blank" rel="noopener"
 &gt;self-propagating worm hit 42 TanStack packages&lt;/a&gt;. It reached Mistral AI and UiPath within hours. It also carried valid &lt;a class="link" href="https://slsa.dev/spec/v1.0/levels" title="SLSA security levels"
 target="_blank" rel="noopener"
 &gt;SLSA provenance&lt;/a&gt; attestations, signed by the legitimate build pipeline.&lt;/p&gt;
&lt;p&gt;The same infrastructure that helps us stay current also amplified these attacks. Automated dependency updates pushed the compromised packages into production builds faster than any human could react.&lt;/p&gt;
&lt;p&gt;We learned that outdated dependencies are dangerous. That is true. But many teams have flipped the lesson around. &lt;em&gt;Newer&lt;/em&gt; is treated as &lt;em&gt;safer&lt;/em&gt;. It is not.&lt;/p&gt;
&lt;h2 id="the-trust-problem"&gt;&lt;a href="#the-trust-problem" class="header-anchor"&gt;&lt;/a&gt;The Trust Problem
&lt;/h2&gt;&lt;p&gt;Modern package managers are not static library linkers. They are code execution pipelines. A single &lt;code&gt;npm install&lt;/code&gt; can run &lt;a class="link" href="https://docs.npmjs.com/cli/v11/using-npm/scripts" title="npm scripts documentation"
 target="_blank" rel="noopener"
 &gt;arbitrary install scripts&lt;/a&gt;, read credentials, and run with full CI permissions. It does this across hundreds of transitive packages that nobody on the team has reviewed. Every dependency expands your &lt;a class="link" href="https://en.wikipedia.org/wiki/Trusted_computing_base" title="Trusted Computing Base"
 target="_blank" rel="noopener"
 &gt;trusted computing base&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;It&amp;rsquo;s not every dependency you add that&amp;rsquo;s a problem; it&amp;rsquo;s every dependency you update.&lt;/p&gt;&lt;span class="cite"&gt;&lt;span&gt;― &lt;/span&gt;&lt;span&gt;Ben Hoyt, &lt;/span&gt;&lt;a href="https://benhoyt.com/writings/dependencies/"&gt;&lt;cite&gt;Every dependency you add is a supply chain attack waiting to happen&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;Two problems arise here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Version ranges like &lt;code&gt;^1.14.0&lt;/code&gt; do not mean &lt;em&gt;&amp;ldquo;I trust version 1.14.0&amp;rdquo;&lt;/em&gt;. They mean &lt;em&gt;&amp;ldquo;I trust every future release that matches this constraint&amp;rdquo;&lt;/em&gt;. That is a large, open-ended trust delegation. The Axios attackers used exactly that. Version &lt;code&gt;1.14.1&lt;/code&gt; was a &lt;a class="link" href="https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/" title="Microsoft: Mitigating the Axios npm supply chain compromise"
 target="_blank" rel="noopener"
 &gt;manifest-only change&lt;/a&gt; that added a malicious dependency while leaving the Axios source code untouched. &lt;a class="link" href="https://semver.org/" title="Semantic Versioning Specification"
 target="_blank" rel="noopener"
 &gt;Semantic versioning&lt;/a&gt; signals maintainer intent, not trustworthiness. The same applies to &lt;a class="link" href="https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html" title="OWASP Docker Security Cheat Sheet"
 target="_blank" rel="noopener"
 &gt;floating Docker tags&lt;/a&gt;, &lt;a class="link" href="https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions" title="GitHub: Security hardening for GitHub Actions"
 target="_blank" rel="noopener"
 &gt;unpinned GitHub Actions&lt;/a&gt;, and &lt;code&gt;latest&lt;/code&gt; tags everywhere.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tools like &lt;a class="link" href="https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/dependabot-quickstart-guide" target="_blank" rel="noopener"
 &gt;Dependabot&lt;/a&gt; and &lt;a class="link" href="https://docs.renovatebot.com/" target="_blank" rel="noopener"
 &gt;Renovate&lt;/a&gt; normalized constant dependency updates. At scale, they improve visibility and shorten the window for known CVEs. But they also changed how we consume trust. Security automation quietly became trust automation. Dependency update PRs receive less review than internal code, even though they run with the same or higher privilege. The volume is overwhelming and eventually the workflow shrinks to: CI passes → vulnerability score drops → merge. That creates a dangerous illusion. &lt;em&gt;&amp;ldquo;No known CVEs&amp;rdquo;&lt;/em&gt; does not mean &lt;em&gt;&amp;ldquo;safe&amp;rdquo;&lt;/em&gt;. Supply-chain attacks are newly published, initially undetected, semantically valid, and technically &lt;em&gt;&amp;ldquo;up to date&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
 &lt;p&gt;False positive alerts are not only a waste of time, they also reduce security by causing alert fatigue and making proper triage impractical.&lt;/p&gt;&lt;span class="cite"&gt;&lt;span&gt;― &lt;/span&gt;&lt;span&gt;Filippo Valsorda, &lt;/span&gt;&lt;a href="https://words.filippo.io/dependabot/"&gt;&lt;cite&gt;Turn Dependabot Off&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;The &lt;a class="link" href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor" title="XZ Utils backdoor"
 target="_blank" rel="noopener"
 &gt;XZ Utils backdoor&lt;/a&gt; in 2024 showed how long-running social engineering can compromise critical infrastructure. The &lt;a class="link" href="https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack" title="Wiz: Shai-Hulud 2.0 Supply Chain Attack"
 target="_blank" rel="noopener"
 &gt;Mini Shai-Hulud&lt;/a&gt; incidents in 2025 and the ongoing supply chain attacks show that the attack surface extends well beyond maintainer compromise. It reaches into version ranges, build pipelines, provenance systems, and the update automation that ties them together. These are not theoretical risks. They are documented, attributed attacks against the most popular packages in the ecosystem.&lt;/p&gt;
&lt;h2 id="what-controlled-trust-looks-like"&gt;&lt;a href="#what-controlled-trust-looks-like" class="header-anchor"&gt;&lt;/a&gt;What Controlled Trust Looks Like
&lt;/h2&gt;&lt;p&gt;There is no perfect fix for supply-chain risk. There are practical ways to replace blind trust with controlled trust:&lt;/p&gt;
&lt;h3 id="pin-versions"&gt;&lt;a href="#pin-versions" class="header-anchor"&gt;&lt;/a&gt;Pin Versions
&lt;/h3&gt;&lt;p&gt;Remove &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt; from &lt;code&gt;package.json&lt;/code&gt; or whatever dependency manifest you use. Use exact versions. For transitive dependencies, force the version with &lt;a class="link" href="https://docs.npmjs.com/cli/v11/configuring-npm/package-json#overrides" title="npm overrides"
 target="_blank" rel="noopener"
 &gt;overrides&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;axios&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.14.0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;overrides&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;axios&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.14.0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Lockfiles like &lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;, &lt;code&gt;Cargo.lock&lt;/code&gt;, and &lt;code&gt;poetry.lock&lt;/code&gt; do not make dependencies safe. They make dependency state reproducible. You cannot investigate what you cannot reproduce.&lt;/p&gt;
&lt;h3 id="enforce-release-age-cooldowns"&gt;&lt;a href="#enforce-release-age-cooldowns" class="header-anchor"&gt;&lt;/a&gt;Enforce Release-Age Cooldowns
&lt;/h3&gt;&lt;p&gt;Supply-chain attacks have a short half-life. The compromised TanStack versions lasted about three hours before someone caught them. A &lt;a class="link" href="https://nesbitt.io/2026/03/04/package-managers-need-to-cool-down.html" title="Package Managers Need to Cool Down"
 target="_blank" rel="noopener"
 &gt;release-age cooldown&lt;/a&gt; rejects packages younger than a threshold. That gives the community time to spot compromises before they reach your builds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.npmrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;min-release-age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# pnpm-workspace.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;minimumReleaseAge: 1440&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.bunfig.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;minimumReleaseAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;604800&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.config/uv/uv.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;exclude-newer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;7 days&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A seven-day cooldown would have prevented both the Axios and TanStack compromises.&lt;/p&gt;
&lt;h3 id="control-upgrade-cadence"&gt;&lt;a href="#control-upgrade-cadence" class="header-anchor"&gt;&lt;/a&gt;Control Upgrade Cadence
&lt;/h3&gt;&lt;p&gt;Filippo Valsorda &lt;a class="link" href="https://words.filippo.io/dependabot/#test-against-latest-instead-of-updating" title="Test against latest instead of updating"
 target="_blank" rel="noopener"
 &gt;recommends a better model&lt;/a&gt; than auto-updating: Run your test suite against the latest dependency versions daily in a sandboxed CI job. You get early warning of breakage without pulling untrusted code into production. You still update. You do it on your schedule, after review, when you are ready.&lt;/p&gt;
&lt;h3 id="verify-provenance-but-do-not-trust-it-alone"&gt;&lt;a href="#verify-provenance-but-do-not-trust-it-alone" class="header-anchor"&gt;&lt;/a&gt;Verify Provenance, But Do Not Trust It Alone
&lt;/h3&gt;&lt;p&gt;The TanStack attack is the first documented case of malicious npm packages carrying valid &lt;a class="link" href="https://slsa.dev/spec/v1.0/levels#build-l3" title="SLSA Build Level 3"
 target="_blank" rel="noopener"
 &gt;SLSA Build Level 3&lt;/a&gt; attestations. Provenance verification is necessary but not sufficient. Pair it for instance with &lt;a class="link" href="https://socket.dev" title="Socket: Supply Chain Security"
 target="_blank" rel="noopener"
 &gt;deep package inspection&lt;/a&gt; to catch suspicious network calls, filesystem access, and hidden scripts before they run.&lt;/p&gt;
&lt;h3 id="deploy-repository-proxies"&gt;&lt;a href="#deploy-repository-proxies" class="header-anchor"&gt;&lt;/a&gt;Deploy Repository Proxies
&lt;/h3&gt;&lt;p&gt;Place package managers behind a properly configured repository proxy like &lt;a class="link" href="https://www.sonatype.com/products/sonatype-nexus-repository" target="_blank" rel="noopener"
 &gt;Sonatype Nexus&lt;/a&gt;, &lt;a class="link" href="https://jfrog.com/artifactory/" target="_blank" rel="noopener"
 &gt;JFrog Artifactory&lt;/a&gt;, or an internal mirror. These proxies should be configured not only to mirror (ideally approved) dependencies, but also quarantine suspicious releases and scan for known malware. But keep in mind that most scanners only detect known threats. Sophisticated attacks are novel by design. Proxy scanning is one layer, not a replacement for version pinning and controlled upgrades.&lt;/p&gt;
&lt;h3 id="use-reachability-aware-scanning"&gt;&lt;a href="#use-reachability-aware-scanning" class="header-anchor"&gt;&lt;/a&gt;Use Reachability-Aware Scanning
&lt;/h3&gt;&lt;p&gt;Not every CVE in your dependency tree matters. A vulnerability in a function you never call does not deserve the same urgency as one that is on your hot path. Go&amp;rsquo;s &lt;a class="link" href="https://go.dev/doc/tutorial/govulncheck" title="Go Vulnerability Checking"
 target="_blank" rel="noopener"
 &gt;govulncheck&lt;/a&gt; traces your actual call graph and filters out findings in code your application never reaches. Other ecosystems are catching up. At a minimum, make sure your scanner can tell whether a vulnerable package is actually used. The best scanners go further and trace the call graph down to the specific vulnerable function.&lt;/p&gt;
&lt;h3 id="reduce-dependency-surface-area"&gt;&lt;a href="#reduce-dependency-surface-area" class="header-anchor"&gt;&lt;/a&gt;Reduce Dependency Surface Area
&lt;/h3&gt;&lt;blockquote&gt;
 &lt;p&gt;A little copying is better than a little dependency.&lt;/p&gt;&lt;span class="cite"&gt;&lt;span&gt;― &lt;/span&gt;&lt;a href="https://go-proverbs.github.io/"&gt;&lt;cite&gt;Go Proverbs&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;Every package adds code, maintainers, credentials, infrastructure, and trust assumptions. Modern ecosystems normalize &lt;a class="link" href="https://research.swtch.com/deps" title="Russ Cox: Our Software Dependency Problem"
 target="_blank" rel="noopener"
 &gt;absurd dependency depth&lt;/a&gt;. Before adding a package, ask: can we write this in 30 lines? If yes, write it. A &lt;a class="link" href="https://www.janbrennenstuhl.eu/tags/agentic-engineering/" &gt;coding agent&lt;/a&gt; can do most of the work for you.&lt;/p&gt;
&lt;h2 id="controlled-trust-not-blind-trust"&gt;&lt;a href="#controlled-trust-not-blind-trust" class="header-anchor"&gt;&lt;/a&gt;Controlled Trust, Not Blind Trust
&lt;/h2&gt;&lt;p&gt;The lesson from 2025 and 2026 supply-chain attacks is not &lt;em&gt;&amp;ldquo;never update&amp;rdquo;&lt;/em&gt;. Stale dependencies become liabilities too. The lesson is that &lt;strong&gt;blind trust does not become safer just because it is automated&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Secure software supply chains are built on controlled trust: Pinned versions. Enforced cooldowns. Verified provenance. Proxy repository managers. Reachability-aware scanning. A small dependency surface. The fastest update is not always the safest. Sometimes the safest thing to do is wait.&lt;/p&gt;</description></item></channel></rss>