mm_device_security

Flutter Android iOS License

Plugin de Flutter que consulta al sistema operativo si el dispositivo está rooteado / con jailbreak, si es emulador / simulador, o si tiene modo desarrollador. Dart no puede saber eso: el trabajo lo hacen Kotlin (Android) y Swift (iOS). Este paquete es el puente.

Pensado para apps que deben fallar cerrado (banca, tickets, RASP ligero). App Store, Play Store y TestFlight reportan las señales reales. La única excepción es Firebase Test Lab (pre-launch automático de Play).

Heurística, no un RASP comercial. Un Magisk Hide o un emulador parcheado puede colarse. Sirve como primer filtro, no como garantía absoluta.


Contenido


Instalación

Elige una forma. No hace falta publicar en pub.flutter-io.cn para usarlo.

A. Desde GitHub (recomendado para Warena)

En el pubspec.yaml de tu app:

dependencies:
  mm_device_security:
    git:
      url: https://github.com/Mario-Fernando24/mm_device_security.git
      ref: main
flutter pub get

Para fijar una versión, usa un tag en vez de main:

ref: v0.0.1

B. Desde una carpeta local (desarrollo)

dependencies:
  mm_device_security:
    path: ../plugin/mm_device_security   # ajusta la ruta

C. Desde pub.flutter-io.cn (cuando el paquete esté publicado)

dependencies:
  mm_device_security: ^0.0.1
flutter pub add mm_device_security

Después borra de MainActivity / AppDelegate de la app el canal verificaciones_seguridad y los detectores. Flutter registra este plugin solo. La pantalla de bloqueo (UI de marca) se queda en la app.


Uso

import 'package:flutter/services.dart';
import 'package:mm_device_security/mm_device_security.dart';

Future<DeviceSecurityResult> verificarDispositivo() async {
  try {
    return await MmDeviceSecurity().getSecuritySummary();
  } on MissingPluginException {
    // Tests de widget / escritorio: no hay canal nativo.
    return const DeviceSecurityResult();
  }
}

final resultado = await verificarDispositivo();
if (resultado.isBlocked) {
  // Navega a tu PantallaBloqueo.
}

Usa siempre getSecuritySummary() para decidir. Ahí el nativo ya relaja emulador y modo desarrollador si es Firebase Test Lab. Root / jailbreak nunca se relaja.

Métodos sueltos (señal cruda, sin relajar Test Lab):

final plugin = MmDeviceSecurity();
await plugin.isRooted();
await plugin.isJailbroken();             // mismo chequeo, nombre iOS
await plugin.isEmulator();
await plugin.isDeveloperModeEnabled();

Alerta nativa opcional (cierra el proceso):

await plugin.showBlockAlertAndExit(
  title: 'Dispositivo no permitido',
  message: 'Esta aplicación no puede ejecutarse en este dispositivo.',
);

API

Método / campo Qué es
getSecuritySummary() Todas las señales de una vez (método del splash)
isRooted() / isJailbroken() Root Android o jailbreak iOS
isEmulator() AVD, BlueStacks, Simulator, etc.
isDeveloperModeEnabled() ADB / opciones de desarrollador / get-task-allow
showBlockAlertAndExit() Diálogo nativo no cancelable + exit
DeviceSecurityResult.isBlocked rooted || emulator || developer
DeviceSecurityResult.isSecure Lo contrario de isBlocked
DeviceSecurityResult.isTestLab Firebase Test Lab (solo Android)
DeviceSecurityResult.isTestFlight Recibo sandboxReceipt (solo iOS)

Los alias nativos en español (resumenSeguridad, estaRooteado, …) siguen existiendo en Kotlin/Swift por si migras a medias. El API Dart público está en inglés.


Qué significa cada señal

getSecuritySummary()
        │
        ├── isRooted          root (Android) o jailbreak (iOS)
        ├── isEmulator        AVD / BlueStacks / Simulator / …
        ├── isDeveloperMode   ADB u opciones de desarrollador
        ├── isTestLab         Firebase Test Lab (solo Android)
        ├── isTestFlight      recibo sandboxReceipt (solo iOS)
        ├── isBlocked         rooted || emulator || developer
        └── isSecure          !isBlocked

