DeviceGuard SDK

Kotlin Multiplatform SDK for comprehensive device security: fingerprinting, root/jailbreak detection, emulator detection, app integrity, network inspection, remote-control app detection, and surveillance / tampering detection — on Android, iOS, JVM/Desktop, and Web.
📘 Full documentation: https://dongnh311.github.io/DeviceGuardSDK/
Status
Pre-release. APIs are unstable and may change before v0.1.0. See DEVICEGUARD_PLAN.md for the roadmap.
Installation
Coordinates will be published after Phase 9. Placeholder shown below.
dependencies {
implementation("io.github.dongnh311:deviceguard-core:0.1.0")
implementation("io.github.dongnh311:deviceguard-fingerprint:0.1.0")
implementation("io.github.dongnh311:deviceguard-rootcheck:0.1.0")
implementation("io.github.dongnh311:deviceguard-emulator:0.1.0")
implementation("io.github.dongnh311:deviceguard-integrity:0.1.0")
implementation("io.github.dongnh311:deviceguard-network:0.1.0")
implementation("io.github.dongnh311:deviceguard-remote:0.1.0")
implementation("io.github.dongnh311:deviceguard-surveillance:0.1.0")
}
Quick Start
val guard = DeviceGuard.Builder(context)
.enableFingerprint()
.enableRootCheck(strict = true)
.enableEmulatorCheck()
.enableIntegrityCheck(expectedSignature = "…")
.enableNetworkCheck()
.enableRemoteCheck()
.enableSurveillanceCheck()
.build()
val report = guard.analyze()
println("Risk: ${report.riskScore}/100")
println("Threats: ${report.threats}")
println("Device ID: ${report.fingerprint.id}")
Realtime observe() — emit on state change
analyze() gives a one-shot snapshot. For continuously monitoring the device, collect
DeviceGuard.observe(periodMs): it polls at the configured interval and only emits when
the threat set or fingerprint actually changes (same threats, same report → no emission).
guard.observe(periodMs = 5_000L).collect { report ->
updateUi(report)
}
Minimum period is 500 ms — leaves headroom above the 200 ms p95 per-analyze budget.
Detector × platform matrix — what it does and how it works
Legend: ✅ full coverage · ⚠️ partial / best-effort · — not applicable on this platform, or deferred pending a follow-up (cell text clarifies which). Both return DetectionResult.NotApplicable today.
Every detector is optional — only enabled modules run. analyze() fans them out concurrently via coroutineScope + async / awaitAll on the caller's dispatcher, isolating each with a try/catch; the orchestrator merges confidences into a single SecurityReport.riskScore (0–100) and riskLevel (SAFE / LOW / MEDIUM / HIGH / CRITICAL).
Sample apps
sample/ ships a Compose Multiplatform demo across every target. The UI lives once in
sample/shared/ (Material 3, Compose Multiplatform 1.7.x); each platform wrapper is a
thin entry point that hosts the shared App() composable.
Every app shows the same screen: toggle detectors, tap Analyze, inspect the live
SecurityReport — risk score with a coloured progress bar, threat list, fingerprint id,
error list, and end-to-end timing.
End-to-end smoke-verified on 2026-04-20 across all 4 samples with the full
7-detector build enabled:
Desktop headless ./gradlew :sample:desktop:run — report produced;
Android (Pixel 6 API 34 emulator) — 2 threats, 16 signals, MEDIUM risk, no false positives on the new Remote/Surveillance toggles;
iOS (iPhone 16 Simulator, iOS 18) — CRITICAL risk on simulator (expected: Emulator always; ScreenBeingCaptured only when AirPlay / ReplayKit / Control-Center recording is active);
Web (Chromium headless @ jsBrowserProductionWebpack output) — LOW risk, 1 threat, 13 signals, 21 ms elapsed.
Manual QA matrix
Before the first release, each threat path must fire at least once on a real target. The
table below is the checklist for that sweep — tick boxes as devices are exercised.
The v0.1.0 publish (Phase 9) is gated on every row above being confirmed on a real host.
Recording a short video or sharing the Copy JSON report payload per row is enough
evidence — attach to the release notes PR.
Modules
Supported platforms
See the detector × platform matrix above for what each detector does on each OS, including the signal weights and how they're collected.
Fingerprinting
The deviceguard-fingerprint module collects non-PII platform signals and hashes them
(SHA-256 with length-prefixed encoding, no collision risk) into a stable device id.
Signals collected per platform:
No IMEI, advertising id, account name, email, or other directly identifying value is read —
the fingerprint is stable across app launches but invalidates on a factory reset or MAC change.
Root / Jailbreak detection
deviceguard-rootcheck surfaces root on Android and jailbreak on iOS. Opt in via
DeviceGuard.Builder(context).enableRootCheck(strict = true). The detector aggregates
per-signal weights in [0.0, 1.0] and trips RootCheckResult.isRooted at:
strict = false (default): confidence ≥ 0.5 — a single strong signal wins.
strict = true: confidence ≥ 0.2 — any moderate signal wins.
Signals per platform:
When isRooted is true, the detector adds a ThreatType.Root (Android) or
ThreatType.Jailbreak (iOS) threat to SecurityReport.threats with the aggregated
confidence. The indicators list is suitable for forensic logging and contains no PII.
Emulator / Debugger detection
deviceguard-emulator surfaces emulator / virtual-device signals alongside
attached-debugger signals. Opt in via
DeviceGuard.Builder(context).enableEmulatorCheck(). Two disjoint indicator lists feed
two independent confidences (each clamped to [0, 1]) and trip
ThreatType.Emulator / ThreatType.DebuggerAttached independently at a fixed 0.5
threshold — so a debug build attached to an IDE on a real device produces a debugger
threat but not an emulator threat, and vice versa.
Signals per platform:
When a threat fires, EmulatorCheckResult carries separate emulatorIndicators and
debuggerIndicators lists so consumers don't need to parse prefixed strings to route
forensic logging. Unlike root detection, no strict knob is exposed — the primary
signals are deterministic (weight 1.0) and the threshold is a fixed 0.5.
App integrity
deviceguard-integrity surfaces app-tampering and dynamic-instrumentation signals. Opt in
via DeviceGuard.Builder(context).enableIntegrityCheck(expectedSignature, trustedInstallers).
Two disjoint indicator streams feed two independent confidences at a fixed 0.5 threshold:
ThreatType.SignatureMismatch and ThreatType.HookFramework.
val guard = DeviceGuard.Builder(context)
.enableIntegrityCheck(
expectedSignature = "1A:2B:3C:…",
trustedInstallers = setOf("com.android.vending"),
)
.build()
Signals per platform:
IntegrityCheckResult exposes , ,
, confidences, and separate indicator lists. — on platforms that skip the signature stream both fields are false, and the
detector advertises this via the signal. The
string is accepted in -style hex with
whitespace; the detector normalises to lowercase packed hex before comparison.
VPN / proxy detection
deviceguard-network surfaces active VPN tunnels and HTTP/SOCKS proxy routing. Opt in via
DeviceGuard.Builder(context).enableNetworkCheck(). Two disjoint indicator streams feed two
independent confidences at a fixed 0.5 threshold: ThreatType.VpnActive and
ThreatType.ProxyActive.
VPN / proxy presence is not inherently hostile — corporate deployments, privacy-focused
users, and ISPs all legitimately terminate on tunnels — so the emitted threats carry
intentionally low default weights. The detector surfaces the state; the risk-scoring
strategy decides how much it matters.
Signals per platform:
NetworkCheckResult carries vpnActive, proxyActive, confidences, and separate
vpnIndicators / proxyIndicators lists so consumers don't parse prefixes. Interface
matching is case-insensitive (TAP0 on Windows and utun3 on macOS both count).
Building
./gradlew build
./gradlew allTests
./gradlew dokkaHtml
./gradlew koverHtmlReport
./gradlew koverXmlReport
Testing & coverage
Tests run at two layers:
- Common logic — orchestrator, scoring, serialization, threat/result validation, and every
detector's threshold + threat-emission paths live in each module's source set
and execute on JVM, JS, Android unit, and iOS simulator runners.
Coverage reports are produced by the Kover plugin
and aggregated at the root. After ./gradlew koverHtmlReport, open
build/reports/kover/html/index.html.
A JVM p95 latency benchmark
(deviceguard-core/src/jvmTest/.../AnalyzePerfBenchmarkJvmTest.kt) guards the orchestrator's
overhead against the SDK's 200ms p95 budget on a mid-tier device. It runs synthetic stub
detectors on Dispatchers.Default, so regressions that drop detector parallelism or add
per-run blocking work fail the suite.
Contributing
See CONTRIBUTING.md for dev setup and the PR workflow. Participation is
governed by our Code of Conduct. Release history is tracked in
CHANGELOG.md.
Security
Do not file a public issue for security vulnerabilities. Follow the private disclosure
process in SECURITY.md.
License
Apache License 2.0 — see LICENSE.