.NET IL trimming is a build-time optimization that analyzes your application and removes unused code, producing smaller binaries with faster startup times. For containerized deployments and serverless functions, the size reduction is significant. But trimming operates on static analysis, and static analysis cannot always determine what code is actually needed at runtime.
When trimming removes code that handles security validation, authentication checks, or error handling, the application does not crash. It just silently skips the security logic. That makes trimming-related security bugs some of the hardest to detect.
How Trimming Works
The .NET trimmer performs a reachability analysis starting from your application entry points. It follows method calls, type references, and metadata to build a graph of everything that is reachable. Anything not in the graph is removed from the final assembly.
There are three trimming modes: partial (the default in .NET 7), full, and the newer copyused mode. Full trimming is the most aggressive, removing everything that is not provably reachable. Partial trimming only trims assemblies that have opted into trimming via the [TrimmingSupported] attribute.
The critical issue is reflection. When your code loads types or calls methods dynamically (through Type.GetType(), Activator.CreateInstance(), or dependency injection frameworks), the trimmer cannot see those references in its static analysis. It removes the types, and at runtime, the reflection call fails or returns null.
Security-Relevant Trimming Scenarios
Authentication and Authorization Middleware
ASP.NET Core uses a middleware pipeline where handlers are often resolved through dependency injection. If a custom authentication handler is registered by type name but never directly referenced in code, the trimmer may remove it. The application still starts and serves requests -- it just skips the authentication step.
This is not hypothetical. The .NET team has documented cases where trimming broke authentication middleware in unexpected ways. The application appeared to work correctly in testing (because tests often bypass authentication) but was wide open in production.
Input Validation Libraries
Libraries like FluentValidation or DataAnnotations validators use reflection extensively. Trimming can remove validator classes that are registered but not directly referenced. The result is that input validation silently stops working for certain request types.
An API endpoint that should reject malformed input starts accepting it. The application does not throw errors -- the validators simply are not there to run.
Cryptographic Provider Resolution
.NET cryptographic APIs resolve providers dynamically. The CryptoConfig class maps algorithm names to implementation types. If the trimmer removes an implementation class that is only referenced through configuration, the cryptographic operation may fall back to a default provider or fail in ways that are not immediately obvious.
Serialization and Deserialization
JSON and XML serializers use reflection to discover type properties. Trimming can remove types that are only used in deserialization, causing the deserializer to silently skip fields. If those fields contain security-relevant data (like role claims or permission flags), the deserialization produces an object with default values instead of the actual data.
Protecting Against Trimming Bugs
Use trim warnings. Starting with .NET 6, the compiler emits warnings when it detects patterns that are incompatible with trimming. Enable <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings> and treat these warnings as errors in your build pipeline.
Mark security-critical types with [DynamicallyAccessedMembers]. This attribute tells the trimmer that a type is used through reflection and should not be removed. Apply it generously to authentication handlers, validators, and serialization models.
Use source generators instead of reflection. .NET System.Text.Json source generators produce serialization code at compile time rather than using runtime reflection. This makes the code visible to the trimmer and prevents types from being removed.
Test with trimming enabled. Do not wait until deployment to discover trimming issues. Run your full test suite (including integration tests that exercise authentication and authorization) against the trimmed binary.
Create a trim roots file. The TrimmerRootAssembly and TrimmerRootDescriptor MSBuild properties let you explicitly preserve types and assemblies from trimming. Use these for security-critical components that you know are accessed through reflection.
Audit your trimmed binary. Compare the trimmed and untrimmed binaries to see exactly what was removed. Pay special attention to security-related assemblies and types. Tools like ILSpy or dotnet-dump can help with this analysis.
The NativeAOT Dimension
.NET NativeAOT compilation takes trimming further by compiling the application to native code ahead of time. This provides even more aggressive dead code elimination and eliminates the JIT compiler entirely.
NativeAOT has the same trimming risks, amplified. Because there is no JIT compiler, runtime code generation (used by some serializers and ORM frameworks) is impossible. This forces developers to use source generators or explicit type registration, which is actually better from a security perspective because it makes dependencies explicit.
However, NativeAOT also breaks some security-relevant features like runtime assembly loading (used by plugin systems) and certain cryptographic provider resolution patterns. If your security architecture depends on these features, NativeAOT requires careful evaluation.
How Safeguard.sh Helps
Safeguard.sh analyzes your .NET application dependencies and identifies components that are known to be trim-sensitive. Our platform flags security-relevant libraries that use reflection patterns incompatible with trimming, helping you identify potential security gaps before they reach production. Combined with our SBOM generation, you get visibility into exactly which components are present in your trimmed binary and which have been removed.