# The .NET Illusion: Why Your NuGet Dependencies Are a Hacker's Playground

Every time npm suffers a supply chain incident, .NET developers breathe a collective sigh of relief. "Haha, npm had another attack!" they think, while feeling safe in their Microsoft-centric world. But here's the uncomfortable truth: the tools to execute the same attacks in .NET aren't just available—they're built into the framework itself. Module initializers, source generators, MSBuild targets, and startup hooks all provide vectors for smuggling malicious code into unsuspecting codebases, and most of them execute before your application's `Main` method ever runs. This is the story of how one security researcher discovered that .NET's supply chain is far more vulnerable than most developers realize, and why your NuGet dependencies might be the weakest link in your cybersecurity posture.

## Understanding the Software Supply Chain Attack

A software supply chain attack targets the interconnected web of dependencies that modern applications rely on. Rather than attacking a single system directly, threat actors compromise legitimate libraries, tools, or vendors, then let the malicious payload flow downstream to thousands of ultimate targets. These attacks often employ a "sleeper" approach: quiet infiltration first, activation later. The payload might steal credentials, deploy ransomware, or establish persistent remote control over compromised systems.

Two recent examples demonstrate the devastating potential of these attacks. The SolarWinds Orion breach (2020) saw attackers compromise the company's CI/build infrastructure, smuggling malicious code into products shipped to customers, including US government agencies. More recently, the npm Shai Hulud campaign (November 2025) compromised maintainer accounts for popular packages like Zapier, PostHog, and Postman, publishing trojanized versions that affected over 25,000 GitHub repositories. The Shai Hulud attack is particularly relevant to .NET developers because its techniques translate directly to the Microsoft ecosystem.

## The Shai Hulud Blueprint: A Masterclass in Modern Malware

The Shai Hulud campaign demonstrated remarkable sophistication across every stage of the attack chain. The infection vector involved compromising maintainer accounts and publishing new versions of legitimate packages—when developers ran `npm install`, the trojanized version was pulled automatically. The dropper executed via npm's preinstall lifecycle hook, the equivalent of NuGet's `.targets` files or module initializers. On CI runners, it executed synchronously to ensure completion before shutdown; on developer machines, it spawned background processes to avoid suspiciously long install times. Environment detection checked for `GITHUB_ACTIONS`, `CI`, `TF_BUILD`, and similar variables.

The payload harvested credentials from `~/.aws/credentials`, `~/.azure/`, environment variables, and cloud metadata services (IMDS). It dumped secrets from AWS Secrets Manager, Google Secret Manager, and Azure Key Vault, then attempted Docker privilege escalation by mounting the host filesystem into a privileged container. Everything was exfiltrated to GitHub repositories, cross-victim—your secrets could end up in someone else's public repo.

The command-and-control (C2) mechanism was the cleverest part. Rather than using traditional C2 infrastructure, the malware registered compromised machines as GitHub Actions self-hosted runners named SHA1HULUD. It then created a workflow file containing `${{ github.event.discussion.body }}`—an injection vulnerability that allowed attackers to execute arbitrary commands simply by opening a Discussion in the GitHub repository. GitHub Discussions became the C2 channel, producing completely legitimate-looking traffic with no suspicious outbound connections to flag.

## The Infection Vector: Homoglyph Attacks and Dependency Confusion

NuGet searches all configured package sources when restoring packages. If a package exists in multiple sources, resolution becomes non-deterministic. This means if your company has an internal package called `AcmeCorp.Framework` on a private feed, and someone publishes `AcmeCorp.Framework` on NuGet.org, there's a good chance the public version gets downloaded instead. This dependency confusion vulnerability can be mitigated through package source mapping in `nuget.config`, package signing and verification, and registering private package ID prefixes on NuGet.org.

But the more insidious vector involves Unicode homoglyphs. When the researcher published `Mİcrosoft.Extensions.AutoMapper` to NuGet.org (note the Turkish İ), they were surprised it was possible at all. After reporting and remediation, they began experimenting further. The classic "rn looks like m" trick with `Microsoft.EntityFrarneworkCore` worked initially, but NuGet.org's internal TypoSquattingService caught single-substitution attempts immediately. However, by combining multiple lookalike Unicode characters—Ukrainian dotted і (identical to Latin i), Armenian ո (looks like n), Greek ο (looks like o), Greek е (looks like e)—the researcher successfully uploaded `Mіcrosοft.EոtityFramewοrkCorе` to NuGet.org. It looked completely legitimate: version 10.0.0, the real EF Core description copied verbatim, matching package icon.

