Getting Started

Five steps from first install to fully integrated CI builds. Install the NuGet package, run the audit, apply any fixes it recommends, obfuscate, wire it into MSBuild.

Step 1 — Install

1a. License (Enterprise)

Set your license key as the DEMEANOR_LICENSE environment variable on whichever machine(s) you build on — CI, dev box, or both.

Windows (persistent):

setx DEMEANOR_LICENSE "your-enterprise-key"

Linux / macOS:

export DEMEANOR_LICENSE="your-enterprise-key"

Close and reopen any terminal afterward so the new variable is picked up. Get an Enterprise license here. Community tier (free) requires no license key — skip to 1b.

1b. Install the obfuscator

dotnet tool install -g WiseOwl.Demeanor

One command. Cross-platform. Puts demeanor on your PATH.

1c. Validate the install

demeanor license

This prints the licensee name, tier, and expiration date. The first invocation also triggers a one-time bootstrap that installs the companion inspect tool.

Confirm both tools are installed:

dotnet tool list -g

You should see two entries:

Package Id          Version      Commands
-------------------------------------------
wiseowl.demeanor    6.0.4        demeanor
wiseowl.inspector   6.0.4        inspect

Step 2 — Audit before you obfuscate

Always start here. The audit reads your assemblies without modifying anything and reports what Demeanor will auto-protect and what it has questions about. It catches most patterns that would cause a post-obfuscation runtime surprise — reflection, serialization, data binding, DI conventions, etc.

Publish your app normally (dotnet publish -c Release), open a terminal in the publish folder, and run:

demeanor audit MyApp.dll --include-deps

Replace MyApp.dll with your entry assembly. For a .NET 8+ app, the .dll next to MyApp.exe is the actual managed assembly. The --include-deps flag tells audit to also analyze co-located private dependencies, so the set it reports on matches what Step 3 will obfuscate.

Example audit output

Here’s what the audit looks like on a sample ASP.NET Core app:

Audit: CatalogService (22 types)

  WHAT DEMEANOR WILL DO FOR YOU
  These patterns are handled automatically — no action needed.

    json-serialization                  1 type(s)
    Serializable                        1 type(s)
    property-usage                      5 type(s)
    mvc-controller                      1 type(s)
    efcore-dbcontext                    4 type(s)
    ioptions-binding                    1 type(s)
    newtonsoft-json                     1 type(s)

  CODE QUALITY ADVISORIES
    minimal-api-handler  — MapGet "/products/{id:int}" returns Product;
                           properties already protected. No action needed.
    minimal-api-handler  — MapGet "/orders/{id:int}/summary" returns
                           OrderSummary; properties will be renamed.
    reflection-json-call — LegacyPlugin.SerializeAuditRecord uses the
                           reflection-path JsonSerializer.Serialize overload;
                           breaks Native AOT (IL2026/IL3050).

  SUMMARY
    Total types:        22
    Auto-protected:     7
    Required changes:   0
    Safe to obfuscate:  22

How to read it

  • WHAT DEMEANOR WILL DO FOR YOU — Framework patterns Demeanor recognized and will protect for you automatically. Nothing for you to do.
  • CODE QUALITY ADVISORIES — Informational. The first here is already covered (no action). The other two are things the obfuscation itself would tolerate but are worth fixing before you ship — a public DTO not registered with the source-gen JSON context, and a reflection-path serialization call that won’t survive Native AOT.
  • SUMMARY — The number that matters is Required changes. If it’s zero, go straight to Step 3.

If you want the audit as JSON (for archiving or sending to support):

demeanor audit MyApp.dll --include-deps --json > audit.json

For the full conversation view of this same run, see the CatalogService walkthrough.

Step 3 — Obfuscate interactively

From the same publish folder:

demeanor MyApp.dll --include-deps --report --verbose

What the flags do:

  • --include-deps — Also obfuscates co-located private dependencies, so cross-assembly name references stay consistent.
  • --report — Writes a JSON name-mapping file. Keep this; you’ll need it to deobfuscate stack traces from production.
  • --verbose — Per-phase stats, plus full stack trace on any error.

Third-party NuGet packages (strong-named assemblies) are skipped automatically.

Example obfuscation output

Obfuscation complete (dry run — no files written).
  Assemblies processed: 1
  Types renamed:           8 of 22 (36%)
  Methods renamed:         31 of 74 (41%)
  Fields renamed:          2 of 4 (50%)
  Properties renamed:      0 of 24 (0%)
  Parameters renamed:      18 of 26 (69%)
  Strings encrypted:       14
  Methods CFG-obfuscated:  12 of 48 (25%)

  Automatic decisions:
   [FrameworkPatternAutoProtected] Preserved 12 framework-pattern types
   [CustomAttributeBlobRewritten]  Rewrote 5 custom-attribute blobs

