.NET Application Protection
Clomotech serves as a technology vendor partner to an enterprise software company, contributing engineering capacity to a commercial .NET application protection platform. The engagement spans mobile runtime security, build pipeline reliability, release correctness, and dependency health.
Engineering Inside a Commercial Product
The platform operates at the intersection of build tooling and runtime security. It intercepts .NET assemblies during the build pipeline — rewriting them to obfuscate logic, encrypt strings, and inject runtime self-protection checks — then ships binary output that must run correctly in production across every customer's runtime environment and OS target.
Clomotech's role as a vendor partner is not client-to-builder. It is integration into the vendor's engineering team — working inside an established commercial codebase with existing customer contracts, existing regression baselines, and a zero-tolerance policy for silent breakage. Every change must be validated against real platform conditions: CI build agents, physical mobile devices, and release configurations that don't exist on a local development machine.
Contributions span four distinct areas of the product, each presenting its own platform-specific engineering challenge.
"Working inside a commercial product's codebase is a different discipline. You're not building from scratch — you're maintaining correctness across existing customer environments and platform versions while shipping new capability."
— Engineering PrincipleFour Areas of Engineering Work
Each contribution addresses a distinct production challenge in the platform — from mobile security to build infrastructure.
Mobile Platform Security
The platform's runtime self-protection (RASP) features — binary tamper verification and rooted device detection — required platform-specific integration work to function reliably on .NET MAUI Android. Updated injection paths and tuned runtime validation to eliminate false positives on real devices.
Build System Reliability
Customers hit intermittent AccessViolationException and transient assembly access failures in high-concurrency CI environments. Introduced bounded retry with controlled backoff across the platform's pipeline and its assembly manipulation layer, targeting only retry-safe failure categories.
Release Pipeline Correctness
Obfuscated output was missing the required deps.json file, causing runtime assembly resolution failures. A second failure mode appeared only in Jenkins release mode due to unstable System.Text.Json serialization under obfuscation. Both were resolved and validated in the actual failing environment.
Dependency Modernization
Executed a security-first NuGet dependency upgrade program — vulnerability-checking packages before upgrading, evaluating alternatives where risk existed, resolving breaking changes in source, and validating functional parity through regression testing across critical flows.
Build-Time Protection Pipeline
The platform runs as a post-build transform step inside the CI/CD pipeline — intercepting .NET assemblies, rewriting them, and producing protected output. Each contribution maps to a specific stage.
Protection Platform — Build Pipeline & Runtime Architecture
RASP on MAUI Android
The platform's runtime application self-protection (RASP) features — binary tamper verification and rooted device detection — are injected into assemblies at build time and executed at runtime to verify the app's integrity and environment. On .NET MAUI Android, the runtime behaves differently from older mobile stacks: APK packaging, signing mechanics, and app startup differ in ways that affect when and how protection checks can reliably fire.
The challenge was making both checks dependable across real device and emulator conditions without generating false positives that block legitimate users. An overly sensitive tamper check would trigger on unsigned debug builds or test distribution; an overly broad root detection check would flag standard developer devices.
The solution involved updating the integration paths through which checks are injected into MAUI Android assemblies, and tuning the validation flow so checks execute at reliable runtime entry points. The fix was iterated against real device conditions — not just emulators — to catch platform-specific timing and execution differences before shipping.
Verifies that binaries were not modified after signing and build. Updated injection paths account for MAUI Android's APK structure and signing pipeline, ensuring the check evaluates the correct binary boundaries.
Detects rooted Android environments where OS-level security assumptions are weaker. Tuned detection sensitivity so legitimate devices — including developer machines where root indicators are present for valid reasons — do not trigger false positives in production distributions.
Why MAUI Android differs
MAUI Android targets the Android runtime through a different abstraction layer than Xamarin.Android. APK assembly layout, startup sequencing, and signing validation differ — meaning injection points that worked for earlier mobile stacks needed platform-specific updates to function correctly in MAUI output binaries.
Balance: security depth vs. usability
RASP checks that are too aggressive break legitimate users. Checks that are too lenient are trivially bypassed. The tuning work balanced detection depth with production usability — a trade-off that can only be verified through testing on real devices under real conditions, not on a developer workstation.
Result
Binary tamper verification and root detection are stable and practical for MAUI Android release pipelines. Security posture is improved without disrupting normal app usage for legitimate users.
Concurrency-Safe Retry Logic
The platform reads and rewrites assemblies during build. In high-concurrency CI environments, assembly files can be temporarily locked by parallel tools — causing intermittent failures that are difficult to reproduce and hard to diagnose.
Customers in shared and parallel CI environments hit intermittent build failures with AccessViolationException and transient assembly access conflicts. These failures were non-deterministic — they depended on the timing of concurrent processes (antivirus scanners, build indexers, or parallel build steps locking the same assembly files) and could not be consistently reproduced in isolation.
Because the root cause was environmental concurrency rather than incorrect logic, the failures were easy to dismiss as infrastructure noise — but they were causing real build breaks in customer pipelines.
Added retry guards around assembly open, read, and write access points. Improved handling of transient lock and contention scenarios at the obfuscation processing stage.
Extended retry-aware handling to the assembly manipulation path — a second access layer independently susceptible to transient lock contention during read and write operations.
Retry applies only to failure categories known to be transient and retry-safe. Non-recoverable errors — corrupted assemblies, permission violations, missing files — preserve fail-fast behavior and are not retried, ensuring errors that signal real problems still surface immediately.
Bounded backoff, not open loops
The retry implementation uses bounded retry counts with controlled backoff intervals. This prevents a stuck build from waiting indefinitely while still giving transient locks time to clear between attempts.
Known limitation
Retry attempts originating within external dependency internals may not always surface in the platform's build logs — logging is bounded to what the platform can observe within its own call stack.
Result
Intermittent concurrency-related build failures were substantially reduced. CI pipelines running parallel build agents, antivirus scanning, and build indexing in parallel now produce more deterministic, stable outcomes.
deps.json and the Jenkins Release Gap
Modern .NET applications rely on a *.deps.json file for runtime assembly and dependency resolution. When the platform's obfuscated output omitted this file, apps failed at startup — the runtime could not locate and load the required assemblies.
The first fix — explicit handling to ensure deps.json is always present in final obfuscated artifacts — resolved the runtime failure. But a second, independent failure mode remained: Jenkins release builds were failing even after the file was included. Local builds and debug configurations passed; the failure was specific to the CI release pipeline.
Investigation revealed that the metadata persistence strategy used to generate and store deps metadata — initially implemented with System.Text.Json — was not stable under obfuscation in release mode. The obfuscation process itself was interfering with the serialization path in a way that only manifested under release-configuration CI conditions.
System.Text.Json
Chosen for modern, lightweight JSON serialization of deps metadata. Passed local and debug validation — failed in Jenkins release builds due to obfuscation-induced serialization instability in release-mode output.
XML Document Generation
Reworked metadata persistence to use XML document generation and storage. More verbose than JSON but proved deterministic across all build configurations — local, debug, and Jenkins release — under obfuscation.
Why validate in Jenkins release mode specifically
The failure only manifested in the actual failing environment — Jenkins release configuration with obfuscation applied. Validating only on local debug builds was insufficient. The fix was confirmed specifically against the CI release pipeline conditions before being accepted as resolved.
The obfuscation/serialization interaction
Obfuscation renames types and methods in assemblies. When a serialization library uses reflection to discover type names or property paths at runtime, renamed identifiers can silently produce incorrect or empty output — a failure mode that is invisible in debug builds where obfuscation may not apply.
Result
Runtime failures due to missing deps.json were eliminated. Jenkins release obfuscation builds are stable. CI artifact quality improved — the obfuscated output reliably includes all dependencies required for runtime startup.
Security-First NuGet Upgrades
Outdated dependencies accumulate CVE exposure, compatibility drift, and maintenance burden. A staged, security-validated upgrade program was executed across the platform's NuGet dependency graph.
Audited the full NuGet dependency graph — direct and transitive — to identify packages no longer at a current stable version.
Vulnerability checks were performed on each candidate package. If known CVEs existed, alternative packages were evaluated. Security findings requiring escalation were escalated before proceeding.
Packages were upgraded in controlled stages rather than all at once. This limits blast radius — a breaking change in one package is isolated from other concurrent upgrades, making root-cause identification tractable.
Packages that introduced API changes were handled with source-level fixes in the platform's codebase — absorbing the package-level breaking change rather than pinning to an older version and deferring the problem.
Full regression testing validated functional parity after each upgrade stage. Version decisions, compatibility notes, and breaking-change handling strategy were documented throughout.
The Platform Engineering Stack
Need engineering capacity for a commercial product?
Whether it's platform-specific security features, build pipeline stability, or dependency health — we've shipped these in production as a trusted engineering partner.
