Skip to content

Reference

Configuration reference

Every field in nebula.config.json — types, defaults, and what it does.

Nebula is driven by a JSON config file (nebula.config.json) passed with --config. This is the complete list of fields. All are optional except inputs; unknown keys produce a warning rather than being silently ignored.

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

Editions. The Free edition does identifier renaming, up to 2 encrypted strings and up to 2 control-flow methods. Everything else (unlimited strings/control-flow, method & resource encryption, reference (proxy-call) obfuscation, anti-tamper, anti-debug, environment/tampering-tool detection, expiry guard, auto-protect licensing, metadata hardening, constant masking, aggressive naming, cross-assembly rename, signing, PDB output, plugins) is Pro; aggressive control-flow intensity, code virtualization and watermarking are Enterprise. See Editions.

Input & output

KeyTypeDefaultDescription
inputsstring[]— (required)Assembly path(s) to obfuscate. List all interdependent assemblies together for cross-consistent renaming — see Multiple assemblies.
outputDirectorystring<inputDir>/nebulatedWhere obfuscated assemblies, symbol maps and reports are written.
allowInPlaceboolfalseSafety guard: writing over the input assembly is refused unless this is true, so a mistyped path can’t destroy your source assembly.
copyRuntimeConfigbooltrueCopy each input’s *.runtimeconfig.json / *.deps.json next to the output so a framework-dependent app runs from the output folder.
verifyOutputbooltrueRe-check the transformed module for dangling references before writing and refuse to emit output that wouldn’t load. Leave on.
schemaVersionint1Config schema version, so older files get a clear upgrade warning.

Renaming

KeyTypeDefaultDescription
preservePublicApiboolfalseKeep public/protected type & member names; rename only internal/private. Turn on for a library referenced by code outside the run; off to rename everything eligible.
autoDetectbooltrueAutomatically preserve names required for correctness — reflection/serialization/DI/XAML targets and [Obfuscation]-marked members. Leave on.
includestring[][]Allowlist of type full names eligible for renaming. When non-empty, only these types (and their members) are renamed; everything else is preserved.
excludestring[][]Force-preserve names. A type full name preserves the type and all members; "Namespace.Type.Member" preserves one member.
excludeNamespacesstring[][]Force-preserve whole namespaces: any type whose namespace equals or starts with an entry (e.g. MyApp.Api) keeps its name and its members’.
renameResourcesboolfalseRename embedded resource names and update references (incl. type-associated .resources so ResourceManager still resolves).
aggressiveNamingboolfalseUse hard-to-read Unicode-confusable identifiers instead of short ASCII names. Still deterministic and collision-free.
crossAssemblyRenameboolfalseIn a multi-assembly run, rename types/members shared across the input assemblies and rewrite every cross-assembly reference. Requires all referencing assemblies to be inputs — see Multiple assemblies.

String encryption

KeyTypeDefaultDescription
encryptStringsboolfalseEncrypt string literals in IL and decrypt at runtime with a per-build key.
encryptStringsIncludestring[][]Allowlist of exact literals to encrypt. When non-empty, only these are encrypted. Discover them with nebula inspect --strings.
excludeStringsstring[][]Exact literals to leave un-encrypted (e.g. license text).

Control flow & code protection

