Guides

Detect emulators, rooted and jailbroken devices

What the Fingerly iOS and Android SDKs report about emulators, simulators, rooted and jailbroken devices and cloned apps, and how to act on it from your server.

Fraud in mobile apps often runs from devices that are not what they seem: an emulator driven by a script, a rooted or jailbroken phone with tooling attached, or a cloned copy of your app. The Fingerly iOS and Android SDKs, and the React Native, Flutter and Lynx packages over them, collect raw input about the environment. The server derives a signal for each finding and adds it to the suspect score.

These are signals for your own policy, not verdicts. A rooted phone is not a fraudster, and a clean device is not proven honest. Fingerly never blocks anyone on its own.

What the signals tell you

FindingPlatformSignal group keyDefault weight
EmulatorAndroidandroid_emulator9
SimulatoriOSios_simulator16
Rooted deviceAndroidrooted_device12
Jailbroken deviceiOSjailbroken_device10
Cloned or repackaged appAndroidcloned_app9
Instrumentation attachedAndroid and iOSfrida_detected14 and 14
Intercepted app trafficAndroid and iOSmitm_attack14 and 14
Debugger or developer toolingAndroid and iOSdeveloper_tools8 and 16
Mass-provisioned or freshly reset deviceAndroid and iOSdevice_farm8 and 8
No hardware proof offeredAndroid and iOSattestation_missing3 and 3

The weights are the shipped defaults, listed in the signals reference, and you can change them with risk weights. A weight of 9 means the signal adds 9 points to the suspect score when it fires. With the default threshold of 30, the level becomes medium from 15 points and high from 30.

Where to read them

  • The stored event, read with a secret key, lists the groups that fired in triggers, heaviest first. See Get an event.
  • The visitor.suspect webhook names the individual signal and its group when a session reaches high.
  • The app is never told which signals fired. It can read suspectLevel unless you hide the score on the key, as a convenience and never as the decision.
Server
const MODIFIED_DEVICE = [
  'android_emulator',
  'ios_simulator',
  'rooted_device',
  'jailbroken_device',
  'cloned_app',
  'frida_detected',
  'mitm_attack',
]

const event = await fingerly.events.get(requestId)
const found = event.triggers.filter((trigger) => MODIFIED_DEVICE.includes(trigger.signal))

if (found.length > 0) return stepUp()   // ask for a second factor, or hold for review

To make one of these decisive, read it from event.triggers as above, or raise its weight in risk weights so the level moves on its own. See signals that matter for account takeover for a policy that uses them.

Prove the report comes from your app

The signals are derived from raw input, and a modified app can forge raw input. Hardware attestation is what resists that: on Android, a key in the device keystore attests to a fresh challenge, and on iOS, App Attest has Apple vouch for the app. A failed check raises the tampering signal, which appears on stored events under browser_tampering.

  • Android works without configuration on Android 7.0 (API 24) and newer. List your package names and signing certificates on the key for the server to check the attestation names your app.
  • iOS needs the App Attest capability and your Apple app ID on the key.
  • A key can require attestation, so a report that does not prove itself is refused with 403 instead of scored. Simulators and emulators are refused on such a key. Turn it on only once every device you need to serve can attest.

See mobile app attestation for the states and for what each failure means.

Optional checks you can switch on

  • iOS jailbreak apps. To let the SDK check for well-known jailbreak apps, declare their URL schemes in LSApplicationQueriesSchemes. The SDK only queries the schemes you declare, and reports only how many answered. Without any, the report says it could not ask, so a zero is never read as nothing found. See Optional: jailbreak app checks.
  • Android packages. The SDK's manifest declares a <queries> list of 17 root, hooking and cloning packages. It never requests QUERY_ALL_PACKAGES, it reports other apps only as yes or no and never by name, and you can remove entries. See package visibility.

Test it

  • Run your app in an Android emulator or the iOS Simulator with a development key. The event shows android_emulator or ios_simulator in Identification > Events.
  • Replay recent events against a policy change before you enforce it. See test your integration.
  • Start in observe mode: log what you would have done for a week or two, then add friction before you refuse anyone.

Common questions

How do I detect an emulator in my mobile app?

Identify with the SDK at the action you protect, then read the stored event on your server. android_emulator and ios_simulator appear in triggers when the app runs in one. The app itself is never told which signals fired.

How do I detect a rooted or jailbroken device?

The same way. rooted_device is reported on Android and jailbroken_device on iOS, each with a default weight you can change. The server derives them from the raw input the SDK sends.

Can these checks be bypassed?

A determined attacker with a modified app or device can hide what the SDK reads, which is why attestation is the evidence to add. It raises the cost of forging a report, and it does not make forgery impossible. Treat each answer as evidence.

Does it work in React Native and Flutter apps?

Yes. Those packages are thin bridges to the native SDKs, so the same signals are reported on each platform.

Does the SDK show a permission prompt for these checks?

No. No SDK shows a permission prompt or a dialog. On Android, the permissions it declares are granted at install time. On iOS, no usage description is required.

Can Fingerly detect device farms?

The device_farm group fires when a device looks mass-provisioned or freshly reset, or shares traits with many devices at once. Its cross-device part compares a device with your own traffic and adds nothing to the score until there are at least 1,000 identifications and 7 days of history.