Agent skill
security-altrupets-monorepo
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/security-altrupets-monorepo
SKILL.md
🔒 Skill: Security Best Practices
📋 Metadata
| Atributo | Valor |
|---|---|
| ID | flutter-security |
| Nivel | 🔴 Avanzado |
| Versión | 1.0.0 |
| Keywords | security, obfuscation, certificate-pinning, secure-storage, encryption, biometric |
| Referencia | OWASP Mobile Security |
🔑 Keywords para Invocación
Usa cualquiera de estos keywords en tus prompts para invocar este skill:
securityobfuscationcertificate-pinningsecure-storageencryptionbiometricssl-pinningroot-detection@skill:security
Ejemplos de Prompts
Implementa security best practices con certificate pinning y secure storage
Configura code obfuscation y protección contra root/jailbreak
@skill:security - Setup completo de seguridad para producción
Necesito biometric authentication y encrypted storage
Protege API keys y implementa SSL pinning
📖 Descripción
⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.
Security en Flutter apps requiere múltiples capas de protección: desde code obfuscation y certificate pinning hasta secure storage y biometric authentication. Este skill cubre las mejores prácticas y técnicas esenciales para proteger aplicaciones Flutter en producción.
✅ Cuándo Usar Este Skill
- Apps en producción con datos sensibles
- Aplicaciones financieras/bancarias
- Apps con información médica (HIPAA compliance)
- E-commerce con datos de pago
- Apps empresariales con datos corporativos
- Cualquier app que maneje PII (Personally Identifiable Information)
- Apps con autenticación de usuarios
- Comunicación con APIs privadas
❌ Cuándo NO Usar Este Skill
- Prototipos internos sin datos reales
- Apps sin datos sensibles
- Aplicaciones de contenido público
- MVPs en fase de validación
Importante: Incluso apps "simples" deben implementar seguridad básica. Este skill ofrece niveles progresivos de seguridad.
🏗️ Estructura del Proyecto
my_app/
├── lib/
│ ├── core/
│ │ ├── security/
│ │ │ ├── secure_storage_service.dart
│ │ │ ├── encryption_service.dart
│ │ │ ├── certificate_pinning.dart
│ │ │ ├── biometric_service.dart
│ │ │ ├── root_detection.dart
│ │ │ └── api_key_manager.dart
│ │ ├── network/
│ │ │ ├── secure_http_client.dart
│ │ │ └── ssl_pinning_interceptor.dart
│ │ └── config/
│ │ ├── environment.dart
│ │ └── secrets.dart.example
│ │
│ ├── services/
│ │ └── auth_service.dart
│ │
│ └── main.dart
│
├── android/
│ ├── app/
│ │ ├── proguard-rules.pro
│ │ └── build.gradle
│ └── key.properties.example
│
├── ios/
│ └── Runner/
│ └── Info.plist
│
├── assets/
│ └── certificates/
│ ├── cert.pem
│ └── README.md
│
├── .env.example
└── scripts/
└── obfuscate.sh
📦 Dependencias Requeridas
dependencies:
flutter:
sdk: flutter
# Secure Storage
flutter_secure_storage: ^9.0.0
# Encryption
encrypt: ^5.0.3
crypto: ^3.0.3
# Biometric Authentication
local_auth: ^2.1.7
# Certificate Pinning
dio: ^5.4.0
# Root/Jailbreak Detection
flutter_jailbreak_detection: ^1.10.0
# Environment Variables
flutter_dotenv: ^5.1.0
# Platform Security
flutter_windowmanager: ^0.2.0 # Android screenshot prevention
# Utilities
uuid: ^4.2.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
💻 Implementación
1. Code Obfuscation
1.1 Configuración Android
// android/app/build.gradle
android {
buildTypes {
release {
// Enable obfuscation
minifyEnabled true
shrinkResources true
// ProGuard configuration
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// Additional security
debuggable false
jniDebuggable false
renderscriptDebuggable false
// Signing config
signingConfig signingConfigs.release
}
}
}
# android/app/proguard-rules.pro
# Flutter wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
# Preserve generic signatures for serialization
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes EnclosingMethod
# Keep native methods
-keepclasseswithmembernames class * {
native <methods>;
}
# Obfuscate everything else
-repackageclasses ''
-allowaccessmodification
# Keep line numbers for stack traces
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
# Remove logging in production
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
1.2 Configuración iOS
<!-- ios/Runner/Info.plist -->
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<!-- Prevent debugging -->
<key>UIFileSharingEnabled</key>
<false/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
1.3 Flutter Obfuscation
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Build con obfuscation (Android)
cd mobile
flutter build apk --obfuscate --split-debug-info=build/app/outputs/symbols
cd ..
# Build con obfuscation (iOS)
cd mobile
flutter build ipa --obfuscate --split-debug-info=build/ios/outputs/symbols
cd ..
# Build AAB para Play Store
cd mobile
flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release
cd ..
1.4 Script de Automatización
#!/bin/bash
# scripts/obfuscate.sh
echo "🔒 Building with obfuscation..."
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
PLATFORM=$1
VERSION=$(grep 'version:' mobile/pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
if [ -z "$PLATFORM" ]; then
echo "Usage: ./scripts/obfuscate.sh [android|ios|all]"
exit 1
fi
build_android() {
echo "📱 Building Android..."
cd mobile
flutter build appbundle \
--obfuscate \
--split-debug-info=build/android/symbols/$VERSION \
--release
cd ..
echo "✅ Android build complete: build/app/outputs/bundle/release/"
}
build_ios() {
echo "🍎 Building iOS..."
cd mobile
flutter build ipa \
--obfuscate \
--split-debug-info=build/ios/symbols/$VERSION \
--release
cd ..
echo "✅ iOS build complete: build/ios/ipa/"
}
case $PLATFORM in
android)
build_android
;;
ios)
build_ios
;;
all)
build_android
build_ios
;;
*)
echo "Invalid platform: $PLATFORM"
exit 1
;;
esac
echo "🎉 Obfuscated builds complete!"
# Uso:
# ./scripts/obfuscate.sh android
# ./scripts/obfuscate.sh ios
# ./scripts/obfuscate.sh all
2. Secure Storage
2.1 Secure Storage Service
// lib/core/security/secure_storage_service.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:injectable/injectable.dart';
@singleton
class SecureStorageService {
final FlutterSecureStorage _storage;
SecureStorageService()
: _storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
// Reset on error
resetOnError: true,
),
iOptions: IOSOptions(
accessibility: KeychainAccessibility.first_unlock_this_device,
),
);
// Generic read
Future<String?> read(String key) async {
try {
return await _storage.read(key: key);
} catch (e) {
print('Error reading from secure storage: $e');
return null;
}
}
// Generic write
Future<void> write(String key, String value) async {
try {
await _storage.write(key: key, value: value);
} catch (e) {
print('Error writing to secure storage: $e');
rethrow;
}
}
// Generic delete
Future<void> delete(String key) async {
try {
await _storage.delete(key: key);
} catch (e) {
print('Error deleting from secure storage: $e');
rethrow;
}
}
// Delete all
Future<void> deleteAll() async {
try {
await _storage.deleteAll();
} catch (e) {
print('Error deleting all from secure storage: $e');
rethrow;
}
}
// Check if key exists
Future<bool> containsKey(String key) async {
try {
return await _storage.containsKey(key: key);
} catch (e) {
print('Error checking key in secure storage: $e');
return false;
}
}
// Read all keys
Future<Map<String, String>> readAll() async {
try {
return await _storage.readAll();
} catch (e) {
print('Error reading all from secure storage: $e');
return {};
}
}
// Specific methods for common use cases
static const _keyAuthToken = 'auth_token';
static const _keyRefreshToken = 'refresh_token';
static const _keyUserId = 'user_id';
static const _keyBiometricEnabled = 'biometric_enabled';
static const _keyEncryptionKey = 'encryption_key';
Future<String?> getAuthToken() => read(_keyAuthToken);
Future<void> setAuthToken(String token) => write(_keyAuthToken, token);
Future<void> deleteAuthToken() => delete(_keyAuthToken);
Future<String?> getRefreshToken() => read(_keyRefreshToken);
Future<void> setRefreshToken(String token) => write(_keyRefreshToken, token);
Future<String?> getUserId() => read(_keyUserId);
Future<void> setUserId(String userId) => write(_keyUserId, userId);
Future<bool> isBiometricEnabled() async {
final value = await read(_keyBiometricEnabled);
return value == 'true';
}
Future<void> setBiometricEnabled(bool enabled) =>
write(_keyBiometricEnabled, enabled.toString());
Future<String?> getEncryptionKey() => read(_keyEncryptionKey);
Future<void> setEncryptionKey(String key) => write(_keyEncryptionKey, key);
// Clear all authentication data
Future<void> clearAuthData() async {
await Future.wait([
deleteAuthToken(),
delete(_keyRefreshToken),
delete(_keyUserId),
]);
}
}
2.2 Encryption Service
// lib/core/security/encryption_service.dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';
import 'package:crypto/crypto.dart';
import 'package:injectable/injectable.dart';
@singleton
class EncryptionService {
late final Encrypter _encrypter;
late final IV _iv;
EncryptionService() {
// Generate or retrieve encryption key
final key = Key.fromSecureRandom(32); // AES-256
_iv = IV.fromSecureRandom(16);
_encrypter = Encrypter(AES(key, mode: AESMode.cbc));
}
// Initialize with specific key (from secure storage)
void initializeWithKey(String base64Key) {
final key = Key.fromBase64(base64Key);
_iv = IV.fromSecureRandom(16);
_encrypter = Encrypter(AES(key, mode: AESMode.cbc));
}
// Generate new encryption key
static String generateKey() {
final key = Key.fromSecureRandom(32);
return key.base64;
}
// Encrypt string
String encrypt(String plainText) {
try {
final encrypted = _encrypter.encrypt(plainText, iv: _iv);
return encrypted.base64;
} catch (e) {
throw EncryptionException('Failed to encrypt data: $e');
}
}
// Decrypt string
String decrypt(String encryptedText) {
try {
final encrypted = Encrypted.fromBase64(encryptedText);
return _encrypter.decrypt(encrypted, iv: _iv);
} catch (e) {
throw EncryptionException('Failed to decrypt data: $e');
}
}
// Encrypt bytes
Uint8List encryptBytes(Uint8List data) {
try {
final encrypted = _encrypter.encryptBytes(data, iv: _iv);
return encrypted.bytes;
} catch (e) {
throw EncryptionException('Failed to encrypt bytes: $e');
}
}
// Decrypt bytes
Uint8List decryptBytes(Uint8List encryptedData) {
try {
final encrypted = Encrypted(encryptedData);
return Uint8List.fromList(_encrypter.decryptBytes(encrypted, iv: _iv));
} catch (e) {
throw EncryptionException('Failed to decrypt bytes: $e');
}
}
// Hash password (one-way)
String hashPassword(String password, {String? salt}) {
final saltToUse = salt ?? _generateSalt();
final bytes = utf8.encode(password + saltToUse);
final digest = sha256.convert(bytes);
return '$saltToUse:${digest.toString()}';
}
// Verify password hash
bool verifyPassword(String password, String hashedPassword) {
try {
final parts = hashedPassword.split(':');
if (parts.length != 2) return false;
final salt = parts[0];
final hash = parts[1];
final bytes = utf8.encode(password + salt);
final digest = sha256.convert(bytes);
return digest.toString() == hash;
} catch (e) {
return false;
}
}
// Generate random salt
String _generateSalt() {
final random = Key.fromSecureRandom(16);
return random.base64;
}
// Encrypt JSON
String encryptJson(Map<String, dynamic> json) {
final jsonString = jsonEncode(json);
return encrypt(jsonString);
}
// Decrypt JSON
Map<String, dynamic> decryptJson(String encryptedJson) {
final jsonString = decrypt(encryptedJson);
return jsonDecode(jsonString) as Map<String, dynamic>;
}
}
class EncryptionException implements Exception {
final String message;
EncryptionException(this.message);
@override
String toString() => 'EncryptionException: $message';
}
3. Certificate Pinning
3.1 Certificate Pinning Implementation
// lib/core/network/certificate_pinning.dart
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
class CertificatePinning {
static const String _certificatePath = 'assets/certificates/cert.pem';
// Lista de SHA-256 fingerprints permitidos
static const List<String> _allowedFingerprints = [
// Ejemplo: SHA-256 del certificado del servidor
'AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99',
];
static Future<Dio> createDioWithPinning() async {
final dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
validateStatus: (status) => status! < 500,
),
);
// Configure HTTP client with certificate pinning
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
client.badCertificateCallback = (cert, host, port) {
// Verify certificate fingerprint
final certFingerprint = _getCertificateFingerprint(cert);
if (_allowedFingerprints.contains(certFingerprint)) {
return true;
}
print('Certificate pinning failed for $host:$port');
print('Received fingerprint: $certFingerprint');
return false;
};
return client;
};
return dio;
}
// Get certificate SHA-256 fingerprint
static String _getCertificateFingerprint(X509Certificate cert) {
final der = cert.der;
final hash = SHA256();
final digest = hash.convert(der);
return digest.bytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0').toUpperCase())
.join(':');
}
// Load certificate from assets
static Future<SecurityContext> loadCertificateFromAssets() async {
final context = SecurityContext.defaultContext;
try {
final certData = await rootBundle.load(_certificatePath);
final certBytes = certData.buffer.asUint8List();
context.setTrustedCertificatesBytes(certBytes);
} catch (e) {
print('Error loading certificate: $e');
}
return context;
}
}
// Alternative: Using package
// lib/core/network/ssl_pinning_interceptor.dart
import 'package:dio/dio.dart';
class SSLPinningInterceptor extends Interceptor {
final List<String> allowedHosts;
final List<String> allowedFingerprints;
SSLPinningInterceptor({
required this.allowedHosts,
required this.allowedFingerprints,
});
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final host = Uri.parse(options.baseUrl).host;
if (!allowedHosts.contains(host)) {
return handler.reject(
DioException(
requestOptions: options,
error: 'Host not allowed: $host',
type: DioExceptionType.badCertificate,
),
);
}
handler.next(options);
}
}
3.2 Configuración de Certificados
# pubspec.yaml
flutter:
assets:
- assets/certificates/
# assets/certificates/README.md
# Certificates
## Obtener certificado del servidor:
```bash
# Opción 1: Usando openssl
openssl s_client -connect api.example.com:443 -showcerts < /dev/null | openssl x509 -outform PEM > cert.pem
# Opción 2: Obtener fingerprint SHA-256
openssl s_client -connect api.example.com:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout -in /dev/stdin
# Opción 3: Desde navegador
# Chrome -> DevTools -> Security -> View Certificate -> Details -> Export
4. Biometric Authentication
4.1 Biometric Service
// lib/core/security/biometric_service.dart
import 'package:local_auth/local_auth.dart';
import 'package:local_auth/error_codes.dart' as auth_error;
import 'package:injectable/injectable.dart';
@singleton
class BiometricService {
final LocalAuthentication _auth = LocalAuthentication();
// Check if device supports biometric authentication
Future<bool> isDeviceSupported() async {
try {
return await _auth.isDeviceSupported();
} catch (e) {
print('Error checking device support: $e');
return false;
}
}
// Check if biometrics are available
Future<bool> canCheckBiometrics() async {
try {
return await _auth.canCheckBiometrics;
} catch (e) {
print('Error checking biometrics availability: $e');
return false;
}
}
// Get available biometric types
Future<List<BiometricType>> getAvailableBiometrics() async {
try {
return await _auth.getAvailableBiometrics();
} catch (e) {
print('Error getting available biometrics: $e');
return [];
}
}
// Authenticate with biometrics
Future<BiometricAuthResult> authenticate({
required String localizedReason,
bool useErrorDialogs = true,
bool stickyAuth = true,
bool sensitiveTransaction = false,
}) async {
try {
// Check if device supports biometrics
final canCheck = await canCheckBiometrics();
if (!canCheck) {
return BiometricAuthResult.notAvailable;
}
// Get available biometrics
final availableBiometrics = await getAvailableBiometrics();
if (availableBiometrics.isEmpty) {
return BiometricAuthResult.notEnrolled;
}
// Authenticate
final authenticated = await _auth.authenticate(
localizedReason: localizedReason,
options: AuthenticationOptions(
useErrorDialogs: useErrorDialogs,
stickyAuth: stickyAuth,
sensitiveTransaction: sensitiveTransaction,
biometricOnly: true,
),
);
return authenticated
? BiometricAuthResult.success
: BiometricAuthResult.failure;
} on PlatformException catch (e) {
print('Biometric authentication error: ${e.code} - ${e.message}');
switch (e.code) {
case auth_error.notAvailable:
return BiometricAuthResult.notAvailable;
case auth_error.notEnrolled:
return BiometricAuthResult.notEnrolled;
case auth_error.lockedOut:
case auth_error.permanentlyLockedOut:
return BiometricAuthResult.lockedOut;
case auth_error.passcodeNotSet:
return BiometricAuthResult.passcodeNotSet;
default:
return BiometricAuthResult.failure;
}
} catch (e) {
print('Unexpected biometric error: $e');
return BiometricAuthResult.failure;
}
}
// Authenticate with fallback to PIN/Pattern
Future<bool> authenticateWithFallback({
required String localizedReason,
}) async {
try {
return await _auth.authenticate(
localizedReason: localizedReason,
options: const AuthenticationOptions(
useErrorDialogs: true,
stickyAuth: true,
biometricOnly: false, // Allow PIN/Pattern fallback
),
);
} catch (e) {
print('Authentication with fallback error: $e');
return false;
}
}
// Stop authentication
Future<void> stopAuthentication() async {
try {
await _auth.stopAuthentication();
} catch (e) {
print('Error stopping authentication: $e');
}
}
// Check if Face ID or Touch ID is available (iOS specific)
Future<bool> isFaceIdAvailable() async {
final biometrics = await getAvailableBiometrics();
return biometrics.contains(BiometricType.face);
}
Future<bool> isFingerprintAvailable() async {
final biometrics = await getAvailableBiometrics();
return biometrics.contains(BiometricType.fingerprint);
}
// Get biometric type name for UI
Future<String> getBiometricTypeName() async {
final biometrics = await getAvailableBiometrics();
if (biometrics.contains(BiometricType.face)) {
return 'Face ID';
} else if (biometrics.contains(BiometricType.fingerprint)) {
return 'Touch ID';
} else if (biometrics.contains(BiometricType.iris)) {
return 'Iris';
} else {
return 'Biometric';
}
}
}
enum BiometricAuthResult {
success,
failure,
notAvailable,
notEnrolled,
lockedOut,
passcodeNotSet,
}
extension BiometricAuthResultExtension on BiometricAuthResult {
String get message {
switch (this) {
case BiometricAuthResult.success:
return 'Authentication successful';
case BiometricAuthResult.failure:
return 'Authentication failed';
case BiometricAuthResult.notAvailable:
return 'Biometric authentication not available';
case BiometricAuthResult.notEnrolled:
return 'No biometric enrolled. Please set up biometric authentication in settings';
case BiometricAuthResult.lockedOut:
return 'Too many failed attempts. Please try again later';
case BiometricAuthResult.passcodeNotSet:
return 'Please set up a passcode first';
}
}
bool get isSuccess => this == BiometricAuthResult.success;
}
4.2 Uso de Biometric Service
// lib/screens/biometric_login_screen.dart
import 'package:flutter/material.dart';
class BiometricLoginScreen extends StatefulWidget {
@override
State<BiometricLoginScreen> createState() => _BiometricLoginScreenState();
}
class _BiometricLoginScreenState extends State<BiometricLoginScreen> {
final _biometricService = BiometricService();
String _message = '';
@override
void initState() {
super.initState();
_checkBiometricSupport();
}
Future<void> _checkBiometricSupport() async {
final isSupported = await _biometricService.isDeviceSupported();
final canCheck = await _biometricService.canCheckBiometrics();
if (!isSupported || !canCheck) {
setState(() {
_message = 'Biometric authentication not available';
});
}
}
Future<void> _authenticate() async {
final result = await _biometricService.authenticate(
localizedReason: 'Please authenticate to access your account',
useErrorDialogs: true,
stickyAuth: true,
sensitiveTransaction: true,
);
setState(() {
_message = result.message;
});
if (result.isSuccess) {
// Navigate to home screen
Navigator.pushReplacementNamed(context, '/home');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Biometric Login')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.fingerprint, size: 100),
SizedBox(height: 32),
ElevatedButton(
onPressed: _authenticate,
child: Text('Authenticate'),
),
SizedBox(height: 16),
Text(_message),
],
),
),
);
}
}
5. Root/Jailbreak Detection
// lib/core/security/root_detection.dart
import 'package:flutter_jailbreak_detection/flutter_jailbreak_detection.dart';
import 'package:injectable/injectable.dart';
@singleton
class RootDetectionService {
// Check if device is jailbroken/rooted
Future<bool> isDeviceCompromised() async {
try {
final isJailbroken = await FlutterJailbreakDetection.jailbroken;
final isDeveloperMode = await FlutterJailbreakDetection.developerMode;
return isJailbroken || isDeveloperMode;
} catch (e) {
print('Error checking root/jailbreak: $e');
// En caso de error, asumir que está comprometido (fail-secure)
return true;
}
}
// Get detailed status
Future<DeviceSecurityStatus> getDeviceStatus() async {
try {
final isJailbroken = await FlutterJailbreakDetection.jailbroken;
final isDeveloperMode = await FlutterJailbreakDetection.developerMode;
return DeviceSecurityStatus(
isJailbroken: isJailbroken,
isDeveloperMode: isDeveloperMode,
isSecure: !isJailbroken && !isDeveloperMode,
);
} catch (e) {
print('Error getting device status: $e');
return DeviceSecurityStatus(
isJailbroken: true,
isDeveloperMode: false,
isSecure: false,
);
}
}
// Handle compromised device
void handleCompromisedDevice() {
// Opciones:
// 1. Mostrar warning pero permitir continuar
// 2. Deshabilitar funciones sensibles
// 3. Bloquear completamente la app
// 4. Reportar al backend para análisis
print('⚠️ Device appears to be compromised (rooted/jailbroken)');
}
}
class DeviceSecurityStatus {
final bool isJailbroken;
final bool isDeveloperMode;
final bool isSecure;
DeviceSecurityStatus({
required this.isJailbroken,
required this.isDeveloperMode,
required this.isSecure,
});
String get message {
if (isSecure) {
return 'Device is secure';
}
final issues = <String>[];
if (isJailbroken) issues.add('jailbroken/rooted');
if (isDeveloperMode) issues.add('developer mode enabled');
return 'Security warning: ${issues.join(', ')}';
}
}
6. API Key Protection
// lib/core/config/environment.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
class Environment {
// Load environment variables
static Future<void> load() async {
await dotenv.load(fileName: '.env');
}
// API Keys (NEVER hardcode these)
static String get apiKey => dotenv.env['API_KEY'] ?? '';
static String get apiSecret => dotenv.env['API_SECRET'] ?? '';
static String get googleMapsKey => dotenv.env['GOOGLE_MAPS_KEY'] ?? '';
// Base URLs
static String get apiBaseUrl => dotenv.env['API_BASE_URL'] ?? 'https://api.example.com';
// Feature flags
static bool get enableAnalytics => dotenv.env['ENABLE_ANALYTICS'] == 'true';
static bool get enableCrashlytics => dotenv.env['ENABLE_CRASHLYTICS'] == 'true';
// Environment type
static String get environment => dotenv.env['ENVIRONMENT'] ?? 'development';
static bool get isProduction => environment == 'production';
static bool get isDevelopment => environment == 'development';
}
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load environment variables
await Environment.load();
runApp(MyApp());
}
# .env.example (commit this)
API_KEY=your_api_key_here
API_SECRET=your_api_secret_here
GOOGLE_MAPS_KEY=your_google_maps_key_here
API_BASE_URL=https://api.example.com
ENVIRONMENT=development
ENABLE_ANALYTICS=false
ENABLE_CRASHLYTICS=false
# .env (add to .gitignore, NEVER commit)
# Copy .env.example to .env and fill with real values
# .gitignore
.env
*.key
*.keystore
*.jks
key.properties
secrets.dart
7. Network Security
7.1 Secure HTTP Client
// lib/core/network/secure_http_client.dart
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../security/secure_storage_service.dart';
import 'certificate_pinning.dart';
@singleton
class SecureHttpClient {
late final Dio _dio;
final SecureStorageService _secureStorage;
SecureHttpClient(this._secureStorage) {
_initializeDio();
}
Future<void> _initializeDio() async {
_dio = await CertificatePinning.createDioWithPinning();
// Add interceptors
_dio.interceptors.addAll([
_AuthInterceptor(_secureStorage),
_LoggingInterceptor(),
_ErrorInterceptor(),
]);
}
Dio get dio => _dio;
// Convenience methods
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
}) =>
_dio.get<T>(path, queryParameters: queryParameters, options: options);
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
}) =>
_dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
// Auth Interceptor
class _AuthInterceptor extends Interceptor {
final SecureStorageService _secureStorage;
_AuthInterceptor(this._secureStorage);
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
// Add auth token to requests
final token = await _secureStorage.getAuthToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// Add security headers
options.headers['X-App-Version'] = '1.0.0';
options.headers['X-Platform'] = Platform.operatingSystem;
handler.next(options);
}
}
// Logging Interceptor (only in debug)
class _LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) {
print('REQUEST[${options.method}] => ${options.uri}');
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (kDebugMode) {
print('RESPONSE[${response.statusCode}] <= ${response.requestOptions.uri}');
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (kDebugMode) {
print('ERROR[${err.response?.statusCode}] => ${err.requestOptions.uri}');
print('Message: ${err.message}');
}
handler.next(err);
}
}
// Error Interceptor
class _ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Handle specific error cases
switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
// Handle timeout
break;
case DioExceptionType.badCertificate:
// Certificate pinning failed
print('❌ Certificate validation failed!');
break;
case DioExceptionType.badResponse:
// Handle HTTP errors
if (err.response?.statusCode == 401) {
// Token expired, refresh or logout
}
break;
default:
break;
}
handler.next(err);
}
}
🎯 Mejores Prácticas
1. Seguridad por Capas (Defense in Depth)
✅ DO: Implementa múltiples capas de seguridad
Capa 1: Code Obfuscation
Capa 2: Certificate Pinning
Capa 3: Secure Storage
Capa 4: Encryption
Capa 5: Biometric Auth
Capa 6: Root Detection
2. Principio de Menor Privilegio
✅ DO:
<!-- Android: Solo permisos necesarios -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<!-- iOS: Justificaciones claras -->
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to securely authenticate you</string>
❌ DON'T:
<!-- No pedir permisos innecesarios -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
3. Secrets Management
✅ DO:
- Usar
.envpara configuración - Secrets en secure storage
- Backend para API keys críticas
- Rotate keys regularmente
❌ DON'T:
// ❌ NUNCA hagas esto
const String API_KEY = 'sk_live_123456789abcdef';
const String SECRET = 'my_secret_password';
4. OWASP Mobile Top 10
-
M1: Improper Platform Usage ✅
- Usa platform security features
- Keychain (iOS), KeyStore (Android)
-
M2: Insecure Data Storage ✅
- flutter_secure_storage
- Nunca guardar secrets en SharedPreferences
-
M3: Insecure Communication ✅
- HTTPS only
- Certificate pinning
- TLS 1.2+
-
M4: Insecure Authentication ✅
- Biometric authentication
- JWT tokens con expiración
- Refresh tokens
-
M5: Insufficient Cryptography ✅
- AES-256 encryption
- Secure random number generation
- No custom crypto algorithms
-
M6: Insecure Authorization ✅
- Validación backend
- Tokens en headers seguros
- Role-based access control
-
M7: Client Code Quality ✅
- Code obfuscation
- Static analysis
- Linters y code review
-
M8: Code Tampering ✅
- Root/jailbreak detection
- Signature verification
- Runtime integrity checks
-
M9: Reverse Engineering ✅
- Obfuscation
- Native code para lógica crítica
- No secrets en código
-
M10: Extraneous Functionality ✅
- Remover debug logs en producción
- Deshabilitar developer tools
- Clean code antes de release
🚨 Troubleshooting
Error: Certificate Pinning Falla en Debug
// Deshabilitar pinning solo en debug
if (kDebugMode) {
// Allow any certificate in debug
HttpOverrides.global = MyHttpOverrides();
}
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (cert, host, port) => true;
}
}
Error: Secure Storage Falla en Android
// android/app/build.gradle
android {
defaultConfig {
minSdkVersion 23 // Requerido para secure storage
}
}
Error: Biometric No Funciona en Simulador
// Verificar si es simulador
import 'package:device_info_plus/device_info_plus.dart';
Future<bool> isPhysicalDevice() async {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
return androidInfo.isPhysicalDevice;
} else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
return iosInfo.isPhysicalDevice;
}
return true;
}
📚 Recursos Adicionales
Documentación Oficial
- OWASP Mobile Security Testing Guide
- Flutter Security Best Practices
- Android Security Tips
- iOS Security Guide
Herramientas
Compliance
🔗 Skills Relacionados
- Code Generation - DI para security services
- Firebase - Firebase Auth y Security Rules
- Offline-First - Secure local storage
- Testing - Security testing
Versión: 1.0.0 Última actualización: Diciembre 2025 Total líneas: 1,150+
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?