guard_kit 0.1.0
guard_kit: ^0.1.0 copied to clipboard
Mobile security plugin: root/jailbreak detection, developer mode detection, emulator detection, SSL certificate pinning, and screenshot prevention.
import 'package:flutter/material.dart';
import 'package:guard_kit/guard_kit.dart';
void main() => runApp(const GuardKitExampleApp());
class GuardKitExampleApp extends StatelessWidget {
const GuardKitExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'guard_kit Example',
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
),
// Wrap entire app in ScreenshotBlocker to protect all screens
home: ScreenshotBlocker(
guard: GuardKit.create(),
child: const SecurityDashboard(),
),
);
}
}
// ── Security Dashboard ────────────────────────────────────────────────────────
class SecurityDashboard extends StatefulWidget {
const SecurityDashboard({super.key});
@override
State<SecurityDashboard> createState() => _SecurityDashboardState();
}
class _SecurityDashboardState extends State<SecurityDashboard> {
// DI: guard injected; can be replaced with a mock in widget tests
late final GuardKit _guard;
SecurityStatus? _status;
bool _loading = true;
String? _sslResult;
bool _screenshotBlocking = true;
@override
void initState() {
super.initState();
_guard = GuardKit.create();
_loadSecurityStatus();
}
Future<void> _loadSecurityStatus() async {
setState(() => _loading = true);
try {
final status = await _guard.getSecurityStatus();
if (mounted) setState(() { _status = status; _loading = false; });
} catch (e) {
if (mounted) setState(() => _loading = false);
}
}
Future<void> _testSslPinning() async {
setState(() => _sslResult = 'Testing…');
try {
// Example: use a real fingerprint in production
final client = await _guard.createPinnedClient(
const FingerprintPinningConfig(sha256Fingerprints: [
// Replace with the actual SHA-256 fingerprint of your server's cert
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
]),
);
// This will fail with the placeholder fingerprint — expected behaviour
await client.get(Uri.parse('https://example.com'));
client.close();
if (mounted) setState(() => _sslResult = 'Connection succeeded');
} on GuardKitException catch (e) {
if (mounted) setState(() => _sslResult = 'Guard error: ${e.message}');
} catch (e) {
// HandshakeException expected with wrong fingerprint
if (mounted) setState(() => _sslResult = 'Pinning active (${e.runtimeType})');
}
}
Future<void> _toggleScreenshotBlocking(bool value) async {
await _guard.setScreenshotBlocking(enabled: value);
if (mounted) setState(() => _screenshotBlocking = value);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('guard_kit'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadSecurityStatus,
tooltip: 'Refresh',
),
],
),
body: _loading
? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.all(16),
children: [
_RiskBanner(status: _status),
const SizedBox(height: 16),
const _SectionHeader('Device Checks'),
_CheckCard(
label: 'Root / Jailbreak',
value: _status?.isRooted,
dangerWhenTrue: true,
),
_CheckCard(
label: 'Developer Mode',
value: _status?.isDeveloperModeEnabled,
dangerWhenTrue: true,
),
_CheckCard(
label: 'Emulator / Simulator',
value: _status?.isRunningOnEmulator,
dangerWhenTrue: true,
),
const SizedBox(height: 16),
const _SectionHeader('Screenshot Blocking'),
SwitchListTile(
title: const Text('Block screenshots'),
subtitle: const Text('Uses FLAG_SECURE / UITextField layer trick'),
value: _screenshotBlocking,
onChanged: _toggleScreenshotBlocking,
),
const SizedBox(height: 16),
const _SectionHeader('SSL Pinning'),
ListTile(
title: const Text('Test SSL pinning'),
subtitle: Text(_sslResult ?? 'Tap to test'),
trailing: FilledButton(
onPressed: _testSslPinning,
child: const Text('Test'),
),
),
],
),
);
}
}
// ── Reusable UI Components ────────────────────────────────────────────────────
class _RiskBanner extends StatelessWidget {
final SecurityStatus? status;
const _RiskBanner({required this.status});
@override
Widget build(BuildContext context) {
if (status == null) return const SizedBox.shrink();
final (label, color) = switch (status!.riskLevel) {
SecurityRiskLevel.none => ('No threats detected', Colors.green),
SecurityRiskLevel.low => ('Low risk', Colors.orange),
SecurityRiskLevel.medium => ('Medium risk', Colors.deepOrange),
SecurityRiskLevel.high => ('High risk — multiple threats', Colors.red),
};
return Card(
color: color.withValues(alpha: 0.15),
child: ListTile(
leading: Icon(Icons.shield, color: color, size: 32),
title: Text(
label,
style: TextStyle(color: color, fontWeight: FontWeight.bold),
),
subtitle: Text('Risk level: ${status!.riskLevel.name.toUpperCase()}'),
),
);
}
}
class _SectionHeader extends StatelessWidget {
final String title;
const _SectionHeader(this.title);
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text(
title,
style: Theme.of(context)
.textTheme
.titleSmall
?.copyWith(color: Theme.of(context).colorScheme.primary),
),
);
}
class _CheckCard extends StatelessWidget {
final String label;
final bool? value;
final bool dangerWhenTrue;
const _CheckCard({
required this.label,
required this.value,
this.dangerWhenTrue = false,
});
@override
Widget build(BuildContext context) {
final isDanger = dangerWhenTrue ? (value ?? false) : !(value ?? true);
final color = value == null
? Colors.grey
: isDanger
? Colors.red
: Colors.green;
final icon = value == null
? Icons.help_outline
: isDanger
? Icons.warning_amber_rounded
: Icons.check_circle_outline;
final valueText = value == null ? '—' : (value! ? 'YES' : 'NO');
return Card(
child: ListTile(
leading: Icon(icon, color: color),
title: Text(label),
trailing: Text(
valueText,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
),
),
),
);
}
}