A structured mobile security baseline framework for Flutter applications.
Kryvon provides a guard-based runtime security layer that runs checks in parallel, aggregates risk across multiple threat signals, and enforces policy through a configurable enforcement strategy — all with a minimal API surface.
Platform support: Android only. iOS support is not currently implemented.
- Root detection — 6 native indicators (su binary, su execution, dangerous props, writable system, known root apps, test keys)
- Debugger detection — 5 native signals (tracerPid, JDWP, Android debugger, system-debuggable flag, debuggable app flag)
- Hook detection — Frida (process names, ports 27042/27043, memory maps), Xposed (reflection), Substrate (library maps)
- Emulator detection — QEMU, Genymotion, generic Android emulator fingerprints
- Integrity detection — APK signing certificate SHA-256 verification
- Parallel guard execution — all guards run concurrently via
Future.wait - Per-type enforcement — immediate
blockAppon hook/tamper,restrictFeatureson root - Risk aggregation — per-type flat weights combined into a final severity
- Hardened transport — nonce-validated
MethodChannelviaSecureRuntimeBridge; any failure → compromised - Fail-secure — any guard failure is treated as
hookDetected/critical - Extensible — implement
Guardto add custom detectors
Add to your pubspec.yaml:
dependencies:
kryvon: ^0.3.0Then run:
flutter pub getimport 'package:kryvon/kryvon.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
Kryvon.initialize(
policy: KryvonPolicy(
blockThreshold: ThreatSeverity.high,
enforcementStrategy: EnforcementStrategy.emitOnly,
onThreat: (event) {
print('Threat: ${event.type.name} [${event.severity.name}]');
},
),
);
await Kryvon.runChecks();
runApp(const MyApp());
}For high-security applications, use the pre-configured fintech policy:
Kryvon.initialize(policy: KryvonPolicy.fintech());
await Kryvon.runChecks();KryvonPolicy.fintech() blocks at ThreatSeverity.medium and terminates the app when exceeded.
To verify APK signing integrity, pass your expected certificate SHA-256:
Kryvon.initialize(
policy: KryvonPolicy(
expectedSignatureSha256: 'YOUR_CERT_SHA256_HERE',
blockThreshold: ThreatSeverity.high,
enforcementStrategy: EnforcementStrategy.blockApp,
onThreat: (event) => print('${event.type.name}: ${event.severity.name}'),
),
);The static entry point. All interaction goes through this class.
| Method | Description |
|---|---|
Kryvon.initialize({required policy, logLevel}) |
Configure and start Kryvon. Auto-registers all five built-in guards. |
Kryvon.registerGuard(guard) |
Add a custom Guard to the pipeline. Call after initialize. |
Kryvon.runChecks() |
Run all guards in parallel, aggregate risk, and enforce policy. |
Controls detection thresholds and enforcement behaviour.
KryvonPolicy({
ThreatSeverity blockThreshold, // default: ThreatSeverity.high
EnforcementStrategy enforcementStrategy, // default: EnforcementStrategy.emitOnly
ThreatHandler? onThreat, // callback per individual ThreatEvent
String? expectedSignatureSha256, // APK signing certificate SHA-256
})strategyForType(ThreatType) returns the enforcement strategy for a given threat type. The built-in defaults are:
| ThreatType | Strategy |
|---|---|
hookDetected |
blockApp |
integrityFailure |
blockApp |
rootDetected |
restrictFeatures |
| all others | enforcementStrategy (from policy) |
| Factory | Description |
|---|---|
KryvonPolicy.fintech() |
blockThreshold: medium, enforcementStrategy: terminateApp |
| Value | Description |
|---|---|
low |
Informational; no immediate risk |
medium |
Moderate risk; worth monitoring |
high |
Significant risk; consider blocking |
critical |
Severe risk; enforce immediately |
| Value | Description |
|---|---|
rootDetected |
Device root indicators found |
debuggerDetected |
Debugger or debug signal active |
hookDetected |
Frida, Xposed, or Substrate instrumentation detected |
emulatorDetected |
App running in an emulator |
integrityFailure |
APK signing certificate mismatch |
insecureStorage |
Sensitive data in insecure location (not yet implemented) |
networkPinningFailure |
Certificate/key pinning failed (not yet implemented) |
deviceCompromised |
Synthetic aggregate event emitted after all guards complete |
| Value | Description |
|---|---|
emitOnly |
Log and call onThreat; no further action |
terminateApp |
Call exit(1) after logging |
blockApp |
Immediate exit(1) — used for hook and integrity violations |
restrictFeatures |
Signal via callback; host app gates features accordingly |
Hook and integrity violations trigger blockApp immediately — before the risk aggregator runs.
Pass to Kryvon.initialize via the logLevel parameter.
| Value | Description |
|---|---|
debug |
Verbose — guard lifecycle events |
info |
General operational messages (default) |
warning |
Non-fatal issues |
error |
Guard or runtime failures |
silent |
No output |
RootGuard delegates to the native RootDetector.kt over the obfuscated method channel. Six indicators are checked and mapped to severity:
| Indicator | Severity |
|---|---|
suExecution |
critical |
suBinary |
high |
dangerousProps |
high |
writableSystem |
high |
knownRootApp |
medium |
testKeys |
medium |
The highest-priority indicator determines the event severity.
DebuggerGuard delegates to the native DebuggerDetector.kt. Five signals are checked and mapped to severity:
| Signal | Severity |
|---|---|
tracerPid |
critical |
androidDebugger |
high |
systemDebuggable |
high |
jdwpEnabled |
medium |
debuggableApp |
medium |
HookGuard delegates to HookDetector.kt and checks for active instrumentation frameworks:
| Signal | Framework |
|---|---|
Process names in /proc/*/cmdline |
Frida |
| Open ports 27042 / 27043 | Frida |
Library names in /proc/self/maps |
Frida / Substrate |
de.robv.android.xposed class via reflection |
Xposed |
Any positive indicator emits hookDetected/critical. Detection failure is also treated as hookDetected/critical (fail-secure).
EmulatorGuard delegates to EmulatorDetector.kt and checks:
| Signal | Description |
|---|---|
Build.FINGERPRINT |
Generic or unknown fingerprint |
ro.hardware getprop |
QEMU hardware |
Build fields |
Emulator-specific values (e.g. BUILD_ID, MODEL) |
| QEMU device files | /dev/socket/qemud, /dev/qemu_pipe |
| Genymotion properties | ro.product.device contains vbox |
IntegrityGuard delegates to IntegrityDetector.kt and verifies the APK signing certificate:
- Retrieves the signing certificate via
PackageManager - Computes its SHA-256 fingerprint
- Compares (constant-time) against
KryvonPolicy.expectedSignatureSha256
If the value is not set in policy, the check is skipped (no event emitted). A mismatch emits integrityFailure/critical and triggers immediate blockApp.
After all guards complete, RuntimeRiskAggregator combines their events into a single ThreatType.deviceCompromised event using per-type flat weights:
| ThreatType | Weight |
|---|---|
hookDetected |
50 |
integrityFailure |
50 |
rootDetected |
30 |
debuggerDetected |
20 |
emulatorDetected |
20 |
The highest weight across all detected threats determines the aggregated severity:
| Score | Severity |
|---|---|
| ≥ 50 | critical |
| ≥ 30 | high |
| ≥ 20 | medium |
| < 20 | low |
KryvonPolicy.shouldBlock is evaluated against this aggregated event. Individual events still fire onThreat as they are produced.
Note: Hook and integrity violations bypass aggregation —
blockAppfires immediately on the individual event.
All native calls go through SecureRuntimeBridge (default: MethodChannelBridge):
- A 16-byte cryptographically random nonce is attached to every outgoing request (
Random.secure) - The native side must echo the same nonce in its response
- Nonce mismatch, null response, or any exception →
{'__compromised': true} - The channel name is XOR-obfuscated at the source level to impede static analysis
To inject a custom bridge (e.g. in tests):
RuntimeChannel.initialize(MyMockBridge());
Kryvon.initialize(policy: ...);Implement the Guard interface to add your own detectors:
class NetworkPinningGuard implements Guard {
@override
Future<List<ThreatEvent>> check() async {
final pinningValid = await _verifyPins();
if (pinningValid) return [];
return [
ThreatEvent(
type: ThreatType.networkPinningFailure,
severity: ThreatSeverity.high,
),
];
}
}
// Register after initialize:
Kryvon.registerGuard(NetworkPinningGuard());Guards must not throw — any unhandled exception is caught by the runtime and treated as hookDetected/critical (fail-secure).
Kryvon.runChecks()
│
├── RootGuard.check() ──► RootDetector ──► RootDetector.kt
├── DebuggerGuard.check() ──► DebuggerDetector ──► DebuggerDetector.kt
├── HookGuard.check() ──► HookDetector ──► HookDetector.kt
├── EmulatorGuard.check() ──► EmulatorDetector ──► EmulatorDetector.kt
├── IntegrityGuard.check() ──► IntegrityDetector ──► IntegrityDetector.kt
└── [custom guards] ...
│
▼ (Future.wait — parallel)
List<ThreatEvent>
│
├── hook/integrity event? → blockApp immediately (exit)
│
├── onThreat(event) × N (per individual event)
│
▼
RuntimeRiskAggregator.aggregate()
│
▼
ThreatEvent(type: deviceCompromised, severity: aggregated)
│
KryvonPolicy.shouldBlock?
│
▼
EnforcementExecutor.execute(strategy, event)
All native calls pass through SecureRuntimeBridge (nonce-validated). Any bridge failure returns __compromised: true and is treated as hookDetected/critical.
- Flutter
>=3.3.0 - Dart SDK
^3.9.2 - Android only