<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abel Solutions</title>
    <description>The latest articles on DEV Community by Abel Solutions (@abel-dev).</description>
    <link>https://dev.to/abel-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4045531%2F69d74608-9a17-4aef-9b3e-86c17686ca42.png</url>
      <title>DEV Community: Abel Solutions</title>
      <link>https://dev.to/abel-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abel-dev"/>
    <language>en</language>
    <item>
      <title>The NuGet gap Shai-Hulud exposed — and what we built to close part of it)</title>
      <dc:creator>Abel Solutions</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:44:04 +0000</pubDate>
      <link>https://dev.to/abel-dev/the-nuget-gap-shai-hulud-exposed-and-what-we-built-to-close-part-of-it-3k2j</link>
      <guid>https://dev.to/abel-dev/the-nuget-gap-shai-hulud-exposed-and-what-we-built-to-close-part-of-it-3k2j</guid>
      <description>&lt;p&gt;In September 2025, security researchers disclosed Shai-Hulud: a self-propagating worm that spread through the npm ecosystem by stealing maintainer credentials and using them to publish malicious versions of legitimate packages. CISA issued an alert within the week. By the time the dust settled, reporting put the number of poisoned packages above 500, including widely used libraries like &lt;code&gt;@ctrl/tinycolor&lt;/code&gt;. The mechanism was the interesting part: compromised packages scanned the developer or CI environment for npm tokens, GitHub personal access tokens, and cloud credentials, then used whatever they found to publish more malicious versions — automated propagation, not a single actor manually re-uploading. A second wave, reported in late November 2025, showed the technique hadn't gone away.&lt;/p&gt;

&lt;p&gt;None of that is .NET's problem, directly. But it raised an honest question for anyone shipping C# for a living: if the equivalent happened to a popular NuGet package tomorrow — a maintainer's account compromised, a malicious version pushed under a trusted name — what, exactly, would stop it from landing in your build the same day?&lt;/p&gt;

&lt;h2&gt;
  
  
  What NuGet actually has
&lt;/h2&gt;

&lt;p&gt;To answer that fairly, it's worth listing what already exists, because it's more than "nothing."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package signing.&lt;/strong&gt; Authors can sign packages, and every package accepted onto nuget.org is automatically repository-signed. Verification is available via &lt;code&gt;dotnet nuget verify&lt;/code&gt;, and starting with the .NET 8 SDK, signature verification is enabled by default using a certificate trust list Microsoft ships with the SDK. This establishes &lt;em&gt;provenance and integrity&lt;/em&gt; — the package you're restoring is the bit-for-bit artifact that account uploaded, unmodified in transit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package ID prefix reservation.&lt;/strong&gt; Organizations can reserve an ID prefix after a domain-verification process; once reserved, nuget.org rejects any new package under that prefix from a different account. This is a real defense against someone else publishing packages under your reserved namespace — an impersonation control, not an anti-typosquatting one (it doesn't stop a similarly-named-but-different prefix like &lt;code&gt;Newtonsoft.Jso&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted Publishing (OIDC).&lt;/strong&gt; For CI-driven releases, nuget.org now supports exchanging a short-lived GitHub Actions OIDC token for a single-use, ~1-hour NuGet API key — removing the need for long-lived publish secrets sitting in CI, which is exactly the kind of credential Shai-Hulud went looking for on the npm side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that is real, and none of it is nothing. It's also all aimed at the same layer: proving &lt;em&gt;who published this&lt;/em&gt; and &lt;em&gt;that it wasn't tampered with in transit&lt;/em&gt;. Signing answers "is this the artifact that account uploaded." It does not answer "should I trust code from a brand-new version of this package the moment it lands."&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap: nothing gates on trust, and nothing gates on time
&lt;/h2&gt;

&lt;p&gt;Two things are true at once, and neither is a NuGet-specific failing — npm has the equivalent gaps, just a different attack surface (install-time scripts by default, versus automatic build-time hooks for .NET). The ecosystems evolved different defenses on different timelines.&lt;/p&gt;

&lt;p&gt;First: a signed, ID-verified package can still be &lt;em&gt;malicious&lt;/em&gt;. Signing proves identity and integrity, not intent. If an attacker compromises the account that owns a reserved prefix, or if a malicious actor legitimately owns the identity they're publishing under, every defense above passes cleanly.&lt;/p&gt;

