To inspect a NuGet package before you trust it, decompile its DLLs back to C# and read what the code actually does. A NuGet package ships compiled assemblies, not the source you browse on GitHub — and the binary you install is not guaranteed to match any public repo — so decompiling is the only way to audit the exact code you're about to run in your build and your product. A free decompiler such as Glass.NET turns this into a few-minute review.
Here's why that matters, how to do it, and what to look for.
Why you can't fully trust a compiled dependency
When you dotnet add package, you're pulling a compiled artifact from a feed and giving it the same privileges as the rest of your application. A few things make that a genuine trust decision rather than a formality:
-
The binary isn't the repo. The GitHub source might be clean while the published
.nupkgwas built from something else. Unless a package is verifiably built from source (deterministic, reproducible builds are still the exception), the DLL is the source of truth for what runs — and it's opaque until you look inside it. - Supply-chain attacks target this gap. Typosquatted package names, a compromised maintainer account, or a malicious "utility" dependency-of-a-dependency are all well-worn techniques. The malicious code is usually a small addition buried in an otherwise-useful library.
-
Packages can run code at build time. Beyond the runtime assembly, a package can ship MSBuild
.targets/.propsthat execute during your build, and classic packages could include PowerShell (install.ps1/init.ps1). That's execution on your machine, with your credentials, before your app ever runs.
None of this means NuGet is unsafe — it means "popular and green checkmark" isn't the same as "I read what it does." For a sensitive dependency, reading it is cheap insurance.
How to decompile a NuGet DLL back to C
A .nupkg is just a ZIP archive, and the assemblies inside decompile like any other .NET DLL — because a managed assembly carries a near-complete blueprint of its source in IL and metadata (the same reason your own code decompiles so cleanly).
1. Get the package on disk
Either download the .nupkg from nuget.org, or let a restore fetch it and look in the global packages cache:
-
Windows:
%USERPROFILE%\.nuget\packages\<id>\<version>\ -
Linux / macOS:
~/.nuget/packages/<id>/<version>/
The assemblies are under lib/<target-framework>/. (You can also rename the .nupkg to .zip and extract it to see the .targets/.props and any scripts too.)
2. Open the assembly in a decompiler
Open the DLL from lib/ in Glass.NET (drag it onto the window, or File ▸ Open Assembly). It reconstructs readable C# on demand and lists the assembly as a namespaces ▸ types ▸ members tree — no source, PDB or special build required.
3. Read it, or export and grep it
For a quick look, browse the types and read the C#. For a systematic audit, export to a buildable project (glass export MyPackage.dll --output ./src on the CLI) and then grep the reconstructed source for the specific APIs that matter — which brings us to the checklist.
What to look for: the red flags
You're reading with a question in mind: does this library do anything a library of its stated purpose has no reason to do? Concretely:
-
Network calls.
HttpClient,WebClient, rawSocket/TcpClient, DNS lookups, and especially hardcoded URLs or IP addresses. Why is a date-formatting helper phoning home? Watch for telemetry beacons that exfiltrate more than they admit. -
Process spawns.
Process.Start, or anything shelling out tocmd,powershell,bash, or a downloaded binary. A pure library rarely needs to launch processes. -
Reflection used to hide execution.
Assembly.Load(byte[])from an embedded resource or a downloaded blob,Activator.CreateInstance/Type.GetTypedriven by strings, orMethodInfo.Invokechains. Loading and running code that isn't visible in the assembly is a classic staging technique. -
Credential and environment access. Reads of environment variables, token files,
~/.aws,.npmrc, browser stores, or the Windows registry — anything that looks like it's harvesting secrets. - Build-time hooks. MSBuild targets that run tasks, or install scripts, that do any of the above during restore/build rather than at runtime.
- Obfuscated or virtualized code. This is the big one for auditing. If an ordinary, open-source-looking package turns out to have renamed identifiers, flattened control flow, encrypted strings, or a bytecode VM, ask why. Legitimate OSS libraries are almost never obfuscated — obfuscation in a dependency you were told is "just a helper" is a reason to stop and scrutinize, because it's specifically there to stop you doing what you're doing now. (Commercial components are sometimes lightly protected; a free utility from an unknown author is a different story.)
The combination is what's damning: reflection + string decryption + dynamic assembly loading + a hardcoded endpoint in the same package is a textbook malicious-loader shape, even when each piece could be innocent alone.
How a free decompiler makes the review fast
Reading a large assembly by scrolling is slow; a good decompiler turns the checklist above into navigation:
-
Trace a suspicious API. Find a call to
Process.StartorHttpClientand use Find All References and the call hierarchy to see who invokes it and with what — is it reachable from your usage, or dead code? - Jump around by name. Go To All and Go to Definition let you move through an unfamiliar assembly the way you move through your own source.
-
Export and grep. Export to a project and
grepforProcess.Start,Assembly.Load,Socket,Environment.GetEnvironmentVariableacross the whole codebase in one pass. - Diff two versions. Glass's assembly compare shows a line diff between versions — the fastest way to answer "did this innocuous-looking patch bump quietly add a network call?" Review the delta, not the whole library, on every upgrade.
-
Check authenticity and inventory. Glass includes a NuGet package authenticity check to confirm a package matches what its author published, and SBOM export (
glass sbom) to record exactly what a dependency pulls in.
All of that is in a free tool — no licence, no sign-up — so a package review is a coffee-length task, not a project.
Know the limits
Decompilation is powerful but not omniscient. Heavily obfuscated code may not reconstruct into readable C# — which, again, is itself a signal. Static reading won't catch behaviour that only manifests at runtime with specific inputs. And you're auditing one version: pin your dependencies, re-review on upgrade (that's what the diff is for), and pair code review with provenance signals — signed packages, publisher reputation, download history and reproducible builds. Reading the code is the strongest of those signals, not the only one.
One legal note: analyze packages you have the right to. Auditing a dependency you're about to ship in your own product is the mainstream, legitimate case — but some licences restrict reverse engineering, so check before you dig into something you don't otherwise have rights to.
Try it
Pick a dependency you rely on but have never actually read, and open its DLL in a decompiler. It's a fast, sobering exercise — and occasionally a useful one.
Download Glass.NET — it's a free .NET decompiler built on the same proven ICSharpCode.Decompiler engine as ILSpy, with assembly diff, SBOM export and NuGet authenticity checking built in. More on the Glass.NET product page.
Top comments (0)