Skip to content

Reference

MSBuild & CI

Protect your assemblies automatically on every build and in your CI pipeline.

For production, protect automatically rather than by hand. Nebula fits into both MSBuild and any CI system.

Nebula obfuscates your compiled assemblies, not your source. That means there is no source-code dependency: no attributes, no using, no API calls. Your code stays completely Nebula-free; protection is applied to the build output.

Two ways to integrate

  1. The MSBuild package (Delta1Labs.Nebula.MSBuild) — add it to the project and protection runs as part of dotnet build / msbuild, driven by MSBuild properties. Cleanest when you want build-time protection wired into the project; it’s a build-only package (PrivateAssets="all"), so nothing couples to your source or ships as a runtime dependency.
  2. Shell out to the nebula CLI — no package reference at all; a small Directory.Build.targets invokes the CLI on the build output. Best when you want zero project coupling and the CLI is already on the agent.

Both gate on a build-agent-only signal so developer builds stay unobfuscated, and both require a license on the build machine (build integration is a licensed feature).

The MSBuild package

Add the build-only package to the project you ship:

<PackageReference Include="Delta1Labs.Nebula.MSBuild" Version="1.1.4" PrivateAssets="all" />

Turn it on with MSBuild properties — gate NebulaObfuscate on a variable that exists only on the build agent so developer builds are untouched:

<PropertyGroup Condition="'$(NEBULA_OBFUSCATE)' == '1'">
  <NebulaObfuscate>true</NebulaObfuscate>
  <NebulaConfigFile>$(MSBuildProjectDirectory)\nebula.config.json</NebulaConfigFile>
</PropertyGroup>

On dotnet build (Core MSBuild) the package runs the engine in-process via its bundled task; under Visual Studio / msbuild.exe it automatically shells out to the nebula CLI instead (set NebulaToolPath if nebula isn’t on PATH). Every target framework of a multi-targeted project is protected into its own per-TFM nebula output folder automatically — you don’t list the frameworks.

Key properties:

PropertyPurpose
NebulaObfuscatetrue to run on build. Gate it on a build-agent-only variable.
NebulaConfigFilePath to your nebula.config.json (required under Visual Studio / msbuild.exe).
NebulaOutputDirectoryOutput folder (default $(TargetDir)nebula).
NebulaPreservePublicApiKeep public-API names (default true).
NebulaLicenseFilePath to an offline signed license file for the build agent.
NebulaToolPathThe nebula CLI to use under Visual Studio / msbuild.exe (default nebula).
NebulaBlazorObfuscatetrue for Blazor WebAssembly projects — see Protecting Blazor WebAssembly.

The package runs cross-platform (Windows, Linux, macOS) wherever .NET 8+ is installed — see Linux, macOS & CI. Licensing applies to the build machine only — see Licensing only the build server.

Obfuscate only on the build server

A common requirement: developer builds stay unobfuscated; only the build that produces your official, shippable packages is protected. Because MSBuild automatically exposes environment variables as build properties, you gate obfuscation on a variable that exists only on the build agent — same source, same dotnet build, no flags, no code changes.

Build integration is a licensed feature. The Free edition is the CLI and desktop app only; obfuscating as part of a build requires a license on the build machine.

Two ways to license a build machine (don’t confuse them):

  • Online key — run nebula register --key LIC-… once on the agent. Best for a persistent build machine (it uses one seat for that machine).
  • Offline signed license file — set NEBULA_LICENSE (or licenseFile in the config) to the path of a signed .license file. Best for ephemeral CI runners, since it needs no online seat activation. NEBULA_LICENSE is a file path, not a key — a LIC-… string there won’t resolve.

If you prefer no package reference at all, run the nebula CLI from a build-infra file, gated by a build-agent-only variable. The CLI ships with the Nebula install (the download) or as the dotnet tool, and nothing in your .csproj or C# references Nebula. Check a small Directory.Build.targets into the repo that invokes the CLI on the build output:

<!-- Directory.Build.targets at the repo root -->
<Project>
  <Target Name="NebulaObfuscate" AfterTargets="Build"
          Condition="'$(NEBULA_OBFUSCATE)' == '1' and '$(TargetFramework)' != ''">
    <Exec Command="nebula --config &quot;$(MSBuildThisFileDirectory)nebula.config.json&quot; --input &quot;$(TargetPath)&quot; --output &quot;$(TargetDir)obf&quot;" />
  </Target>
</Project>

On the build agent set NEBULA_OBFUSCATE=1 (plus a license — see below) and make sure nebula is on PATH. Developer boxes — where the variable isn’t set — skip the step, build unobfuscated, and don’t need Nebula installed. The --input switch lets one shared settings-only config (transforms but no inputs) drive the run while the build supplies each assembly; the '$(TargetFramework)' != '' guard makes it run once per target framework on multi-targeted projects (and skip the aggregate build). Works under both dotnet build and Visual Studio / msbuild.exe.

