Open Source Security

.NET Source Generator Security Risks

Source generators are C# code that executes during compilation with developer privileges. The .NET equivalent of Rust's proc macros — and the same underexamined attack surface.

Nayan Dey
Senior Security Engineer
6 min read

Roslyn source generators shipped with .NET 5 in November 2020 and became the default way to generate code at compile time in modern C#. They power System.Text.Json source generation, CommunityToolkit.Mvvm boilerplate elimination, logging source generators in Microsoft.Extensions.Logging, and a growing list of domain-specific generators across the ecosystem. What they are, mechanically, is C# code that the compiler loads and runs during the build, with the privileges of the developer or build agent. This is the same model as Rust procedural macros, and it carries the same underexamined attack surface. A source generator can read your filesystem, make network calls, and emit code into your final assembly. If that description makes you pause, the pause is warranted.

What does a source generator actually execute?

During compilation, Roslyn scans referenced analyzer assemblies for classes implementing ISourceGenerator or IIncrementalGenerator. Each such class gets its Execute (or Initialize) method called with a context that exposes the compilation's syntax trees, semantic model, and various hooks for adding generated source files. Whatever C# the generator emits becomes part of the compilation.

The generator runs in the compiler process. It can use any API available to a normal .NET assembly: System.IO for filesystem access, System.Net.Http for network calls, System.Diagnostics.Process to spawn child processes. There is no sandbox. A compromised or malicious source generator has full access to the build machine's filesystem, network, and environment.

How is this different from an analyzer?

Analyzers are the older cousin of source generators (both use the Roslyn analyzer infrastructure). Analyzers also run at compile time with full privileges. The distinction is behavioural: analyzers report diagnostics, source generators emit code. From a supply chain perspective, both are privileged build-time code and both deserve the same scrutiny. In practice, source generators have gotten more attention from attackers' mental models because emitting code is a more interesting capability than emitting warnings.

What can a malicious source generator do?

The same catalog as any privileged build-time code:

  • Exfiltrate source code and build artefacts to an attacker endpoint.
  • Exfiltrate environment variables including CI tokens, cloud credentials, deployment keys.
  • Inject backdoored C# into your compiled assembly — authentication bypasses, telemetry, subtle logic flaws.
  • Modify filesystem state outside the project: SSH keys, profile scripts, git hooks.
  • Phone home with fingerprint data to enable targeted second-stage attacks.

The injection-into-assembly pattern is the most interesting of these, because unlike the others, the attack's payload lives in production code that ships to users. A well-crafted malicious source generator could emit code that only activates under specific conditions, making detection after the fact extremely difficult.

Real-world incidents

The public incident record is sparse. Source generators are still young enough (four years in production use) that large-scale attacks have not yet publicly materialised. What does exist:

  • Proof-of-concept malicious generators have been demonstrated at multiple security conferences.
  • Typosquatting against popular generator packages has been observed on NuGet, in the same pattern that affects ordinary NuGet packages.
  • Compromised contributor scenarios — the XZ Utils incident, while not .NET-specific, demonstrates the social-engineering path that would apply equally to popular source generator maintainers.

The absence of published incidents should not be mistaken for evidence of safety. The same absence existed for npm install scripts in 2017 and for Rust proc macros in 2022.

How do you tell if a NuGet package ships a source generator?

The package includes an analyzers/ folder with DLLs targeting roslyn4.x or similar. In a .csproj, referenced packages that ship generators are listed normally — there is no dedicated "this package has a generator" attribute. You can spot them by inspecting the package contents with dotnet nuget locals or looking in ~/.nuget/packages/<packagename>/<version>/analyzers/.

Two practical checks:

# Find all analyzer/generator DLLs loaded in your solution's package cache
find ~/.nuget/packages -path "*/analyzers/*.dll" | sort -u

# For a specific package, inspect what it ships
unzip -l ~/.nuget/packages/mypackage/1.0.0/mypackage.1.0.0.nupkg | grep -i analyzer

A source generator DLL is approximately a few hundred kilobytes of compiled C# with references to Microsoft.CodeAnalysis. Knowing which of your dependencies ship one is a precondition for any meaningful audit.

What audit looks like in practice

Realistically, nobody has time to decompile every analyzer DLL in their dependency tree on every update. The practical audit posture:

Maintain a list of trusted source generators. First-party Microsoft packages (System.Text.Json, logging, regex generators) and widely-used community packages (CommunityToolkit.Mvvm) have enough scrutiny that they can be accepted without per-version audit.

Gate additions. A new source generator added to Directory.Packages.props is a security-relevant change. Treat it like adding a build.sh or a postinstall script — with skepticism and reviewer signoff.

Pin versions tightly. Patch updates to source generator packages are code-execution updates. The FloatingVersionUpdatePolicy should not float source generator versions without review.

Use analyzer packaging metadata. NuGet has signed analyzer packaging. Verifying signatures on analyzer packages raises the bar for supply chain attacks.

Build isolation is the highest-leverage mitigation

The most impactful defense is a build environment that has minimal privileges during compilation. If the CI agent compiling your C# does not have access to production credentials, cloud deployment tokens, or cross-project filesystem state, the worst-case source generator compromise is contained to the build artefact itself — still bad, but not catastrophic.

Concrete: use separate build and deploy stages. Build stage runs with no deployment credentials. Deploy stage runs outside the compilation context with scoped credentials. Most organisations say they do this; fewer actually do.

A practical checklist

  • Identify source generators in your dependency tree.
  • Maintain a trusted/review-required distinction.
  • Gate additions with reviewer signoff.
  • Pin versions, including patches.
  • Verify analyzer package signatures where available.
  • Ensure CI build stage has minimum-necessary credentials.
  • Budget quarterly audits of your source generator dependency set.

How Safeguard Helps

Safeguard identifies source generator and analyzer packages specifically in the .NET dependency graph and flags them as privileged dependencies in the reachability and policy views. Policy gates can require explicit audit attestation before a new source generator package is allowed to land in a PR, and Griffin AI summarises source-generator-specific changes across version updates so the review surface is the compiled generator code that actually runs at build time. For .NET teams treating source generators as ordinary dependencies, Safeguard surfaces the elevated risk that their current tooling is likely underweighting.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.