See what a .NET assembly depends on and calls with Glass.NET
Before you ship a third-party DLL, you want to know its external surface: which assemblies and packages it references, what framework it targets, which native libraries it P/Invokes, and where it reaches out to the network, the file system or another process. Here is how to audit all of that statically in Glass.NET — the free decompiler that reads the binary and never runs it.
You are about to ship a third-party DLL inside your own product, and you want one thing settled first: what does it actually reach out to? Not what the README claims — what the binary is wired to do. Which other assemblies and packages it pulls in, what framework it expects to run on, whether it calls into native code, and whether it opens a socket, writes a file or launches a process anywhere in its body. That is a dependency’s external surface, and it is exactly the thing you cannot see from the package page.
You can see all of it without running the DLL. A compiled .NET assembly records its dependencies and its calls in metadata and IL, and Glass.NET reads that and reconstructs readable C#. It is free, and it is strictly static — it reads metadata and IL and reconstructs C#, and never executes the assembly it inspects. That is the whole reason this is safe: auditing a dependency you are unsure about means reading it, not running it, and reading is all Glass ever does.
Let’s make it concrete. Say you are evaluating Contoso.ImageKit — a NuGet package that resizes images — before adding it to a service that handles customer uploads. A pure image library should reference an imaging stack and nothing alarming, target a framework you can run, and keep to pixels. If it references System.Net.Http, P/Invokes a native codec you did not expect, or has a Process.Start tucked in a corner, you want to know before it ships, not after. Here is the workflow.
Open it and read the Assembly Information first
Find the DLL. For a NuGet dependency it is in the global packages cache — %USERPROFILE%\.nuget\packages\<id>\<version>\lib\<tfm>\ on Windows, ~/.nuget/packages/... elsewhere. For a vendor drop it is wherever it landed, usually bin/. Open it in Glass.NET by dragging it onto the window, or File ▸ Open Assembly. A plain managed .dll is enough — no PDB, no source, no restore.
Before you read a line of code, open the Assembly Information view. This is where the external surface starts, and it answers two questions immediately:
- What does it target? The view shows the target framework and the product attributes. A reference compiled against a framework you do not run — a
net48-only assembly dropping into anet8.0service with no compatible build in the package — is a compat mismatch you want to catch here, at audit time, not as a load failure in production. The target framework is a fact recorded in the assembly, so it is the authoritative answer, not a guess from the package name. - What does it reference? The view lists the referenced assemblies — the direct dependency set the binary was built against. For an image library you expect an imaging stack and the base class libraries. If
System.Net.Http,System.Net.SocketsorSystem.Diagnostics.Processshow up in that list, a library that claims to only touch pixels has just told you it is linked against the network or the process API, and you have learned it before reading any code.
Glass’s exposure report is a fast companion to this. Run it to gauge how readable the assembly is. An ordinary open-source-style package should read clearly; if an unremarkable image helper comes back heavily obfuscated, that mismatch is itself worth questioning — legitimate utility libraries rarely need to hide. (That is the view from the other side of the sibling obfuscator Nebula.NET, whose code virtualization is a Pro feature — but an ordinary dependency has no reason to look like that.)
Browse the references and type tree
The referenced-assemblies list tells you what the binary links against; the tree tells you how much of it is yours to worry about. Expand the assembly into its references node and its namespaces ▸ types ▸ members tree. The references node is the same dependency set in browsable form — and in Glass you can keep going, opening a referenced assembly to inspect its surface in turn, which is how you follow a dependency-of-a-dependency that the package page never mentioned.
The type tree gives you the shape of the code itself: three tidy types or a sprawl of a hundred, a small public API over a handful of internal helpers or something far busier than a resizer should need. You are reading for proportion here — does the surface match the stated job?
Find the call sites that reach out
Now the real audit: where does the code actually call out of itself? This is where a navigation-first decompiler turns a checklist into a few jumps rather than a scroll.
The method is the same for each capability you care about: find the API, then use Find All References (Shift+F12) to list every call site, streaming into the Find Results panel, and read the method around each one.
- Network. Look for
HttpClient,WebClient, rawSocket/TcpClient, and any hardcoded URL or address. Why would a resizer open a socket? Find All References on the constructor orSendAsyncshows you every place it happens and lets you read the surrounding code. - Process spawns.
Process.Start, or anything shelling out tocmd,powershellor a bundled binary. A pure library rarely needs to launch anything. - File system.
File,FileStream,Directory— reads and writes beyond the temp scratch a resizer legitimately needs, especially reads of paths that look like credentials or config. - Native libraries. This is the P/Invoke surface. A
[DllImport]or source-generated[LibraryImport]method names the native module it binds to right there in the attribute, so it decompiles cleanly and the native dependency is visible in the source. An image library binding a native codec may be entirely expected — but an unexpected[DllImport("kernel32")]doing process or memory work is the kind of thing you audit precisely to catch. For the terminology and why the entry-point name is load-bearing, see obfuscating P/Invoke and DllImport without breaking interop.
Use Go To All (Ctrl+T) to jump to a type or member by name, and Go to Definition (F12) to follow a call into the helper that does the real work. The call hierarchy answers the question that matters most for an audit: is this reachable from the API I actually use, or is it dead code? A Process.Start that nothing on your call path reaches is a very different finding from one your first method hits.
One thing to keep straight: finding a call to HttpClient or Process.Start is static evidence that the call exists in the code and is reachable. It is not Glass watching a request go out — Glass never runs the assembly. That distinction is exactly why this is safe to do on a dependency you do not trust: you are reading the wiring, not energising it. The same discipline underpins safely analysing a suspicious .NET assembly and inspecting a NuGet package before you trust it.
Drop to IL where you need certainty
Most of the time the decompiled C# at a call site is clear enough to judge. Occasionally it is not — a call routed through reflection, or an obfuscated method where the C# reconstruction is patchy. When the high-level view rounds off a detail that matters, switch the same member to the IL view. The IL is what actually runs, so it is the ground truth, and it stays readable even where clean C# does not reconstruct. (Reading IL walks through what you are looking at.) You do not need to live there — reach for it on the handful of call sites where you need to be sure what the code reaches.
The point
A dependency’s external surface is not hidden — it is recorded in the binary, and a static reader surfaces all of it. Open the DLL, read the Assembly Information for its target framework and referenced assemblies, browse the references and type tree for proportion, and use Find All References to land on every network, file, process and P/Invoke call site so you can judge each in context. In a few minutes you can confirm that Contoso.ImageKit only talks to what an image library should — or find the one call that means it does not. And because Glass.NET is free and strictly static, you did the whole audit without running a single line of a dependency you had not yet decided to trust. For the companion workflow of reading what the code does, see understanding unfamiliar third-party .NET code. Download Glass.NET, open the DLL you are about to ship, and see what it reaches for.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.