KeyTypeDefaultDescription
controlFlowObfuscationboolfalseFlatten method control flow into a dispatcher state machine. Works for all method shapes, including try/catch/finally (the top level and each try body are flattened; nested handlers are kept atomic). Anything not provably safe to move is left intact.
controlFlowIntensitystring"normal"none / light / normal / aggressive — roughly 0 / 25 / 60 / 100 % of eligible methods when no explicit include list is given. aggressive is Enterprise-only; on a lower tier it downgrades to normal (and with requireLicensedEdition that downgrade fails the build).
controlFlowIncludestring[][]Allowlist of methods to flatten, by Namespace.Type.Method. When non-empty, only these are transformed. Discover names with nebula inspect --methods.
controlFlowExcludestring[][]Methods to skip, by Namespace.Type.Method.
obfuscateConstantsboolfalseMask integer literal loads with a per-build XOR key resolved at load time (decoded inline at runtime). This is lightweight masking, not cryptography — it breaks the plain literal and defeats naive constant scanning.
proxyReferencesboolfalsePaid. Route call/callvirt through injected, unbranded proxy methods so a decompiler / de4dot can’t read which external or BCL method a site calls. Conservative and behaviour-preserving (skips constructors, generics, value-type instance calls, etc.).
virtualizeMethodsboolfalseEnterprise. Compile eligible methods to a custom bytecode run by an embedded VM — the logic no longer exists as IL. See code virtualization.
virtualizeIncludestring[][]Allowlist of methods to virtualize by Namespace.Type.Method (empty = all eligible).
encryptMethodsboolfalseStrongest static-code protection: encrypt a method’s whole IL and re-emit it at call time via the Nebula.Runtime helper (copied next to the output), keeping the IL encrypted at rest in memory (anti-dump). Eligibility is deliberately narrow — it covers static, non-generic methods with no exception handlers and no by-ref/pointer parameters; anything else is left untouched. Note the trade-offs: once the CLR JIT-compiles a decrypted method its native code still resides in memory, and decrypting on each call adds per-invocation overhead, so target it at a few high-value methods rather than hot paths.
encryptResourcesboolfalseEncrypt embedded resources read via Assembly.GetManifestResourceStream(string) and decrypt on demand. (.resources for ResourceManager/WinForms/satellite are skipped.)

Anti-tamper & anti-debug

KeyTypeDefaultDescription
antiTamperboolfalseInject a load-time integrity check (SHA-256 of the written image) that reacts if the file was modified after obfuscation. Stops casual patching.
antiTamperReactionstring"terminate"On mismatch: terminate (FailFast), throw, exit, or callback.
antiTamperCallbackstringnullNamespace.Type.Method of a static, parameterless method to call when reaction is callback (must be preserved so it resolves).
antiDebugboolfalseInject a managed-debugger detection check that runs on module load. Off by default so it never hinders your own QA builds.
antiDebugReactionstring"throw"On a detected debugger: throw, terminate, exit, or callback.
antiDebugCallbackstringnullCallback method for the anti-debug callback reaction.
environmentChecksboolfalseInject a load-time check that reacts when running under a CLR profiler (CORECLR_ENABLE_PROFILING / COR_ENABLE_PROFILING) or a known runtime-patching framework — Harmony or MonoMod.RuntimeDetour (Detour/Hook/NativeDetour). Detection is by loaded managed type; a purely native inline hook or raw DLL injection is out of scope. Off by default so it never hinders your own profiling/QA.
environmentChecksReactionstring"throw"On detection: throw, terminate, exit, or callback.
environmentChecksCallbackstringnullCallback method for the environment-check callback reaction.
environmentChecksContinuousboolfalseKeep watching beyond load time (requires environmentChecks): subscribe to AppDomain.AssemblyLoad so a patcher attaching after startup is caught when it loads, and re-run the check at the entry of the methods that call your licensing SDK (the same ones autoProtectLicensing finds, via licensingNamespaces) so a late-attached debugger/patcher is caught at the gate. Reuses the same reaction.
environmentChecksIntervalSecondsint0With continuous mode, also re-check on a background timer every N seconds (0 = no timer; the AssemblyLoad hook and call-site re-checks still apply). The first fire is jittered per build so the cadence isn’t trivially identifiable.
autoProtectLicensingboolfalseAuto-target the methods that call your licensing SDK for the strongest protection the edition applies: force-include them in virtualization (if enabled) and control-flow flattening, and never exclude them — no need to name the gate methods. Paid feature.
licensingNamespacesstring[]["Keyright"]Root namespaces autoProtectLicensing treats as the licensing SDK. Defaults to Keyright; add any other licensing SDK’s root namespace.
expiryUtcdatenullExpiry / “time-bomb”: a UTC instant after which the protected build stops working (for betas, evaluations, embargoed releases). A load-time guard reacts once the date passes. Checked against the machine clock, so pair with antiTamper.
expiryReactionstring"throw"On expiry: throw, terminate, exit, or callback.
expiryCallbackstringnullCallback method for the expiry callback reaction.
tamperReportUrlstringnullBuilt-in tamper telemetry (“report-home”): when set, a guard that fires (anti-tamper, anti-debug, environment checks, expiry) also POSTs a minimal {"event":"…"} to this URL — your own endpoint (never Delta1 Labs) before its reaction, so you get telemetry without writing a callback. The send is bounded and self-swallowing — it can’t block or crash the app. Only the event name is sent (no source, assembly contents or personal data). Entirely opt-in: leave it unset and nothing is ever sent. It is your telemetry to your endpoint, so it is independent of NEBULA_NO_TELEMETRY — that variable only silences Nebula’s own anonymous tool analytics (a separate build-time channel) and has no effect on report-home. Paid feature; adds outbound traffic from protected apps (not for air-gapped).
expiryMessagestringnullOptional custom message surfaced by the expiry guard.