&lt;p&gt;Second — and this is the part with a documented track record in .NET specifically — NuGet packages can carry executable logic that runs during &lt;strong&gt;build&lt;/strong&gt;, not just at runtime. MSBuild supports inline tasks and &lt;code&gt;.targets&lt;/code&gt;/&lt;code&gt;.props&lt;/code&gt; files shipped inside a package's &lt;code&gt;build/&lt;/code&gt; folder. For &lt;code&gt;PackageReference&lt;/code&gt; projects, those files get referenced through generated &lt;code&gt;.nuget.g.targets&lt;/code&gt;/&lt;code&gt;.props&lt;/code&gt; during restore, but the code inside them only executes when MSBuild subsequently evaluates and builds the project — restore alone doesn't run it. (Legacy &lt;code&gt;install.ps1&lt;/code&gt;/&lt;code&gt;init.ps1&lt;/code&gt; scripts are a separate, older mechanism scoped to &lt;code&gt;packages.config&lt;/code&gt; projects and the Visual Studio Package Manager; they don't apply to &lt;code&gt;PackageReference&lt;/code&gt; projects at all, and they don't fire on &lt;code&gt;dotnet restore&lt;/code&gt; either.) Researchers documented real campaigns exploiting the build-time mechanism — the "IAmReboot" wave in 2023 used malicious &lt;code&gt;.targets&lt;/code&gt; files that fired, in ReversingLabs' own words, "every time package A is built." That's actually a sharper contrast with Shai-Hulud than a restore-time claim would be: npm's payload runs at &lt;em&gt;install&lt;/em&gt; time, shipped as a &lt;code&gt;postinstall&lt;/code&gt; hook that &lt;code&gt;npm install&lt;/code&gt; executes automatically with no opt-out short of &lt;code&gt;--ignore-scripts&lt;/code&gt;. NuGet's equivalent mechanism fires one step later in the pipeline — a &lt;code&gt;dotnet build&lt;/code&gt; against a poisoned dependency can still execute attacker code before a single line of your own application code compiles, just not at the restore step itself.&lt;/p&gt;

&lt;p&gt;And there's no cooldown anywhere in the pipeline. A version published ninety seconds ago restores exactly the same as one published two years ago. Nothing in the default toolchain treats "brand new" as a signal worth pausing on — and plenty of documented malicious-package campaigns, on NuGet and npm alike, get caught by security researchers days to weeks after publication, not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NuGate does about it
&lt;/h2&gt;

&lt;p&gt;We built NuGate as a dependency age gate. That's the whole scope, and we kept it that way deliberately: the build fails if any resolved package version — direct or transitive — was published less than a configurable number of days ago (seven, by default), unless it's explicitly allowlisted.&lt;/p&gt;

&lt;p&gt;It ships as two pieces. &lt;code&gt;NuGate.Tool&lt;/code&gt; is a dotnet CLI (&lt;code&gt;nugate check&lt;/code&gt;) meant to run in CI between &lt;code&gt;restore&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; — gating &lt;em&gt;before&lt;/em&gt; the next build ever runs, so a malicious &lt;code&gt;.targets&lt;/code&gt; file in a freshly restored package never gets the chance to execute. That directly targets the build-time-execution gap above. &lt;code&gt;NuGate.Build&lt;/code&gt; is an MSBuild task added as a single &lt;code&gt;PackageReference&lt;/code&gt; in &lt;code&gt;Directory.Build.props&lt;/code&gt;, gating every build in a repo including developers' own machines, hooked in after package assets resolve and before compilation starts. A thin GitHub Action wraps the CLI for workflow use.&lt;/p&gt;

&lt;p&gt;The age comes from nuget.org's immutable catalog &lt;code&gt;created&lt;/code&gt; timestamp — not the registration &lt;code&gt;published&lt;/code&gt; field, which nuget.org resets to a sentinel value when a version is unlisted. A version that gets pulled after a compromise is discovered is flagged as a violation regardless of its age, closing that particular loophole. Failures are closed by default: if the nuget.org API is unreachable and age can't be determined, that counts as a violation rather than a silent pass. Config lives in a single &lt;code&gt;nugate.json&lt;/code&gt; — minimum age, enforce vs. warn mode, an allowlist with optional expiry dates so exceptions don't quietly become permanent, and prefix exemptions for internal packages that never touch nuget.org.&lt;/p&gt;

&lt;p&gt;Equally important is what it doesn't do. NuGate does not detect malware, does not scan package contents, and does not prevent attacks. A compromised package that's thirty days old passes the gate exactly the same as any other dependency, by design. A cooldown shrinks the window in which a freshly poisoned version can reach a build — it does not close that window, and it says nothing about what's inside the package. Anyone evaluating it should read it as a narrow, boring control, not a security product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;p&gt;We've released NuGate as open source, Apache-2.0, under the AbelSolutions-io GitHub organization. It installs from NuGet — &lt;code&gt;NuGate.Tool&lt;/code&gt; for CI, &lt;code&gt;NuGate.Build&lt;/code&gt; for the MSBuild integration. Issues and questions are welcome on the repo. There's a small landing page with a longer explanation of the cooldown model at &lt;a href="https://nugate.dev" rel="noopener noreferrer"&gt;nugate.dev&lt;/a&gt; if you want more than this post covers.&lt;/p&gt;

&lt;p&gt;No pricing, because there isn't any.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>security</category>
      <category>opensource</category>
      <category>nuget</category>
    </item>
  </channel>
</rss>
