Jun 3, 2026 · 2 minute readYou’re about to ship a product. Legal asks for the third-party notices, so you generate an SBOM, and half the dependencies come back as NOASSERTION. You can’t put that in a NOTICE file, and legal won’t sign off on it. I’ve spent a lot of late nights writing those files by hand, so this is the talk I gave about the project that saves me from most of them: ClearlyDefined.
SBOMs are great at telling you what’s in your software. They’re much worse at telling you what you’re allowed to do with it. The license fields come from package metadata, and most generators never scan the source, so you get whatever the registry happened to say. Sometimes that’s nothing. Sometimes it’s wrong.
Take uuid@9.0.0. Its package.json declares MIT, and your SBOM believes it. But three files in dist bundle a tiny md5 implementation Paul Johnston wrote back in 1999, and that’s BSD-3-Clause, which has a real attribution clause. Ship uuid without it and your NOTICE file owes him a line you didn’t know about. One curl to the ClearlyDefined API turns up the discovered licenses and every copyright holder, and if it’s still wrong you can file a curation and fix it for everyone downstream.
In the talk I cover where SBOMs fall short, how ClearlyDefined harvests and curates the data, and the handful of ways you’re probably already using it (GitHub’s license data is built on it). If you maintain an open source library, your definition is how every compliance pipeline sees your project, so it’s worth five minutes to check.
Watch on YouTube: "Beyond SBOMs: making license data actionable with ClearlyDefined" May 22, 2026 · 2 minute readWhen NuGet finds a vulnerable package in your project, it tells you. NU1901 through NU1904 have warned about CVEs in your dependencies for a while now. The SDK that runs the build, though? That’s been a blind spot. You can sit on a perfectly patched set of packages and still be running dotnet build with an SDK that went end of life last May.
That always struck me as a gap worth closing. So I decided to fix it.
What you get
In the 11.0 preview 5 SDK, you opt in with one property:
1<PropertyGroup>
2 <CheckSdkVulnerabilities>true</CheckSdkVulnerabilities>
3</PropertyGroup>
Drop that into your Directory.Build.props and the next build checks the SDK you’re using. If anything’s off, you get one of three warnings:
NETSDK1238: the SDK has known CVEsNETSDK1239: the SDK is end of lifeNETSDK1240: your feature band has no newer release, even though the channel is still supported
It’s the same data dotnet sdk check already surfaces, just folded into the build so it lands in your CI logs and your editor’s Problems pane without anyone having to remember a separate command.
The point isn’t to nag. It’s that “is my toolchain patched?” should be answerable by the build, the same way “are my packages patched?” already is. If you scan your dependency graph for CVEs but not your compiler, you’re only checking half of the supply chain.
Suppressing the noise
If you genuinely need to pin to an out-of-support SDK for a bit, suppress the specific code rather than the whole check:
1<NoWarn>$(NoWarn);NETSDK1239</NoWarn>
That keeps NETSDK1238 firing for real CVEs while you sort out the EOL story on your own timeline.
What’s next
Opt-in is the right starting point, but it isn’t the end goal. I’d like to wire this into dotnetup so the warning can point you at an upgrade you can actually run, rather than a download page. Once that lands, flipping the default to on becomes a much easier conversation.
In the meantime, give it a go on 11.0.100-preview.5.
Feb 16, 2026 · 2 minute readI ride a Specialized Vado SL 4.0. It has a TCU (Turbo Connect Unit) that broadcasts telemetry over Bluetooth Low Energy — battery level, speed, motor power, cadence, temperature, the works. Specialized has their own Mission Control app for this, but I wanted the data in Home Assistant.
So I built a custom integration.
The protocol
Specialized’s BLE protocol isn’t documented anywhere official. Fortunately, Sepp62 had already reverse-engineered it for an ESP32 project. The protocol is called “TURBOHMI2017”, and the UUID base literally encodes this string backwards in ASCII, which I find weirdly charming. Messages are dead simple: one byte for sender, one byte for channel, and 1-4 bytes of little-endian data.
I ported the protocol to Python as specialized-turbo, an async library built on bleak. It handles scanning for nearby bikes, pairing (the bike shows a 6-digit PIN on its display), and streaming telemetry. There’s also a CLI if you want to poke around without writing code:
1$ specialized-turbo scan
2Found: My Vado SL (AA:BB:CC:DD:EE:FF)
3
4$ specialized-turbo telemetry --address AA:BB:CC:DD:EE:FF
5battery_charge_pct: 73
6speed_kmh: 24.5
7rider_power_w: 185
8motor_power_w: 120
9cadence_rpm: 82
The Home Assistant integration
ha-specialized-turbo uses Home Assistant’s Bluetooth auto-discovery. Turn on your bike near your HA instance and it shows up. Enter the pairing PIN from the TCU screen and you’re done.
It exposes 18 sensors:
- Battery: charge %, capacity, remaining Wh, health, temperature, charge cycles, voltage, current
- Motor: speed, rider power, motor power, cadence, odometer, motor temperature
- Settings: assist level (Off/Eco/Trail/Turbo), plus the tuning percentages for each level
All data is pushed locally over BLE. No cloud, no polling after the initial connection.
Installation
Install via HACS as a custom repository, or copy the custom_components/specialized_turbo folder into your HA config directory. You’ll need a Bluetooth adapter on your HA host and a Specialized Turbo bike from 2017 or later (Vado, Levo, Creo, or other models with a TCU).
What’s next
The library supports write commands too, like changing the assist level from HA. I haven’t wired that up to the integration yet, but it’s on the list.
Nov 10, 2025 · 4 minute readBack in January 2020, Google open sourced Wombat Dressing Room, an npm proxy that solved a real problem: how do you maintain two-factor authentication for npm packages while still using automation? For teams managing dozens or hundreds of packages, manually entering 2FA codes for every publish wasn’t just inconvenient, it was a dealbreaker. Fast forward to 2025, and npm has finally introduced native support for trusted publishing using OIDC. Which begs the question: is Wombat Dressing Room still necessary?
The problem Wombat Dressing Room solved
npm has supported two-factor authentication for a long time. But 2FA presents a challenge for automation. You need “something you know” (a password) and “something you have” (a code from an authenticator app). That second factor is difficult to automate, leading many teams to simply disable 2FA in their CI/CD pipelines.
Wombat Dressing Room took a different approach. Rather than bypassing 2FA, it created a shared proxy server that managed 2FA centrally and provided three security features:
Per-package tokens
The proxy could generate authentication tokens tied to specific GitHub repositories. If a token leaked, an attacker could only compromise the single package associated with that token—not your entire npm account.
Limited lifetime tokens
Tokens could be configured with a 24 hour lifespan. Even if compromised, the window of vulnerability was limited.
GitHub Releases as 2FA
This was the clever bit. Packages could only be published if a corresponding GitHub release with a matching tag existed. This introduced a true “second factor”, proving access to both the proxy and the GitHub repository.
Enter npm trusted publishing
In 2025, npm rolled out trusted publishing, implementing the OpenSSF trusted publishers standard. It uses OpenID Connect (OIDC) to create a trust relationship between npm and your CI/CD provider. When configured, npm accepts publishes from authorized workflows without requiring long-lived tokens.
The security benefits are clear. Traditional npm tokens can be accidentally exposed in CI logs, require manual rotation, and provide persistent access until revoked. Trusted publishing eliminates all of this by using short-lived tokens that are specific to your workflow and can’t be extracted or reused.
Setting it up
The setup process is straightforward. First, you configure a trusted publisher on npmjs.com by specifying your GitHub organization, repository, and workflow filename. Then you update your workflow to request the necessary OIDC permissions:
1name: Publish Package
2on:
3 push:
4 tags:
5 - 'v*'
6
7permissions:
8 id-token: write # Required for OIDC
9 contents: read
10
11jobs:
12 publish:
13 runs-on: ubuntu-latest
14 steps:
15 - uses: actions/checkout@v4
16
17 - run: npm ci
18
19 - run: npm publish
That’s it. No more NPM_TOKEN secrets, no more token rotation, no more worrying about accidentally leaking credentials. The npm CLI automatically detects the OIDC environment and handles authentication.
There’s one requirement worth noting: you need npm CLI version 11.5.1 or later. But if you’re using a reasonably recent version of Node.js, you’re already covered.
Automatic provenance generation
Trusted publishing also enables automatic provenance generation. When you publish using OIDC from a public repository, npm automatically generates and publishes provenance attestations for your package. You don’t need to add the --provenance flag—it just happens.
Provenance provides cryptographic proof of where and how your package was built, allowing users to verify its authenticity. This transparency is becoming increasingly important as supply chain attacks grow more sophisticated.
Wrapping up
Trusted publishing is a sign that the npm ecosystem is maturing. By implementing the OpenSSF standard, npm joins PyPI, RubyGems, and other major package registries in offering OIDC-based publishing. This standardisation makes it easier for developers working across multiple ecosystems to apply consistent security practices.
Wombat Dressing Room served the community well for over five years, bridging the gap between security requirements and automation needs. Now that npm has addressed those needs natively, the project can retire gracefully. Good infrastructure tools often become unnecessary when the platform absorbs their functionality.
If you’re still using long-lived npm tokens in your CI/CD pipelines, trusted publishing is worth the upgrade. The setup is straightforward, the security is better, and you’ll never have to worry about token rotation again.
Jul 17, 2025 · 5 minute readWhen you create a new .NET project and start writing code, you might find yourself using classes like System.Text.Json.JsonSerializer without ever explicitly adding a reference to System.Text.Json in your .csproj file. This isn’t magic—it’s because these Base Class Libraries (BCLs) are shipped as part of the .NET runtime itself, making them implicit references that are automatically available to your application.
But this convenience comes with a security implication that’s easy to miss: when a vulnerability is discovered in one of these implicit dependencies, patching it isn’t as straightforward as updating a NuGet package reference.
The invisible dependency problem
Let’s start with a real-world example. In October 2024, Microsoft disclosed CVE-2024-43485, a high-severity denial of service vulnerability in System.Text.Json. The vulnerability affects applications that deserialize input to a model with a [JsonExtensionData] property.
Here’s the catch: if you look at your .csproj file, you probably won’t see any explicit reference to System.Text.Json. Yet your application might still be vulnerable. This is because System.Text.Json is part of the .NET runtime’s Base Class Library, making it an implicit dependency that’s automatically available to all .NET applications.
1<Project Sdk="Microsoft.NET.Sdk">
2 <PropertyGroup>
3 <TargetFramework>net8.0</TargetFramework>
4 </PropertyGroup>
5
6 <!-- No explicit System.Text.Json reference, but you can still use it -->
7</Project>
Two paths to patching
When facing a vulnerability in an implicit dependency like this, you have two main options to ensure your application is secure:
Option 1: Add an explicit reference
The most obvious solution is to add an explicit package reference to the vulnerable library:
1<Project Sdk="Microsoft.NET.Sdk">
2 <PropertyGroup>
3 <TargetFramework>net8.0</TargetFramework>
4 </PropertyGroup>
5
6 <PackageReference Include="System.Text.Json" Version="8.0.5" />
7</Project>
This approach leverages NuGet’s “direct dependency wins” rule. When your application has both an implicit dependency (from the runtime) and an explicit dependency (from your .csproj), the explicit one takes precedence, but only if the explicit version is equal to or higher than the BCL version in the runtime.
Here’s the catch: if you specify a lower version than what’s bundled with the runtime, .NET will still use the runtime version. For example, if your runtime includes System.Text.Json version 8.0.4 and you explicitly reference version 8.0.2, the runtime version (8.0.4) will be used. This means you can’t accidentally downgrade to a vulnerable version, but it also means your explicit reference must be at least as recent as the runtime version to take effect.
While this works, it’s not a scalable long-term solution. The .NET runtime includes hundreds of libraries, and making all implicit references explicit would clutter your project files and create a maintenance burden.
Option 2: Set a minimum SDK version with global.json
A better approach is to use global.json to specify a minimum .NET SDK version that includes the patched libraries:
1{
2 "sdk": {
3 "version": "8.0.110",
4 "rollForward": "latestFeature"
5 }
6}
This ensures that anyone building your project—whether locally, in CI/CD, or when deploying—uses at least the specified SDK version, which includes the security patches for all Base Class Libraries.
Self-contained deployments
This matters even more if you’re shipping self-contained applications. When you publish a self-contained app, the .NET runtime is bundled with your application, including all the Base Class Libraries. If you build your self-contained app with an older SDK that contains vulnerable libraries, those vulnerabilities get shipped with your application.
For self-contained deployments, building with an up-to-date SDK isn’t just about development convenience—it’s a security requirement. A global.json file becomes necessary for maintaining a security baseline across your entire deployment pipeline.
The current developer experience pain
Currently, if you don’t have the correct SDK version installed and try to build a project with a global.json requirement, you’ll encounter an often inscrutable error message.
The good news is that the .NET team is aware of the problem. There’s an ongoing effort tracked in dotnet/cli-lab#390 to create a .NET bootstrapper that will improve SDK acquisition and provide better error messages. This work aims to make .NET 10 much more user-friendly when dealing with SDK version mismatches.
What about other runtimes?
This pattern of implicit runtime dependencies isn’t unique to .NET. Java developers face a remarkably similar challenge with the Java standard library. When a vulnerability is discovered in a core Java package like java.util or java.security, the remediation path typically involves updating the entire Java Runtime Environment (JRE) or Java Development Kit (JDK).
For example, when CVE-2022-21449 was discovered in Java’s elliptic curve signature verification, applications using ECDSA signatures were vulnerable regardless of whether they explicitly imported the affected classes. The fix required updating to a patched version of the JRE.
However, Java’s ecosystem has traditionally been more rigid in this regard. While .NET allows you to override BCL versions with explicit NuGet references (leveraging the “direct dependency wins” rule), Java applications are typically bound to whatever version of the standard library comes with their runtime. This makes the global.json approach even more critical in the .NET world, as it’s often your most practical option.
The key difference is that .NET’s package management system provides more flexibility—you can sometimes work around runtime library issues with explicit package references, whereas Java applications usually have no choice but to update their entire runtime environment.
Wrapping up
.NET’s Base Class Libraries make development faster, but they also create a security challenge that’s different from normal NuGet dependencies. BCL vulnerabilities don’t show up in your project file, so they need a different fix.
Using global.json as part of your security strategy helps keep your applications protected against vulnerabilities in both explicit and implicit dependencies. The improvements coming in .NET 10 should make this easier to manage.