En Test Lab el nativo fuerza isEmulator e isDeveloperMode a false para que Play Console no rechace el pre-launch. Un usuario que instala desde Play en BlueStacks se bloquea.

Dónde corre Emulador Modo desarrollador ¿Bloqueado?
AVD / Simulator a menudo SÍ
Android físico, ADB u opciones ON NO
Android físico, opciones OFF, sin root NO NO NO
iPhone + run desde Xcode NO SÍ (get-task-allow)
iPhone + TestFlight / release NO NO NO (si no hay jailbreak)

Probar en local

No hace falta publicar. El example/ ya apunta al plugin con path: ../.

cd example
flutter pub get
flutter devices
flutter run

Tests Dart (mock del canal, sin dispositivo):

flutter test
flutter analyze

Un cambio en Kotlin/Swift no entra con hot reload: hot restart o vuelve a flutter run.


Arquitectura

App  →  MmDeviceSecurity().getSecuritySummary()
     →  MethodChannel('mm_device_security')
     →  Kotlin o Swift corre los detectores
     →  Map { isRooted, isEmulator, isDeveloperMode, … }
     →  DeviceSecurityResult
Archivo Rol
lib/mm_device_security.dart API que importa la app
lib/mm_device_security_method_channel.dart Habla con el nativo
android/.../MmDeviceSecurityPlugin.kt Canal + root + emulador + ADB + Test Lab
ios/.../MmDeviceSecurityPlugin.swift Canal + jailbreak + Simulator + Developer Mode
example/ App mínima para ver SÍ/NO reales

MainActivity y AppDelegate del host quedan vacíos: GeneratedPluginRegistrant registra el plugin leyendo el pubspec.yaml.


Publicar el proyecto

El código ya está en GitHub:

https://github.com/Mario-Fernando24/mm_device_security

Hay dos destinos distintos. Elige según visibilidad.

1. GitHub (ya lo tienes — publicar una versión)

cd /Users/mariomunoz/proyectos/flutter/plugin/mm_device_security

# Comprueba que no hay secretos
git status
flutter analyze
flutter test

git add -A
git commit -m "docs: documentación de uso, instalación y publicación"
git push origin main

En GitHub (Settings → General):

  1. Description: Flutter plugin: root, jailbreak, emulator and developer-mode detection.
  2. Topics: flutter, plugin, android, ios, security, root, jailbreak
  3. Visibilidad: Public si quieres que otros hagan git: / pub.flutter-io.cn. Private si solo Warena debe ver las heurísticas.
  4. Releases → Draft a new release
    • Tag: v0.0.1
    • Title: v0.0.1
    • Notas: copia el CHANGELOG.md

Fijar esa versión en la app:

mm_device_security:
  git:
    url: https://github.com/Mario-Fernando24/mm_device_security.git
    ref: v0.0.1

2. pub.flutter-io.cn (opcional — el paquete queda público)

Cualquiera podrá leer Kotlin/Swift. Si las heurísticas deben ser privadas, no publiques y quédate en GitHub privado + git:.

Checklist antes de subir:

# 1. Metadatos: homepage, repository, LICENSE, CHANGELOG, version
# 2. Análisis limpio
flutter analyze
flutter test

# 3. Ensayo (no sube nada)
flutter pub publish --dry-run

Cuenta y publicación:

  1. Entra en pub.flutter-io.cn con la cuenta de Google.
  2. En la terminal:
dart pub login
flutter pub publish
  1. Confirma y. La URL quedará: https://pub.flutter-io.cn/packages/mm_device_security
  2. Sube la puntuación: en pub.flutter-io.cn → Admin → conectar el repo de GitHub.
  3. Siguiente versión: sube version y CHANGELOG.md (semver: 0.0.2 parche, 0.1.0 API, 1.0.0 estable), commit, tag, otra vez flutter pub publish.

Si dry-run se queja de LICENSE, homepage o description, corrige y repite. No uses --force salvo que sepas por qué.


Limitaciones

  • No sustituye a un RASP comercial (Promon, Guardsquare, etc.).
  • ro.debuggable / test-keys son señales del sistema, no de tu APK.
  • Un run desde Xcode casi siempre marca modo desarrollador.
  • isTestFlight puede ser true también en installs de desarrollo (recibo sandbox).

Licencia

BSD 3-Clause. Ver LICENSE.