Conversational Audit Walkthrough
A real /obfuscate session on a small ASP.NET Core sample, exactly as it happened.
Optional — this page shows the conversational workflow. The same audit runs as a plain CLI command: demeanor audit MyApp.dll prints the same findings you see below, and every fix shown here can be applied by hand. Demeanor’s audit is the intelligence — the assistant is a conversational relay. See Getting Started for the standalone path.
Requires your own Claude subscription — not included with Demeanor. The session was recorded with Claude, but Demeanor’s MCP server is spec-compliant — any MCP-capable assistant (Claude Desktop, Cursor, Windsurf, Continue.dev, VS Code MCP extensions, and others) drives the same loop.
The sample
CatalogService is a small ASP.NET Core 10 web service — about 250 lines of C#. It uses the most common .NET serialization and data-access patterns (EF Core, System.Text.Json, Newtonsoft, MVC, an [Serializable] legacy DTO) and deliberately contains one thing the assistant will ask you about, plus two it flags for your information. Running /obfuscate against this sample is what produced every block below.
From the project directory, start Claude Code:
cd samples/CatalogService
claude Everything below is what the assistant does between your first slash command and your next decision point.
The session
/obfuscate
The assistant confirms Demeanor is installed, confirms the license is set, and builds Release. No output shown — none of it needed your attention.
Audit results
The assistant calls into Demeanor’s MCP server and relays the audit.
Demeanor found 7 such patterns in CatalogService and will keep the original names — no action needed from you:
| Pattern | Types |
|---|---|
JSON reflection-path (properties referenced by JsonSerializer<T>) | AuditRecord |
[Serializable] (BinaryFormatter / XML formatters resolve property names at runtime) | LegacyReportDto |
| Property-usage (property names appear as string literals in code) | CatalogJsonContext, Product, Order, OrderItem, LegacyWebhookPayload |
| MVC controller routing | HealthController |
EF Core entities / DbContext | CatalogDbContext, Product, Order, OrderItem |
IOptions<T> configuration binding | CatalogOptions |
| Newtonsoft.Json public-property contract | LegacyWebhookPayload |
Needs your decision — one thing to decide:
MapGet("/orders/{id:int}/summary")returnsOrderSummary, whose properties will be renamed (F005).
OrderSummaryisn’t registered inCatalogJsonContext(the source-generated JSON context) and no code references its properties by name, so Demeanor has no signal that its property names are part of a public contract. After obfuscation, the endpoint would emit{"a": ..., "b": ... }instead of the readable JSON clients expect.
Two ways to fix:
The assistant recommends #1 — keeps one consistent mechanism across the project.
Informational
LegacyPlugin.SerializeAuditRecorduses a reflection-pathJsonSerializer.Serializecall (F009). Not a renaming risk. It only matters if you publish Native AOT — the IL2026/IL3050 analyzer warnings (trimmer/AOT compatibility diagnostics) will surface because reflection-based serialization can’t be statically analyzed.
Which fix for OrderSummary — #1 (source-gen context) or #2 (attribute on the DTO)?
#1
Fix applied
The assistant edits Serialization/CatalogJsonContext.cs — one attribute added:
[JsonSerializable(typeof(Product))] [JsonSerializable(typeof(Order))] [JsonSerializable(typeof(OrderItem))] +[JsonSerializable(typeof(OrderSummary))] [JsonSerializable(typeof(List<Product>))] [JsonSerializable(typeof(List<Order>))] public partial class CatalogJsonContext : JsonSerializerContext { }
The assistant rebuilds and re-audits to confirm the fix:
Build succeeded.
0 Warning(s)
0 Error(s)
OrderSummary is now auto-protected — the source-gen context
registration made it visible to PropertyUsageAnalyzer. All 24 types
safe to obfuscate. Dry-run results
CatalogService is an executable, so the assistant passes include_publics=true to also rename public types — nothing external references this assembly.
| Renamed / Total | |
|---|---|
| Types | 11 / 28 (39%) |
| Methods | 79 / 251 (31%) |
| Fields | 57 / 155 (36%) |
| Properties | 5 / 53 (9%) |
| Parameters | 86 / 193 (44%) |
| CFG-obfuscated methods (control-flow flattening, which scrambles the order and shape of basic blocks inside a method body so decompilers can’t reconstruct the original logic) | 90 / 242 (37%) |
| Call sites proxied (intra-assembly method calls routed through relay methods so decompilers can’t see the real target) | 9 / 9 |
| Strings encrypted | 84 |
| Constants encrypted | 43 |
The 9% property-rename rate is the signal auto-protection worked as intended — nearly every property belongs to an EF entity, a source-gen JSON DTO, a [Serializable] or Newtonsoft type, or the IOptions POCO. The 31% method-rename rate similarly reflects the MVC and Minimal API surface being preserved.
Ready to run the real obfuscation?
Yes
Obfuscation complete
Obfuscated DLL: bin/Release/net10.0/Demeanor/CatalogService.dll (49.6 KB) A quick smoke test before shipping: launch the obfuscated DLL and curl /orders/1/summary to confirm the JSON shape, then /health to confirm the controller still routes.
Verification
| Check | Result |
|---|---|
| Watermark | Enterprise |
| String encryption | ✓ |
| Anti-tamper | ✓ |
| Anti-debug | ✓ |
| SuppressIldasm | ✓ |
| Exclusions matched | EF, IOptions, [Serializable], Newtonsoft, MVC |
OrderSummary JSON getters preserved | get_Id, get_CustomerName, get_LineCount, get_Total |
Exclusions hit the right types: EF entities (Product, Order, OrderItem, CatalogDbContext), CatalogOptions (IOptions), LegacyReportDto ([Serializable]), LegacyWebhookPayload (Newtonsoft), HealthController (MVC), plus the compiler-generated anonymous type.
OrderSummary’s property getters are absent from the renamed list, so the JSON output still emits {"id","customerName","lineCount","total"} at runtime. The record’s Equals / GetHashCode / ToString / Deconstruct methods are renamed since they’re not part of the JSON contract.
CI/CD integration
Wiring build-time obfuscation into the .csproj means every Release build produces an obfuscated DLL automatically — no manual demeanor step in CI, no drift between dev builds and shipping builds. The assistant edits CatalogService.csproj:
<ItemGroup>
<PackageReference Include="WiseOwl.Demeanor.MSBuild" Version="6.*" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Obfuscate>true</Obfuscate>
</PropertyGroup> CI requirements:
DEMEANOR_LICENSEmust be a build-system secret (GitHub Actions secret, Azure DevOps variable, etc.) — never commit the key to the repo.- No extra install step —
dotnet restorepullsWiseOwl.Demeanor.MSBuildvia thePackageReference. - Keep
*.report.jsonas a build artifact so future crash reports can be decoded withdemeanor deobfuscate. - For release-to-release JSON compatibility, pass the prior release’s report to the next build via
<DemeanorPriorReport>to preserve the name mapping for any obfuscated fields that ship across versions.
GitHub Actions snippet:
- run: dotnet build -c Release
env:
DEMEANOR_LICENSE: ${{ secrets.DEMEANOR_LICENSE }} What this demonstrates
- Seven framework patterns fire automatically. Demeanor does the hard work before the assistant says a word — Phase 1’s seven-row table is produced by the audit, not by the assistant.
- The one finding that needed a human decision picks up with a recommendation and rationale. The
OrderSummarycase presents three valid options and flags the cleanest one (register in the source-gen context) — not “just trust me.” - The assistant waits for approval before editing, before dry-running, and before writing files. Every phase boundary is a yes/no checkpoint.
- Re-running the audit after the edit is what proves the fix, not the assistant’s say-so. The
OrderSummaryfinding flipped from “needs your decision” to “handled automatically”; the dry-run confirms zero properties renamed on the DTO shape.
Like what you just read? Enterprise is $2,999/year.
Per-company licensing. Unlimited developers, unlimited build machines. The audit you just watched is the same one that runs against your code the moment you install — whether you drive it from the CLI or from your AI assistant.
Running it on your own code
You don’t need this particular sample. If you don’t use an AI assistant, install Demeanor per Getting Started and run demeanor audit MyApp.dll — you will see the same categorized list you saw in Phase 1.
If you do use an MCP-capable assistant, install Demeanor, open your project in the assistant, and invoke /obfuscate (Claude Code) or ask the assistant to audit the assembly. The assistant will call into Demeanor’s opt-in MCP server, relay the findings, propose fixes, and make the edits with your approval — the same flow you just read.
Next steps
- Getting Started — the canonical five-step CLI guide
- Conversational Workflow — setup for customers who already use an MCP-capable assistant
- Exclusions Guide — framework-by-framework exclusion patterns and the
[JsonSerializable]recipe [ObfuscationAttribute]Features — per-feature control inside source