> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chmodlab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Device policy

> React to rooted devices, emulators, VPN connections and spoofed GPS locations.

The SDK reports signals about the device the capture was made on. `device_policy` decides
what each signal does to the decision.

These checks apply to **every transaction type** — a `BIOMETRIC_ONLY` step-up is evaluated
for device integrity exactly like a full onboarding.

```json lines theme={null}
{
  "device_policy": {
    "on_compromised_device": "REJECT",
    "on_developer_mode": "WARN",
    "on_emulator": "REJECT",
    "on_vpn_or_proxy": "WARN",
    "on_gps_mock_location": "REJECT",
    "on_gps_ip_location_mismatch": "WARN"
  }
}
```

Every field takes `REJECT`, `WARN` or `IGNORE`.

## Fields

<ParamField body="on_compromised_device" type="string" default="REJECT">
  The device is rooted, jailbroken, or running under a debugger.

  Emits `COMPROMISED_DEVICE`. The strongest signal in this group: root access lets an
  attacker feed synthetic camera frames into the capture. Keep this at `REJECT` unless you
  have a specific reason not to.
</ParamField>

<ParamField body="on_developer_mode" type="string" default="WARN">
  The device has developer mode enabled.

  Emits `DEVELOPER_MODE_ENABLED`. Weak on its own — plenty of ordinary users enable it and
  forget. `WARN` is the sensible default; it records the signal without punishing a
  legitimate user for a setting they toggled a year ago.
</ParamField>

<ParamField body="on_emulator" type="string" default="REJECT">
  The capture was made on an emulator or simulator.

  Emits `EMULATOR_DETECTED`. A real person photographing a real document does not do it
  from an emulator. Set this to `IGNORE` only while you test on an emulator yourself, and remember to change it back before real users arrive.
</ParamField>

<ParamField body="on_vpn_or_proxy" type="string" default="WARN">
  The connection is routed through a VPN, a proxy or Tor.

  Emits `VPN_OR_PROXY_DETECTED`. Consumer VPNs are common and often corporate policy, so
  `REJECT` here will cost you legitimate users. Consider `WARN` unless your risk model
  specifically calls for it.
</ParamField>

<ParamField body="on_gps_mock_location" type="string" default="REJECT">
  The reported GPS location is simulated.

  Emits `MOCK_LOCATION_DETECTED`. Unlike developer mode, mock location has essentially no
  innocent explanation on a consumer device.
</ParamField>

<ParamField body="on_gps_ip_location_mismatch" type="string" default="WARN">
  The country derived from GPS does not match the country derived from the IP address.

  Emits `GEO_IP_MISMATCH`. Legitimate causes are common — travellers, border regions,
  carrier routing that exits in another country — so this is a weak signal on its own.
</ParamField>

## When a signal is missing

A policy can only act on a signal the device actually reported. Some are unavailable by
platform (`root_detected` is meaningless on iOS), and location signals depend on the user
granting permission.

When a policy is active (not `IGNORE`) but its signal is missing, chmod emits a `WARN`
issue rather than silently passing the check:

| Issue | Emitted when |
| - | - |
| `COMPROMISED_DEVICE_NOT_EVALUABLE` | Root, jailbreak and debugger signals were all absent |
| `DEVELOPER_MODE_NOT_EVALUABLE` | The developer-mode signal was absent |
| `EMULATOR_NOT_EVALUABLE` | The emulator signal was absent |
| `VPN_OR_PROXY_NOT_EVALUABLE` | Network signals were absent or inconclusive |
| `MOCK_LOCATION_NOT_EVALUABLE` | No GPS coordinates were available |
| `GEO_IP_MISMATCH_NOT_EVALUABLE` | GPS or IP country was missing, so they could not be compared |

These never change the decision — they tell you a rule you configured did not get the
chance to run.

## GPS policies need location permission

`on_gps_mock_location` and `on_gps_ip_location_mismatch` depend on GPS coordinates, which
the user has to grant. That permission is controlled by the **SDK** configuration, not by
this policy:

<CodeGroup>
  ```swift iOS theme={null}
  ChmodKycConfiguration(requiresLocationPermission: true)
  ```

  ```kotlin Android theme={null}
  SdkConfig(locationPermission = LocationPermissionMode.REQUIRED)
  ```
</CodeGroup>

If you enable a GPS policy but the SDK never asks for location, chmod emits
`CONFIG_GPS_POLICY_WITHOUT_PERMISSION` — a warning that your two halves disagree.

When location was never requested or the user denied it, `geolocation` is `null` in the
result and you get one of:

| Issue | Meaning |
| - | - |
| `GEOLOCATION_NOT_REQUESTED` | The SDK never asked for location permission |
| `GEOLOCATION_DENIED` | The user refused |

Both are `INFO`. Neither changes the decision.

<Warning>
  Asking for location has a real cost: it is an extra permission prompt early in the flow,
  and some users drop out there. Enable it when the GPS policies genuinely matter to you,
  not by default.
</Warning>

## Where the signals appear

Every reported signal comes back in the result under `result_data.metadata`, whatever the
decision was:

```json lines theme={null}
{
  "metadata": {
    "device": {
      "sdk_platform": "iOS",
      "sdk_version": "0.0.20",
      "os_version": "18.2",
      "device_model": "iPhone16,1",
      "is_emulator": false,
      "root_detected": false,
      "jailbreak_detected": false,
      "developer_mode_enabled": null,
      "debugger_attached": false
    },
    "network": {
      "ip_address": "200.115.48.32",
      "ip_type": "MOBILE",
      "ip_country": "AR",
      "vpn_detected": false,
      "proxy_detected": false,
      "tor_detected": false,
      "connection_type": "CELLULAR",
      "carrier_name": "Movistar AR"
    },
    "geolocation": {
      "latitude": -34.6037,
      "longitude": -58.3816,
      "source": "GPS",
      "is_mock_location": false,
      "country_from_coords": "AR"
    }
  }
}
```

This is why device issues carry no `details`: everything that triggered them is already in
`metadata`, so you can audit a decision without a second call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.