aegis_guardian 3.1.4
aegis_guardian: ^3.1.4 copied to clipboard
Native Android and iOS runtime security checks, device signing, screen privacy, and Apple App Attest for Flutter.
AegisGuardian for Flutter #
Flutter plugin 3.1.4 wrapping AegisGuardian Android 3.1.0 (Maven Central) and iOS 3.1.3 (CocoaPods or SwiftPM). Android API 24+, iOS 15+. The iOS binary is built and tested with Xcode 27 (Swift 6.4); older Xcode versions are untested. Development and validation use Flutter 3.44.4 / Dart 3.12.2. Only Android and iOS are registered.
Local security checks work offline. Aegis Sentinel monitoring (Android Play Integrity plus an enrolled device key, iOS App Attest) needs server configuration and an enrollment ID, which this Flutter API cannot create yet; see the verified telemetry contract.
Limitations of the Flutter preview #
The plugin exposes a subset of the native SDKs. Implemented does not mean verified on physical devices or against real attacks. See the support matrix for individual APIs and VERIFICATION.md for test results.
Platform and native API differences #
- iOS screenshots cannot be blocked. Screen privacy uses an inactive/capture overlay; capture notifications can arrive after a frame. Android uses
FLAG_SECURE. - USB debugging and developer-options checks are unsupported on iOS. Their report status is
unsupported, notclear. - App identity checks differ: Android compares the APK signing certificate; iOS compares the bundle identifier only. These checks do not provide equivalent tamper protection.
- Attestation is platform-specific. Android key-attestation challenges/certificate chains are not Apple App Attest. iOS rejects
attestationChallenge, returns an empty certificate chain, and exposes separate App Attest helpers. App Attest calls on Android returnunsupported. - Key lookup on iOS:
hasDeviceKey()andgetDevicePublicKey()returnunsupportedbecause the current native SDK has no non-mutating lookup API. Obtain the public key fromcreateDeviceKey()instead; it creates a key if absent. - Reset/delete on Android:
resetDeviceId()anddeleteDeviceKey()returnunsupportedbecause these operations are not exposed by the current native SDK. Also note that AndroidcreateDeviceKey()replaces the existing key, while iOS reuses it. - Web and desktop are unsupported. The plugin is registered only for Android and iOS.
Native features not wrapped in version 3.1 #
OkHttp/URLSession SPKI pinning, the Encryption Gateway client, native tapjacking/touch filtering, and native memory-wiping helpers are not exposed through this Flutter API. Native builder/strict-policy callbacks are represented by explicit Dart policy evaluation and screen-protection calls, rather than a direct wrapper of those APIs.
Installing the plugin does not protect Dio or Dart http traffic automatically. Networking needs a separate integration. Dart buffers and platform-channel copies also prevent this wrapper from guaranteeing native-style memory wiping. Missing wrappers are implementation gaps; they do not mean Flutter cannot call those native features.
Verification and physical hardware testing #
Automated integration tests passed on physical hardware (Huawei MIS-LX9, Android 12) and emulators (API 35), and on a physical iPhone 11 (Apple A13 Bionic, iOS 27) and iOS 26.5 simulator. Clean stock baselines, hardware key attestation / Keychain device IDs, screen privacy toggles, and AOT release builds have been verified on hardware. See VERIFICATION.md for full test results. Items that remain to be verified include:
- Real App Attest live enrollment/assertions with configured entitlements and a backend verifier.
- Detection effectiveness against live malicious tools (physical devices are preserved clean as uncompromised baselines).
- Runtime compatibility on the minimum supported Android API 24 and iOS 15 physical hardware.
Unsupported, unavailable, or unconfigured checks must not be treated as clean results. evaluatePolicy() blocks when a required check is not clear; Android's native SDK still has the probe-failure reporting limitation described below.
Install #
flutter pub add aegis_guardian
For development from this repository, use a local dependency:
dependencies:
aegis_guardian:
path: /path/to/aegis-guardian/flutter/aegis_guardian
The Android build resolves aegis-guardian 3.1.0 from Maven Central. iOS resolves AegisGuardian 3.1.2 using CocoaPods or aegis-guardian-ios-binary 3.1.2 using SwiftPM. Native SDK source is not duplicated in the Flutter package. The native iOS dependency includes its privacy manifest.
Inspect and evaluate policy #
import 'package:aegis_guardian/aegis_guardian.dart';
final guardian = AegisGuardian();
try {
final report = await guardian.scan();
final decision = report.evaluatePolicy({
SecurityCheck.root,
SecurityCheck.frida,
SecurityCheck.hooking,
});
if (!decision.allowed) {
// Apply your app's policy. Verify integrity on your backend too.
}
} catch (error) {
// Inspection unavailable: do not treat a failed call as a clean device.
}
Use scan(expectedSignatureSha256: fingerprint) on Android (64 hex characters, with or without colons) or scan(expectedBundleIdentifier: bundleId) on iOS. Supplying the other platform's option raises PlatformException(code: 'unsupported'). Bundle ID comparison does not provide Android-style APK signature verification.
Reports contain the same ten check names and five statuses on Android, iOS, and Flutter: clear, detected, unavailable, notConfigured, and unsupported. The shared check names are root, emulator, debugger, frida, hooking, proxy, vpn, appIdentity, usbDebugging, and developerOptions. iOS marks Android-only checks unsupported; local premium Frida checks are unavailable; omitted identity expectations are notConfigured. Android's legacy detectors remain best-effort and cannot distinguish every failed probe from a negative finding. Flutter's isSecure is derived from normalized statuses: detected, unavailable, missing, and configured-but-unchecked results fail; unsupported platform checks and an unconfigured optional identity check are skipped. It is not cryptographic proof. A required unsupported/unavailable/unconfigured check blocks evaluatePolicy.
Aegis Sentinel premium security #
AegisGuardian() remains offline without sentinelConfig. Basic checks continue to work, while Sentinel Frida, server-authorized app identity/MOD, and blacklist results require verified enrollment:
final guardian = AegisGuardian();
final report = await guardian.scan(); // local only
To opt in, enroll the device with Sentinel first. Supply the returned enrollment ID and the platform evidence configuration as well as the dashboard URL and public Client ID. Android uses a Play Cloud project number; iOS uses the registered App Attest key ID.
final guardian = AegisGuardian(
sentinelConfig: SentinelConfig(
baseUrl: Uri.parse('https://sentinel.example.com'),
clientId: 'client-id',
enrollmentId: registeredEnrollmentId,
cloudProjectNumber: androidCloudProjectNumber, // Android
appAttestKeyId: registeredAppAttestKeyId, // iOS
),
);
final result = await guardian.scanAndMonitor(
userId: currentUserId,
sessionId: currentSessionId,
);
result.fold(
onSuccess: (assessment) {
if (assessment.isBlacklisted) showBlacklistedStatus(assessment.action);
else showConnectedStatus(assessment.riskTier);
},
onFailure: showTelemetryUnavailable, // Safe to ignore; request failure is a value.
);
scan() remains local. It marks premium Frida checks unavailable; a supplied Android signing fingerprint or iOS bundle ID can still produce a local diagnostic identity result. scanAndMonitor() requires verified enrollment and platform evidence, then validates the Client ID, active subscription, package/bundle ID and certificate policy on Sentinel before returning authoritative premium results. It returns SentinelResult<SentinelAssessment>; the convenience premium methods return SentinelResult<bool>. Network errors, invalid configuration, unknown Client IDs, and server rejections are failures in the result value. Ignoring a failed result does not throw or stop the caller's task. The Android plugin includes android.permission.INTERNET; iOS needs no equivalent permission and uses HTTPS under App Transport Security.
Use SentinelResult.failureCode for a stable category when a request fails. The 3.1.0 migration guide includes examples for each platform.
The Client Secret field exists for configuration compatibility but is intentionally never transmitted or used by the app. Keep it on a trusted backend for /api/v1/assess; Flutter telemetry uses the public Client ID.
Identity and signing #
Device ID is already available in the published API. It does not require App Attest, key enrollment, or a backend to read:
final deviceId = await AegisGuardian().getDeviceId();
The example app's Read device ID button displays the identifier independently of the security scan and reports native errors without retaining a stale ID. Android returns a 64-character SHA-256 hex identifier; iOS returns an app-scoped Keychain UUID. These are not a shared identifier across platforms/apps, and permanence after reinstall, data clearing, or factory reset is not guaranteed.
import 'dart:typed_data';
final id = await guardian.getDeviceId();
final enrollment = await guardian.createDeviceKey();
final signature = await guardian.sign(Uint8List.fromList([1, 2, 3]));
// Send enrollment.publicKey and signature to your backend for verification.
- Public keys are P-256 X.509 SubjectPublicKeyInfo DER; signatures are SHA-256/ECDSA ASN.1 DER on both platforms.
- Android:
createDeviceKeyreplaces the existing native signing key. Enroll intentionally, not on every launch. Supply a fresh backendattestationChallenge(1..128 bytes) for Android Key Attestation and send the returned leaf-first DER certificate chain to the server. A returned chain does not guarantee hardware attestation; the SDK can fall back to a plain key. Verify trust, challenge and app identity on the server. - iOS:
createDeviceKeyreuses the key if present and requires Secure Enclave by default. The certificate chain is empty; Secure Enclave signing is not App Attest. ExplicitrequireSecureEnclave: falseon both create/sign permits development software keys on a simulator. There is no silent downgrade. - iOS identity/signing use a separate namespace derived from the host bundle ID plus
.aegis.flutter. Existing native app keys are not automatically migrated. Android uses the existing native SDK key alias. - Device IDs have platform-specific lifetimes. They are identifiers, not authentication credentials.
Screen privacy #
Call await guardian.setScreenProtection(true) after the Flutter window is attached; pass false to release the plugin's protection. Android sets FLAG_SECURE on the current Activity and reapplies it after configuration changes. The plugin preserves a flag already set by the host. Coordinate ownership if another component also changes the same flag after enabling this plugin.
iOS uses the native inactive/capture overlay on the current Flutter window. It cannot prevent screenshots, and capture notification can arrive after a frame. Enable separately for each engine/window you use; multi-window transitions require device testing. Screen operations remain on the main thread; scan/key operations run on a serial worker queue.
Apple App Attest #
The iOS helpers are isAppAttestSupported, generateAppAttestKey, attestAppAttestKey and generateAppAttestAssertion. Configure the host's App Attest entitlement and backend first. Persist your key ID, and pass a 32-byte SHA-256 hash of server-bound client data for attestation/assertion. The plugin does not invent challenges, configure entitlements, or verify server trust. Android calls return unsupported.
Support matrix #
“Implemented” means bridged; see VERIFICATION.md for what actually ran.
| Function | Android | iOS |
|---|---|---|
| Scan root/jailbreak, emulator/simulator, debugger, Frida, hooking, VPN, proxy | Implemented | Implemented |
| App identity | APK signing certificate SHA-256 | Bundle identifier only |
| USB debugging, developer options | Implemented | unsupported |
| Device ID | Native Widevine/ANDROID_ID/fallback | App-scoped Keychain UUID |
| Create key / sign | Android Keystore | Secure Enclave; explicit development fallback |
| Key attestation challenge / certificate chain | Implemented | unsupported challenge; empty chain |
hasDeviceKey, getDevicePublicKey |
Implemented | unsupported; use create result for public key |
deleteDeviceKey, resetDeviceId |
unsupported by current native API |
Implemented |
| Policy evaluation | Dart policy over native report | Dart policy over native report |
| Screen protection | FLAG_SECURE | Inactive/capture overlay, no screenshot prevention |
| App Attest | unsupported |
Helpers implemented; requires device/backend setup |
| Play Integrity as Sentinel v2 evidence | Implemented; requires cloud project and server verification | Not applicable |
| App Attest as Sentinel v2 evidence | Not applicable | Implemented; requires entitlement and server enrollment |
| OkHttp / URLSession SPKI pinning | Not wrapped in 3.1 | Not wrapped in 3.1 |
| Encryption Gateway client | Not wrapped in 3.1 | No matching native module in this SDK |
| Native tapjacking/touch filtering | Not wrapped in 3.1 | No equivalent exposed |
| Native memory wiping | Not exposed to Dart | No equivalent exposed |
| Native builder / strict callback APIs | Use Dart policy and explicit screen calls | Use Dart policy and explicit screen calls |
Networking through Dio or Dart http is not automatically routed through OkHttp/URLSession and is not protected by installing this package. Adding native pinning alone would not cover that traffic. Managed Dart buffers/platform-channel copies also cannot provide native memory-wiping guarantees. Web/desktop are not supported and calls raise MissingPluginException.
Test #
flutter pub get
flutter analyze
flutter test
cd example
flutter test
flutter test integration_test/plugin_integration_test.dart -d DEVICE_ID
flutter build apk --debug
flutter build ios --release --no-codesign
The integration command builds and runs on the selected simulator/device. On the tested Xcode 27 setup, generic dual-architecture simulator builds need the ARM64 workaround described in VERIFICATION.md. The unsigned iOS release build validates compilation only.
Use the disposable example application for integration tests: they replace Android signing keys and reset/delete iOS example identities/keys. The integration test independently verifies native signatures with PointyCastle and rejects altered messages; these dependencies are test-only.
The example demonstrates scanning and screen privacy. Beyond the Android emulator screenshot check, live App Attest, hardware attestation trust, threat injection, physical-device screenshot behavior and background/foreground behavior require additional configured-device testing; mocks are not evidence that these protections work on a device.
Plugin architecture follows Flutter platform channels.