The implications were staggering. The researcher could open "drive-by PRs" to hundreds of open-source projects, changing package references in `.csproj` files. The diff shown in GitHub's PR interface would appear as a simple version bump, with no clear indication of the meddled package ID. GitHub's bug bounty program closed the report as "by design," and while GitHub Copilot's PR review does catch homoglyph substitutions, most human reviewers would never notice.

## The Dropper: Multiple Execution Vectors in .NET

Once infection occurs, .NET offers multiple mechanisms for executing malicious code. The `[ModuleInitializer]` attribute tells the runtime to execute a method when the containing module loads. By decorating the class with `[EditorBrowsable(Never)]`, `[CompilerGenerated]`, and `[DebuggerHidden]`, the code becomes effectively invisible in day-to-day development. The try/catch with an empty handler ensures failures don't leak exceptions that might alert anyone. The only downside is that the assembly must be loaded—you need to convince developers to consume a type from the dropper assembly.

Source generators are even more powerful. A NuGet package can ship a source generator that runs at compile time, injecting code directly into the consuming project's compilation output. The generated code lives in the `obj/` folder, invisible in source control. With a CI-only trigger, developers building locally never see the dropper—only the CI build server gets the injected code. The generated file name looks like a normal source-generated file, using the project's own root namespace to blend in perfectly.

MSBuild `.targets` and `.props` files in a `build/` or `buildTransitive/` folder are automatically imported into the consuming project's build. They can write files, run processes, or generate and compile C# code. The build target runs before compilation, so generated code is included in the final assembly. There's no way to block this mechanism without disabling core .NET SDK functionality, since the SDK itself relies on the same pattern.

The `DOTNET_STARTUP_HOOKS` environment variable tells the .NET runtime to load and execute code before an application's `Main` method. A malicious NuGet package could set this via MSBuild, and from that point forward, every .NET process on the machine would run the hook. This provides persistent, system-wide code execution that's extremely difficult to detect without specialized endpoint monitoring.

## Payload and C2: The Final Stages

Once code runs on the target machine, the payload options are nearly limitless: credential theft, data exfiltration, ransomware deployment, or establishing persistent backdoors. For stealth, payloads can be obfuscated using Unicode invisible characters to encode arbitrary bytes—the encoded output appears as an empty string in source code, hiding the actual payload from anyone reading the code.

For command-and-control, attackers can leverage legitimate services as C2 channels, as demonstrated by the Shai Hulud campaign's use of GitHub Discussions. For .NET-specific C2, Covenant provides an open-source .NET command-and-control framework with HTTP listeners, agent management, task execution, and a web UI—ready-made components for attackers who don't want to build their own infrastructure.

## Mitigation: The Swiss Cheese Model

Defending against supply chain attacks requires layered security, much like the Swiss Cheese model—every layer has holes, but stacked together they're effective. No single mitigation is enough, but combinations make attacks significantly harder. Key defenses include:

- **Package source mapping** to bind specific package patterns to specific sources - **Package signing and verification** to ensure only trusted packages are restored - **Lock files** (`RestorePackagesWithLockFile` + `RestoreLockedMode`) to catch unexpected version changes - **SBOM analysis tools** to identify known vulnerabilities in dependencies - **Rigorous PR review**, including AI-assisted code review tools - **Monitoring environment variable changes** with endpoint detection tools - **Inspecting source generator output** by setting `true`

## The Uncomfortable Reality

There is no 100% failsafe solution. A modern application pulls in hundreds of transitive dependencies, and nobody has time to audit every single one on every update. Mitigations add friction to workflows—lock files break restores and cause merge conflicts, source mapping requires maintenance, PR reviews take longer. Friction is the enemy of shipping software, so some teams will skip steps, and some attacks will get through.

Supply chain attacks will keep happening. The goal isn't perfection—it's making your project an expensive enough target that attackers move on to the next one. The .NET ecosystem has all the same vulnerabilities as npm, and the tools to exploit them are built into the framework itself. The question isn't whether your supply chain will be attacked; it's whether you've implemented enough layers of defense to make the attack fail. Stay vigilant, stay updated, and never assume your ecosystem is immune.