Reference
Multiple assemblies & renaming across them
Obfuscate interdependent assemblies together so cross-assembly references stay valid — including how to rename public members shared between your own assemblies.
If your product ships more than one assembly and they reference each other — an app plus its libraries, or a set of libraries — obfuscate them together in one run. Nebula then keeps every cross-assembly reference consistent.
Why obfuscate them together
When assembly A calls a public type or member in assembly B, renaming that member in B is
only safe if the reference in A is rewritten to the new name at the same time. Nebula can only
rewrite references in assemblies that are part of the same run — so list every interdependent
assembly in inputs:
{
"inputs": [
"bin/Release/MyApp.dll",
"bin/Release/MyApp.Core.dll",
"bin/Release/MyApp.Plugins.dll"
],
"outputDirectory": "bin/Release/obf"
}
What gets renamed by default
- Internal / private members are renamed — they aren’t visible outside the assembly, so this is always safe.
- Public API is preserved (
preservePublicApidefaults totrue), so code outside the run that references your assemblies keeps working. - Even with
preservePublicApi: false, Nebula auto-detects public members that a sibling input references and keeps those names stable — unless you explicitly enable cross-assembly rename (below). autoDetect(on by default) also preserves names used via reflection, serialization, DI, and XAML, and honours[Obfuscation].
Renaming public members shared between your assemblies
This is the important case for multi-assembly projects: you want to rename a public method or type in B that A calls. The project config needs three things:
{
"inputs": ["bin/Release/A.dll", "bin/Release/B.dll"], // EVERY caller must be listed here
"outputDirectory": "bin/Release/obf",
"preservePublicApi": false, // allow public members to be renamed
"crossAssemblyRename": true // rename shared public types AND rewrite the references in the siblings
}
With this in place:
- B’s public members are renamed.
- A’s references to them are rewritten to the new names, and A is obfuscated and written out too.
- You deploy the obfuscated A and B together — the original A would no longer resolve the renamed members.
The rule to remember
Every assembly that calls a public member you want renamed must be in inputs. If a caller is
not in the run, it will still reference the old name and break at load/JIT time. Therefore:
- You cannot rename public API that external or third-party code (which you can’t include in the run) depends on. Keep
preservePublicApi: truefor that surface, or carve it out withinclude/excludebelow. - The default (
preservePublicApi: true) exists precisely to protect those external callers — only internals get renamed.
Keeping specific public entry points stable
Even when renaming public members, some names must survive — a plugin interface, a public entry point, types created by reflection or DI, serialization contracts. Use:
exclude— force-preserve specific names. A type full name preserves the type and all its members;"Namespace.Type.Method"preserves a single member:"exclude": ["MyApp.Plugins.IPlugin", "MyApp.Api.PublicFacade"]include— an allowlist: when non-empty, only the listed types (and their members) are eligible for renaming and everything else is preserved. Handy to rename just your internal libraries while leaving a public SDK surface untouched.autoDetectalready preserves reflection/serialization/[Obfuscation]targets automatically — you rarely need to list those by hand.
Virtual, interface, generic, field, property and event members
Nebula preserves override / interface / generic consistency across assemblies. An overridden
method, an interface implementation, a generic type or method, and public fields, properties,
events and overloads are all renamed consistently, and every cross-assembly reference to them is
rewritten. This is covered by our test suite, which obfuscates two interdependent assemblies
together (with crossAssemblyRename) and runs the referring app to confirm every reference —
including all of those shapes — still resolves.
Strong-naming across the set
If you re-sign with your own key (strongNameKeyFile), Nebula updates each sibling’s assembly
reference to carry the new public-key token, so the whole set stays consistent and loadable.
Worked example
Two assemblies — SampleLibA.dll (a library) and SampleAppB.dll (an app that calls it):
{
"inputs": ["out/SampleLibA.dll", "out/SampleAppB.dll"],
"outputDirectory": "out/obf",
"preservePublicApi": false,
"crossAssemblyRename": true,
"controlFlowObfuscation": true,
"encryptStrings": true
}
nebula --config nebula.config.json
Both assemblies come out obfuscated; the app’s references to the now-renamed library members are
rewritten; you deploy both from out/obf.
Rule of thumb: include every assembly you control that references the renamed surface; keep
preservePublicApi: true(or useexclude) for any API that outside code depends on.