Reports & Incremental Obfuscation

Enterprise feature. Generate detailed obfuscation reports for auditing, stack trace decoding, and incremental builds.

Generating a Report

demeanor --report --report-file MyAppReport.json MyApp.dll

Or in MSBuild:

<DemeanorReport>true</DemeanorReport>
<DemeanorReportFile>$(TargetDir)$(TargetName)Report.json</DemeanorReportFile>

The report is a JSON file containing every type and member in the obfuscated assembly. For each symbol, it records:

  • Renamed symbols: original name → obfuscated name
  • Excluded symbols: original name + reason why it was not renamed

Report Schema

Each type in the report contains lists of methods, fields, properties, and events. Each symbol has mutually exclusive fields:

// Renamed symbol:
{ "name": "GetCount", "renamed": "a", "accessibility": "public" }

// Excluded symbol:
{ "name": ".ctor", "excludedReason": "runtime special name", "accessibility": "public" }

// Type:
{
  "name": "MyApp.PricingEngine",
  "renamed": "b",
  "visibility": "public",
  "methods": [ ... ],
  "fields": [ ... ]
}

Exclusion Reasons

The excludedReason field explains why a symbol was not renamed. Common reasons:

ReasonMeaning
category disabledA --no-types, --no-methods, etc. flag disabled this category
excluded by --exclude or --xrMatched a command-line exclusion pattern
excluded by [Obfuscation] attributeThe source code has [Obfuscation(Exclude = true)]
name used in string-based reflectionThe name appears in a reflection lookup whose target Demeanor can resolve
name used in dynamic dispatchThe dynamic keyword resolves this member by name at runtime
public/protected visibilitySymbol is externally visible (use --include-publics to override)
runtime special name.ctor, .cctor, and other runtime-reserved names
native/runtime implementationMethod body is provided by the runtime, not the assembly
virtual override of external methodOverrides a framework method whose name cannot rename
vararg calling conventionThe runtime resolves vararg call sites by name
COM interop type[ComVisible] or [ComImport] — COM uses name-based dispatch
serializable type--no-serializable flag active
enumeration type--no-enumerations flag active
compiler-generated typeAsync state machine, lambda closure, etc.

Incremental Obfuscation

Incremental obfuscation preserves name mappings across versions. When you update your application, existing symbols keep their same obfuscated names while new symbols get new names.

Why use incremental obfuscation?

  • Serialization compatibility: Data serialized with v1's obfuscated type and field names must deserialize correctly in v2. Without incremental mode, a new type inserted before existing types shifts all obfuscated names.
  • Plugin stability: External code may reference obfuscated names stored in configuration files, databases, or DI containers.
  • Smaller patches: Unchanged symbols produce identical metadata bytes, keeping binary diffs small for update distribution.

Workflow

# v1: Initial obfuscation with report
demeanor --report --report-file v1-report.json MyApp.dll

# v2: Incremental obfuscation using v1's report
demeanor --prior-report v1-report.json --report --report-file v2-report.json MyApp.dll

# v3: Chain continues — always use the most recent report
demeanor --prior-report v2-report.json --report --report-file v3-report.json MyApp.dll

How it works

Demeanor reads the prior version’s report and reuses each existing symbol’s obfuscated name when that symbol still exists in the current assembly. New symbols get fresh names; removed symbols’ names are not reused, so cross-version data does not collide.

Adding new types and members

New symbols get fresh obfuscated names. Adding types or members in a new release does not change the obfuscated names of symbols that existed in the prior release.

Removing types and members

Removed symbols are absent from the new report. Their previous obfuscated names are not reused, preventing accidental name collisions that could break cross-version serialization.

MSBuild integration

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <!-- <Obfuscate>true</Obfuscate> not needed: auto-enabled on Release. -->
  <DemeanorReport>true</DemeanorReport>
  <DemeanorReportFile>$(TargetDir)$(TargetName)Report.json</DemeanorReportFile>
  <DemeanorPriorReport>$(MSBuildProjectDirectory)\prior-report.json</DemeanorPriorReport>
</PropertyGroup>

Store the report in source control alongside your release. Before each release, copy the current report to prior-report.json and rebuild.

CI/CD workflow

# GitHub Actions example
- name: Download prior report
  uses: actions/download-artifact@v4
  with:
    name: obfuscation-report
    path: prior-report.json
  continue-on-error: true  # First build has no prior report

- name: Build and obfuscate
  env:
    DEMEANOR_LICENSE: ${{ secrets.DEMEANOR_LICENSE }}
  run: dotnet build -c Release

- name: Upload report for next build
  uses: actions/upload-artifact@v4
  with:
    name: obfuscation-report
    path: bin/Release/net10.0/MyAppReport.json