antiDebug, environmentChecks and expiryUtc are paid-edition features (dropped in the Free edition). antiDebug and expiryUtc are load-time guards; environmentChecks is load-time by default but can run continuously (environmentChecksContinuous) to also catch a patcher/debugger attached after startup. All of them deter tampering rather than defeating a fully determined attacker, so combine them with antiTamper and control-flow/string protection.

Metadata & symbols

KeyTypeDefaultDescription
metadataHardeningboolfalseStrip compile-time-only metadata decompilers use but the CLR doesn’t need (nullable annotation attributes, etc.), preserving runtime-critical metadata.
pdbModestring"none"none discards debug symbols (max protection); obfuscated emits a PDB matching the obfuscated IL for your private crash analysis.

Signing

KeyTypeDefaultDescription
strongNameKeyFilestringnullPath to a .snk to re-sign the output (renaming invalidates any existing strong name). Relative paths resolve against the config file.
strongNameKeyEnvVarstringnullName of an env var holding a base64-encoded .snk, so keys need not live in the config. Takes precedence over strongNameKeyFile.
delaySignboolfalseDelay-sign only (embed the public key, leave room for a later signature).
authenticodeCertFilestringnull.pfx to Authenticode-sign the output (after strong-name signing). Requires signtool / Windows SDK.
authenticodeCertPasswordstringnullPassword for the .pfx — prefer supplying via an env var over inline text.
authenticodeCertThumbprintstringnullSHA-1 thumbprint of a cert in the Windows store to sign with (alternative to a .pfx).
authenticodeTimestampUrlstringnullOptional RFC-3161 timestamp URL for Authenticode signing.

Licensing, incremental & extensibility

KeyTypeDefaultDescription
licenseFilestringnullPath to a license file. When omitted, the NEBULA_LICENSE env var and default locations are checked. Without a valid license, the Free edition runs.
requireLicensedEditionboolfalseFail the run if the resolved license can’t apply the requested protection — it resolved to Free (missing / revoked / lapsed license), or a Pro license had an Enterprise-only feature (e.g. aggressive control flow, watermarking) downgraded. Stops a build server from silently shipping weakly-obfuscated output; recommended for CI. See licensing.
seedMapFilestringnullA previous run’s .symbols.json for incremental/seeded obfuscation: unchanged members reuse their previous obfuscated names; only added/changed members get new names.
pluginsstring[][]Paths to plugin assemblies providing extra IObfuscationPass implementations. Each is appended to the pipeline.
watermarkstringnullEnterprise. A per-customer identifier embedded (obfuscated) into the output so a leaked binary traces back to the customer. Read it with nebula watermark --read.

A fuller example

{
  "inputs": ["out/MyApp.dll", "out/MyApp.Core.dll"],
  "outputDirectory": "out/obf",
  "preservePublicApi": false,
  "crossAssemblyRename": true,
  "encryptStrings": true,
  "controlFlowObfuscation": true,
  "controlFlowIntensity": "normal",
  "obfuscateConstants": true,
  "antiTamper": true,
  "metadataHardening": true,
  "strongNameKeyEnvVar": "MYAPP_SNK_BASE64",
  "pdbMode": "none"
}

See also: CLI reference · MSBuild & CI · Multiple assemblies.