How to read it

  • Per-phase counters (Proxied, Encrypted, Injected) report what was applied. These run unconditionally on every eligible site — full Enterprise hardening regardless of rename percentages.
  • Rename percentages will look modest on a web app, and that’s correct. Properties: 0 of 43 (0%) looks alarming until you realize every property in a web app is on a DTO, an EF entity, an MVC action input, or a SignalR hub argument — all framework-bound by name at runtime. Renaming any would break the app.
  • Where the real protection comes from on a web app: string encryption, integer-constant encryption, control-flow flattening, anti-tamper, anti-debug, and call-hiding proxies. None of these depend on renaming, and all of them ran on every eligible site.
  • Automatic decisions at the bottom explains why the rename percentages are what they are — one line per framework pattern that opted certain types out of renaming.

Output goes to a subfolder named Demeanor by default (use --out <dir> to change). Copy the obfuscated DLLs into your deployment layout and run the app. If it behaves identically, move to Step 5. If anything is off, see Step 4.

Step 4 — If your obfuscated app misbehaves

Almost always a reflection or serialization pattern the audit didn’t flag. The fix is to tell Demeanor to leave the affected type or member alone. Two approaches:

Preferred: [Obfuscation] attribute in source

Add the standard System.Reflection.ObfuscationAttribute to the type or member:

using System.Reflection;

[Obfuscation(Exclude = true, ApplyToMembers = true)]
public class MyDtoThatGetsSerialized
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Or on a single member:

[Obfuscation(Exclude = true)]
public string SomeNameLookedUpByReflection { get; set; }

This is the right long-term fix because:

  • The exclusion lives next to the code that needs it — future edits can’t accidentally remove it.
  • It survives into CI/MSBuild integration with zero extra config.
  • The reason is self-documenting — add a comment explaining what depends on the name.

Quick diagnostic: CLI --exclude

Useful for iterating without rebuilding from source:

demeanor MyApp.dll --include-deps --report --verbose --exclude "MyNamespace.MyType"

Or a regex match for a group (repeatable):

demeanor MyApp.dll --include-deps --report --verbose --xr "MyNamespace\.Dtos\..*"

Once an exclusion fixes the behavior, move it into source as an [Obfuscation] attribute so it doesn’t have to be re-specified on every build. See the Exclusions Guide for full details.

Step 5 — Integrate into your build

Only do this once Step 3 produces a working obfuscated app. The MSBuild path uses the same engine, but diagnosing issues is much easier interactively than from a CI log.

From your project directory:

demeanor init

demeanor init adds a PackageReference to WiseOwl.Demeanor.MSBuild and an <Obfuscate>true</Obfuscate> property guarded on Release builds. The package bundles its own obfuscator binary, so build machines need zero global-tool installs — dotnet restore plus dotnet build -c Release is enough.

Then build

dotnet build -c Release

Obfuscation runs automatically on Release builds; Debug builds stay unmodified.

Manual alternative

If you prefer to see the changes explicitly, demeanor init writes this snippet into your .csproj:

<ItemGroup>
  <PackageReference Include="WiseOwl.Demeanor.MSBuild" Version="6.0.4" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <Obfuscate>true</Obfuscate>
</PropertyGroup>

You can add this by hand if you prefer. The WiseOwl.Demeanor global tool and the WiseOwl.Demeanor.MSBuild package ship as a matched pair from one build pipeline, so any current version works — pick whichever version you want the build to run against.

Useful MSBuild properties (all optional)

PropertyDefaultDescription
<Obfuscate>falseEnables obfuscation for this build.
<DemeanorIncludeDeps>falseAlso obfuscate co-located private dependencies.
<DemeanorReport>trueEmit .report.json for stack-trace decoding.
<DemeanorQuiet>trueSuppress non-error output. Set to false for CI logs with per-phase stats.

MSBuild excludes

If you need CLI-style excludes from MSBuild (instead of [Obfuscation] attributes):

<ItemGroup>
  <DemeanorExclude Include="MyNamespace.MyType" />
  <DemeanorExcludeRegex Include="MyNamespace\.Dtos\..*" />
  <DemeanorExcludeAssembly Include="ThirdParty.Library" />
</ItemGroup>

Any [Obfuscation] attributes you added during Step 4 carry over with zero extra config — the engine reads them from the assembly itself. The MSBuild package respects DEMEANOR_LICENSE the same way the CLI does.

Wire Demeanor into your real build.

Ready to wire Demeanor into your real build? Enterprise is $2,999/year, per-company, unlimited developers and CI machines. No seat counting, no activation server, no phone-home.

Buy Enterprise — $2,999/yr See pricing details

Using this with an AI assistant (optional)

If you already use an MCP-capable assistant (Claude Code, Claude Desktop, Cursor, Windsurf, Continue.dev, or a VS Code MCP extension), Demeanor’s opt-in MCP server lets you drive the same audit as a chat. Requires your own Claude subscription — not included with Demeanor.

Conversational workflow → · See a full conversation →