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 |
|---|---|
| System.Text.Json (reflection-mode) | AuditRecord |
| [Serializable] type | LegacyReportDto |
| Property names referenced as strings in code | CatalogJsonContext, Product, Order, OrderItem, LegacyWebhookPayload |
| MVC controller | HealthController |
| EF Core entity / DbContext | CatalogDbContext, Product, Order, OrderItem |
| Configuration binding (IOptions) | CatalogOptions |
| Newtonsoft.Json type | 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%) |
| Methods with control-flow obfuscation (the order of operations inside each method body becomes unreadable in a decompiler) | 90 / 242 (37%) |
| Calls hidden from static analysis (decompilers can’t resolve which methods call which) | 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> Just the <PackageReference> — the package auto-enables obfuscation for Release builds and auto-skips test projects via IsTestProject.
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 a per-company subscription. 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. See pricing →
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