
AppArmorX — Android security delivered through a Gradle plugin.
How I built Android runtime protection into a Gradle plugin — and what testing it against real attacks revealed.
Android security often arrives as another integration project: add an SDK, initialize it, register callbacks, and design a response screen. Meanwhile, third-party code already inside the app may call sensitive platform APIs without the app team having written those calls itself.
I built AppArmorX to move that integration work into the build. It is an Android runtime security engine delivered through the com.apparmorx.security Gradle plugin. The core works without an account or a hosted service. Version 0.1.0 is live on the Gradle Plugin Portal (https://plugins.gradle.org/plugin/com.apparmorx.security) and the source is on GitHub (https://github.com/dev-shushant/security-gradle).
Let the build do the integration
The consumer-facing plugin declaration is small:
plugins {
id("com.android.application")
id("com.apparmorx.security") version "0.1.0"
}Version 0.1.0 is available on the Gradle Plugin Portal. If your project defines pluginManagement.repositories, include gradlePluginPortal() there. Follow the setup and signing steps in the quickstart: https://github.com/dev-shushant/security-gradle/blob/main/QUICKSTART.md
AppArmorX uses the Android Gradle Plugin’s instrumentation API and ASM to rewrite compiled bytecode. It injects lifecycle guards into Android components and reroutes supported sensitive API calls through its policy layer. The generated protection lives in build outputs; the consumer’s source files remain untouched.
That distinction matters: avoiding initialization code does not eliminate release configuration. Signing setup, policy choices, supported build variants, and the resulting reports still need review.
Two layers: build-time coverage and runtime evidence
At build time, the plugin verifies authenticated policy, instruments supported bytecode, bundles the native engine for arm64-v8a and x86_64, audits the merged manifest, and generates reports. Packaged-output checks help catch required protection artifacts missing from a release.
At runtime, the engine evaluates JVM, Android framework, and native signals. The documented probe matrix contains 28 checks covering root indicators, debugger and tracer state, hooking artifacts, injected threads, executable-memory signals, SELinux state, emulator indicators, and signing or APK-integrity evidence.
Some sensitive native reads use direct syscalls rather than libc wrappers. This reduces dependence on one interception point; it does not make the engine immune to an attacker controlling the process or operating system.
Guards cover Activities, Application, Services, and BroadcastReceivers. A startup preflight and optional fail-closed behavior can hold guarded execution until a verdict is available. Configuration-cache support is also part of the build integration.
The architecture diagram below shows how these build and runtime responsibilities connect.

Build-time instrumentation and runtime security evidence. Architecture illustration.
The dependency firewall: policy at compiled call sites
Runtime threat detection and dependency behavior are different problems. An SDK can make an unwanted clipboard or identifier request on an otherwise ordinary device. AppArmorX’s dependency firewall addresses supported calls in instrumented application and dependency bytecode.
- Clipboard: supported getPrimaryClip and hasPrimaryClip calls can be neutralized.
- Identifiers: supported ANDROID_ID, device-ID, and IMEI access is routed through sanitization.
- Location: supported last-known-location calls can return no location.
- TLS: supported hostname-verifier and trust-manager setup is checked to reject permissive configurations.
The value is coverage of library call sites that the application team did not write. The boundary is equally important: this is not a universal Android API sandbox. Reflection, dynamically injected code, unsupported APIs, or paths outside rewritten bytecode need separate analysis.
In the documented sample exercise, all four attack buttons were neutralized: clipboard, identifier, last location, and a permissive HTTPS verifier. That is evidence for those four scenarios, not proof that every SDK or every exfiltration path is covered.
The comparison below illustrates the firewall behavior; the full coverage boundary remains the supported rewritten call sites.

Illustration of supported dependency-firewall behavior; not universal API coverage.
Unknown must never mean clean
Every probe returns CLEAR, DETECTED, or UNAVAILABLE. CLEAR means the probe ran without finding its targeted evidence. DETECTED means it found evidence. UNAVAILABLE means it could not establish a result. None of these is a global guarantee that the device is safe.
This became practical during investigation. Three native checks reading /proc/net/tcp or /sys/fs/selinux/enforce had treated failed reads as clean. SELinux can deny those reads from an application’s sandbox. A denied read tells us about access, not the absence of an attacker. The documented fixes changed that outcome to UNAVAILABLE.
Reporting uncertainty keeps policy decisions honest. Teams can distinguish missing evidence from a successful check and decide how sensitive operations should behave.
Profiles and signing without hand-written initialization
DEVELOPMENT favors observation and developer workflows. STANDARD enables the dependency firewall and a blocking response for confirmed threats. HARDENED adds stricter startup behavior and screen-capture restrictions. Debuggable builds suppress enforcement against their own developer.
security {
profile.set("HARDENED")
}The generated report highlights overrides weaker than the selected profile. This makes a release’s effective configuration visible instead of assuming that choosing a profile proves every layer is active.
Release signer pins can be derived from the configured signing keystore at build time. That removes manual fingerprint copying when the required signing configuration is available. Re-signing the APK with an unrelated certificate can then produce a signer mismatch. Signing checks still depend on the correct expected certificate and policy.
Seeing the evidence in the sample app
The sample presents security posture, attack buttons, and individual probe outcomes. Its screenshots are recorded examples, not a fresh device test performed for this article. A deliberate signer-mismatch fixture demonstrates how a detected condition appears; it is not a production signing configuration.
The status view makes unavailable checks visible rather than counting them as passed.

Recorded sample status: detected findings and unavailable checks remain visible.
The attack panel shows the four supported SDK-style behaviors and their observed outcomes.

Recorded sample exercise: four supported SDK-style attack scenarios neutralized.
The individual checks view shows why a summarized verdict needs supporting evidence and explicit unknown states.

Recorded probe breakdown: individual outcomes support the summarized posture.
Testing beyond the green test suite
A passing suite proves the assertions that were written. It does not establish that an attacker cannot choose a different path. AppArmorX’s validation records therefore include a rooted Android emulator, a real Frida server, an attacker HTTPS endpoint with a self-signed certificate, and an independently built consumer app using the embedded runtime.
The records describe root detection, supported firewall scenarios, and a signing test that moved from an expected signer to a tampered APK re-signed with an unrelated key. Those are useful checks because they exercise the packaged app and actual runtime evidence.
The more revealing result was a bypass of the firewall’s scope. Injected Frida code called framework TLS APIs directly with a permissive trust manager. These calls did not pass through rewritten application call sites. In the documented experiment, a man-in-the-middle request succeeded even while the runtime posture was restricted.
A restricted UI and an API call-site firewall are not equivalent to containing arbitrary code already executing inside the process. That result belongs in the product description because it explains what the protection actually does.
An opt-in hooking response, with a measured boundary
AppArmorX provides an explicit killOnConfirmedHooking policy switch:
security {
killOnConfirmedHooking.set(true)
}It defaults to false in every profile, including HARDENED. It is forced off for debuggable builds. It may terminate the process only for DETECTED hooking or injection evidence, never for unavailable evidence, root alone, or a signer mismatch.
The HookWatchdog checks relevant native evidence periodically, approximately every 750 milliseconds, independently of Activity lifecycle callbacks. These are periodic checks, not continuous interception. They cannot guarantee prevention of the single action already in flight when detection fires; they can stop further activity in that process.
In the documented attach-mode test, Frida attached to an already-running protected app. The process exited in about a second, before that particular script installed its permissive trust manager. The script reported a closed connection. That is a measured success for the tested timing and payload, not a guarantee against all attach-mode attacks.
In the spawn-mode test, Frida launched the app with injected code already present. The attacker request completed before AppArmorX’s response. The experiment remained successful for the attacker. An in-process check cannot retroactively prevent code that ran before the check or its enforcement.
Writing both outcomes makes the control actionable: teams can understand its trigger, defaults, observed benefit, and the race it does not eliminate.
Reports that explain the effective protection
Build integration should produce evidence a release team can inspect. AppArmorX generates a per-variant HTML report with protection state, configured layers, overrides, and digests, plus SARIF output for compatible analysis workflows. Manifest findings such as permitted cleartext traffic belong in the review context rather than being confused with proof of an attacker.
Optional telemetry can send security events to an explicitly configured owner-controlled endpoint. It is off unless configured. The documented design includes signed payloads, queueing, backoff, and restricted redirect behavior. Teams still need to review endpoint ownership and delivery policy before enabling it.
What remains unfinished
AppArmorX is a pre-release project, not an independently certified security product. Validation is limited to the documented devices, emulators, builds, and attack scenarios. No independent penetration test has been completed.
Packaged integrity includes artifact-presence checks and optional digest pins; it should not be described as a complete cryptographic seal of every APK byte. Full GOT relocation validation is unfinished. Flutter, React Native, and Unity hosts remain untested, and Android-shell instrumentation does not imply coverage of their application logic. Server-side authorization remains necessary. The engine includes an Android Keystore attestation helper, but your server must verify its evidence and make authorization decisions. It is not a hosted attestation service or a guarantee of device trust.
Why I am building it
The goal is to make useful Android protection easier to adopt and easier to inspect. A small Gradle integration can move repetitive security plumbing into the build while keeping the resulting policy and evidence visible.
The most valuable result so far is not a clean dashboard. It is having concrete records of supported attacks being neutralized, unavailable checks being reported honestly, and a real injection path that exposes the boundary of call-site rewriting.
Try AppArmorX: https://apparmorx.com/docs/quickstart
Get the plugin: https://plugins.gradle.org/plugin/com.apparmorx.security
Explore the Apache-2.0 source: https://github.com/dev-shushant/security-gradle
Read the test evidence and known limits: https://github.com/dev-shushant/security-gradle/blob/main/docs/VALIDATION.md
If you work on Android security, the feedback I would value most is reproducible: the environment, attack path, expected behavior, and actual outcome.
— Shushant Tiwari, creator of AppArmorX


