September 24, 2026
Building Wazuh Mobile Sentinel: Bringing Mobile Visibility to a SIEM That Doesnβt Have It (Yet)
The gap

By Muhammad Ahmad Abid
6 min read
The gap
Wazuh covers Linux, Windows, macOS, cloud workloads, even containers but there's no official mobile agent. It's not an oversight nobody's noticed either: there's an open feature request on Wazuh's own GitHub repo (#32881) asking for exactly this, and a Wazuh blog post on forwarding Android syslog that closes with an open invitation for the community to build in this space.
Mobile devices carry as much (arguably more) sensitive exposure as a laptop on the network email, banking apps, corporate messaging, VPN clients and they're often completely outside a SOC's visibility. I wanted to fix a sliver of that, as a real open-source contribution rather than a class project.
What I built
Wazuh Mobile Sentinel, a small Android app (Kotlin, no root required) that reports device security posture to a Wazuh manager. It collects:
- OS version, security patch level, manufacturer/model
- Installed app count (total + user-installed)
- Storage encryption status
- Screen lock configuration
- Developer options / USB debugging state
- VPN and network connection type
- A Play Integrity API signal (root/tamper indicator)
- Real-time new-app-install detection, including whether an app was sideloaded and whether it requests sensitive permissions (SMS, contacts, camera, mic, location, install-packages)
The pipeline: the Android app POSTs JSON to a lightweight Flask receiver, which appends structured log lines that a Wazuh manager picks up through standard log monitoring. Custom Wazuh rules turn that into alerts β including a correlation rule that escalates severity when multiple weak signals (e.g. USB debugging and no screen lock) appear on the same device within a two-minute window.
Running on real hardware, a Redmi Note 13.
Same app, same result, on a stock Android emulator, confirms it's not device-specific.
Android App (Kotlin)
| JSON payload
v
Flask receiver (EC2)
v
Wazuh manager (log monitoring)
| built-in JSON decoding + custom rules
v
Wazuh Dashboard β alerts, Threat HuntingAndroid App (Kotlin)
| JSON payload
v
Flask receiver (EC2)
v
Wazuh manager (log monitoring)
| built-in JSON decoding + custom rules
v
Wazuh Dashboard β alerts, Threat HuntingDeliberately not an MDM. Consumer security apps (Zimperium, Lookout, etc.) don't expose their data through an open API β it stays in their own cloud. Enterprise MDM does, but needs enterprise enrollment and is far heavier than most Wazuh setups need. This sits in between: no MDM infrastructure, no vendor lock-in, built entirely on public Android APIs and Wazuh's existing log-ingestion model.
Two problems that weren't obvious at all
1. Wazuh's own JSON decoder was fighting my custom one
Every custom decoder I wrote, even a near-empty one with just <parent>json</parent> and a <prematch> silently broke Wazuh's automatic JSON field extraction. Rule descriptions referencing $(device_id) came back empty, even though wazuh-logtest showed the rule technically firing.
I tried chaining parent/child decoders, use_own_name, different combinations of prematch and regex/order β each fixed one symptom and broke another (sometimes crashing wazuh-manager outright with "Parent decoder name invalid").
The fix was to stop fighting it: delete the custom decoder entirely, let Wazuh's built-in json decoder auto-extract every field (it does this natively for valid single-object JSON logs), and write rules that match on <decoded_as>json</decoded_as> plus a <regex> against the raw text rather than relying on <field> conditions. That combination gave both correct field substitution and reliable rule firing.
Sometimes the fix is to delete code, not write more of it.
2. App-install detection silently didn't work on any device
This was the harder one. AppInstallReceiver, listening for PACKAGE_ADDED/PACKAGE_REPLACED, simply never fired. Not on the emulator, not on a real Redmi Note 13, regardless of permissions.
I chased a few plausible leads first:
- Suspected the app needed a first manual open before receiving broadcasts (partially true, but not the full story)
- Suspected MIUI/HyperOS-specific battery or autostart restrictions β added
QUERY_ALL_PACKAGESto fix a real (but separate) Android 11+ package-visibility issue, and tried whitelisting the app viadumpsys deviceidleandam set-standby-bucket. No change. - Built an isolated dummy test app to eliminate any self-install confound, and reproduced the exact same failure on both a stock emulator and real MIUI hardware β ruling out an OEM-specific cause.
The actual root cause showed up in adb shell dumpsys activity broadcasts history-forced:
reason: skipped by policy at enqueue: Background execution not allowed:
receiving Intent { ... PACKAGE_ADDED ... } to
com.wazuh.mobilesentinel/.receivers.AppInstallReceiverreason: skipped by policy at enqueue: Background execution not allowed:
receiving Intent { ... PACKAGE_ADDED ... } to
com.wazuh.mobilesentinel/.receivers.AppInstallReceiverAnd the same exact skip reason was applied, in the same broadcast history dump, to Google's own Play Store, Play Services, and Google Search receivers. This isn't an app bug or an OEM quirk β it's Android's platform-level Background Execution Limits (introduced in Android 8) silently dropping manifest-declared broadcast receivers for any app that isn't in the foreground.
Tracing broadcast delivery through dumpsys β this is what finally pointed at the platform-level restriction instead of an app bug.
The fix: register the receiver dynamically, from inside an active foreground service (MonitoringService, started via startForeground()), instead of declaring it as a static <receiver> in the manifest. A receiver registered on a live process isn't subject to that same restriction. After this change, install events fired reliably and reached the Wazuh dashboard in real time β verified on both the emulator and a real device, installing real Play Store apps.
The rules, and proof they work
The full rule set, 8 posture rules, 3 app-install rules, and the correlation rule:
Each rule was individually verified with wazuh-logtest before trusting it in production. Here's the Play Integrity failure rule firing correctly:
And here's a simulated sideloaded, sensitive-permission app correctly escalating to the highest-severity install rule:
The real proof, though, is real Play Store apps triggering real-time detection on actual hardware not a simulated payload:
These are real installs from the Play Store on my own phone, captured the moment they happened.
What it looks like in the Dashboard
This is the point of the whole pipeline β alerts showing up where a SOC analyst actually works:
USB debugging, Play Integrity failures, sideloaded installs, and the correlation rule β all visible side by side.
Field substitution working end to end: device_id and package_name both populated correctly in the alert detail.
Where it stands
Verified end-to-end: all 11 individual rules plus the correlation rule tested via wazuh-logtest, confirmed live in the Wazuh Dashboard's Threat Hunting view, on both a Pixel 7 emulator (Android 14) and a real Redmi Note 13 (Android 15, HyperOS).
Not yet done, and documented as such:
- Full Play Integrity server-side verification (needs a registered Google Cloud project + Play Console linkage)
- Scheduled/background posture collection beyond the manual trigger (install detection is already real-time)
- iOS β intentionally out of scope
Why write this up
This started as a "can Wazuh even do mobile" question and turned into two genuinely non-obvious platform-level debugging problems that I couldn't find documented together anywhere. If you're building anything similar β a Wazuh integration, or any Android app relying on install broadcasts β I hope the dumpsys activity broadcasts history-forced trick in particular saves someone else the same few hours I spent on it.
Code, README and setup instructions: https://github.com/cyberxperts29-sudo/wazuh-mobile-sentinel
Feedback, issues and PRs welcome β this is meant to grow as a real community contribution, in the spirit of the original Wazuh issue that inspired it.