Skip to content

Nebula.NET

Protecting different project types

Recipes for every kind of .NET project — libraries & NuGet packages, WPF/WinForms, ASP.NET Core & Blazor, MAUI, console apps & services, Unity, single-file, F#/VB.

Nebula protects any managed .NET assembly, but the right settings depend on how your code is loaded and called. Find your project type below. The recurring rule: anything resolved by name at runtime (reflection, serialization, XAML/Razor binding) must be preserved — Nebula auto-detects most of it, and you can preserve the rest explicitly.

If your exact case isn’t listed, the closest one plus Keeping your app working and Troubleshooting will cover it — and you can always ask us.

Class library / SDK (consumed by others)

Your public API is your contract — keep it, obfuscate the internals.

{ "inputs": ["bin/Release/net8.0/MyLib.dll"], "outputDirectory": "protected",
  "preservePublicApi": true, "controlFlowObfuscation": true, "encryptStrings": true }
  • preservePublicApi: true keeps public/protected types and members named; everything internal/private is renamed.
  • Shipping several libraries that call each other? Obfuscate them together with crossAssemblyRename — see Multiple assemblies.

NuGet package (.nupkg)

Point Nebula straight at the package — it obfuscates the assemblies inside and repacks a protected .nupkg:

{ "inputs": ["bin/Release/MyLib.1.2.3.nupkg"], "outputDirectory": "protected", "preservePublicApi": true }

Console app / Windows service / daemon

No public surface to preserve, so protect everything for maximum strength:

{ "inputs": ["bin/Release/net8.0/MyApp.dll"], "outputDirectory": "protected",
  "preservePublicApi": false, "controlFlowObfuscation": true, "controlFlowIntensity": "aggressive",
  "encryptStrings": true }

The entry point is renamed safely — the runtime locates Main by metadata token, not by name.

WPF / WinForms (desktop, XAML)

XAML binds to view-model properties, commands, and event handlers by name at runtime, and the WinForms designer serializes control fields by name. Preserve what the UI layer touches:

  • Keep preservePublicApi: true (bound members are usually public).
  • Exclude the types that XAML binds to if any bound member isn’t public — e.g. view-models and their bound properties. See exclusions.
  • Don’t rename types referenced by name in XAML (x:Class, converters, {x:Type}); Nebula’s auto-detect catches most, but test the running app.

ASP.NET Core / Blazor / Web API

Model binding, routing, Razor/Blazor markup, and JSON serialization all resolve by name:

  • Controllers / minimal-API handlers / Razor Pages / Blazor components referenced in routing or markup — preserve (usually public; preservePublicApi: true).
  • DTOs / request & response models — their property names become JSON/XML fields, so preserve them (auto-detected when they carry System.Text.Json/Newtonsoft/DataContract attributes; otherwise exclude them).
  • Dependency injection is type-based (AddScoped<IFoo, Foo>()) and safe; convention/assembly-scanning by name needs the scanned types preserved.

Internals (business logic, algorithms) rename and control-flow freely — that’s where the value is.

.NET MAUI / Xamarin

Treat like WPF (XAML binding) plus platform interop: preserve XAML-bound view-models and any [DllImport]/platform-service names. [DllImport] entry points are preserved automatically.

Unity

Unity serializes public/[SerializeField] fields by name and can call methods via SendMessage("Name"). Preserve serialized fields and any message-target methods (annotate them — see below), and preserve MonoBehaviour subclasses referenced by name in scenes.

F# and VB.NET

Fully supported — Nebula works at the IL level, so language doesn’t matter. F# emits extra compiler-generated members; keep autoDetect: true (the default) and run your tests against the protected build.

Single-file & self-contained apps

Obfuscate the managed assemblies, then let the single-file publish bundle the protected copies (build → obfuscate → dotnet publish --no-build). Two notes:

  • Anti-tamper’s self-hash no-ops inside a single-file bundle (the assembly has no on-disk path), so rely on control-flow + string encryption there. For multi-file/self-contained-folder layouts, anti-tamper works normally.
  • Self-contained just adds the runtime; it doesn’t change how you obfuscate your assemblies.

Strong-named or Authenticode-signed output

Obfuscation rewrites the assembly, so sign after obfuscating — Nebula does it for you:

{ "strongNameKeyFile": "keys/mykey.snk",
  "authenticodeCertFile": "keys/cert.pfx", "authenticodeTimestampUrl": "http://timestamp.digicert.com" }

Strong naming is re-applied to the protected assembly; Authenticode signs the final output.

Licensing / anti-piracy code (like a license check)

This is a prime target — flatten the decision logic so it can’t be read or patched:

{ "controlFlowObfuscation": true, "controlFlowIntensity": "aggressive",
  "controlFlowInclude": ["MyApp.Licensing.LicenseManager.Validate", "MyApp.Licensing.LicenseManager.CheckEdition"],
  "encryptStrings": true }

Target the specific methods with controlFlowInclude, encrypt the strings that reveal branch conditions, and keep any type-identity self-checks ([Guid], Assembly.GetExecutingAssembly()) working — Nebula preserves [Guid] and assembly identity through obfuscation.