Setting the variables on the build agent

Set these once on the build machine, as the account your builds run under, using whichever shell your agent uses.

Windows — Command Prompt (cmd):

:: Persistent (applies to future build sessions):
setx NEBULA_OBFUSCATE 1
setx NEBULA_LICENSE "C:\keys\nebula-license.json"

:: Just the current session:
set NEBULA_OBFUSCATE=1
set NEBULA_LICENSE=C:\keys\nebula-license.json

Windows — PowerShell:

# Persistent, machine-wide (run PowerShell as Administrator):
[Environment]::SetEnvironmentVariable('NEBULA_OBFUSCATE', '1', 'Machine')
[Environment]::SetEnvironmentVariable('NEBULA_LICENSE', 'C:\keys\nebula-license.json', 'Machine')

# Just the current session:
$env:NEBULA_OBFUSCATE = '1'
$env:NEBULA_LICENSE   = 'C:\keys\nebula-license.json'

Linux / macOS build agents (bash):

# Add to the agent's service environment or profile:
export NEBULA_OBFUSCATE=1
export NEBULA_LICENSE=/etc/nebula/nebula-license.json

Most CI systems also let you define these as pipeline/agent variables in their UI — set them on the build agent only, never in the repo. After setx or a machine-level change, restart the agent/shell so new builds pick them up.

For a multi-assembly product, obfuscate the published output as one step (all DLLs together) rather than per project, so cross-assembly references stay consistent — use AfterTargets="Publish" or a dedicated release step, gated the same way. See Multiple assemblies.

In CI

Because the CLI is config-driven and returns meaningful exit codes, wiring it into a pipeline is a single step:

nebula --config nebula.config.json --json nebula-summary.json

If protection fails (bad config, missing license), the non-zero exit code fails the build. The --json summary can be archived as a build artifact.

Licensing in CI

CI runners are machines too, so an online activation uses a seat per machine. Choose by runner type:

  • Persistent / self-hosted runner: activate once with nebula register --key LIC-…. It uses one seat for that machine and every build reuses it.
  • Ephemeral runner (e.g. GitHub-hosted — a fresh VM each run): use an offline signed license file so you don’t burn a new seat every run. Store it as a secret, write it to a file in the job, and point NEBULA_LICENSE at that path (not the key). Ask us for an offline license file for your build pipeline.

Tip: keep nebula.config.json in source control alongside your project so protection settings are versioned with your code.

Multi-targeted projects — every framework, automatically

The '$(TargetFramework)' != '' guard on the target (above) makes it run once per target framework on a multi-targeted project (for example <TargetFrameworks>net462;net6.0</TargetFrameworks>), obfuscating each framework’s own $(TargetPath) and skipping the aggregate outer build. --output "$(TargetDir)obf" is already per-framework, so there are no filename collisions. You don’t list the frameworks anywhere — whatever a project builds, gets protected.

This pairs with a settings-only config — a nebula.config.json with the transforms (renaming, controlFlowInclude, encryptStringsInclude, signing) but no inputs. The CLI’s --input switch supplies each framework’s assembly at build time, so one shared config protects every project and every framework:

nebula --config settings.json --input MyApp.dll --output out

Visual Studio and msbuild.exe

Because the integration shells out to the nebula CLI rather than loading an MSBuild task, it works the same under both dotnet build and Visual Studio / msbuild.exe — no engine-specific setup. Put nebula on the build agent’s PATH (or use its full path in the <Exec> command). Developer machines that don’t set the gating variable build exactly as before and don’t need Nebula installed.

Licensing only the build server

Because you obfuscate on the build server, you only need to license the build machine(s) — not every developer. Nebula licensing is per-machine: on a persistent agent activate an online key with nebula register --key LIC-…, or point NEBULA_LICENSE / licenseFile at the path of an offline signed license file (best for ephemeral runners). Developers need nothing installed.

Set "requireLicensedEdition": true in the config so a build fails instead of silently shipping weakly-obfuscated output when the license is missing, revoked, lapsed, or too low a tier for what you asked for (for example aggressive control flow needs Enterprise; on Pro it would otherwise downgrade to normal). Without this flag a license problem simply drops to the Free edition and still emits output.

The build re-verifies the activation lease online as it nears expiry, so a revoked license is caught at the next build — give the agent outbound network access. Offline, the cached lease is honored only until it lapses, after which the edition drops to Free (and, with requireLicensedEdition, the build fails). Keep the agent’s license active.