Can we trust Mova AI Vacuum Cleaner?

    Filip Luchianenco

    Founder at CanITrustThat

    9 min read

    TLDR

    I monitored device setup flow, scanning the room, the map, AI features, voice commands on both the vacuum cleaner and on the iOS app. The product respects user privacy better than most but it could have been better. Default dangerous features are off with a consent screen for each. But when enabled, that data goes to cloud, including video stream and photo of the objects. And what does the app say? Tik Tak Tik Tak. Ad SDKs bundled, data shared and attribution is sent.

    Scope and method

    The audit target is a MOVA P10 Pro Ultra on stock firmware, internal model dreame.vacuum.r2491 (aliased mova.vacuum.r2491), paired with its stock companion app. Two capture surfaces were used: the robot's own network traffic, and the companion app's cloud calls.

    The robot ran on a dedicated OpenWrt AP. A full tcpdump on the LAN bridge, filtered to the robot MAC (OUI 70:C9:32:…), captured pre-NAT, so destination IPs are the robot's real targets including hardcoded-IP connections that skip DNS. A parallel dnsmasq query log gave a separate domain inventory.

    The app side ran through mitmproxy with a trusted CA. The app does not pin certificates, so its cloud calls decrypt cleanly. The robot's own traffic stays TLS the whole time. App-side payloads are plaintext; robot-side payloads are opaque, and only destinations, ports, cadence, and byte volumes survive.

    Static review covered both companion builds: Android com.dreame.movahome v1.6.2.474, roughly 2,400 classes, and iOS com.mova.smarthome v1.6.3. The iOS binary is the exact one captured on the wire, so static and dynamic agree on iOS.

    What the hardware is

    The SoC is an Allwinner MR813, quad aarch64 with DDR4, read off the UART serial boot log. The boot chain is SBOOT into BL3-1/OP-TEE, then U-Boot 2018.05 branded "dreame", then OpenWrt/Tina Linux 4.9 running Dreame's "ava" framework, not Android.

    The boot log shows a camera.sh init service and a dedicated video_streamer service at /etc/rc.d/video_streamer.sh. The audio codec configures two microphones, MIC1 and MIC2, with independent gain and separate ADCL/ADCR boost. LiDAR is present.

    Wi-Fi is an RTL8189FS, 2.4 GHz only.

    The root password is regenerated every boot. ADB is gated behind /mnt/misc/adb_flag, which is absent. The data partition is dm-crypt plus RPMB encrypted, and /mnt/private is present. The firmware image format is enc(zip) → signed → enc → sqfs(signed) and needs /etc/*.pem keys to unpack. Decrypting the robot's payloads requires on-device root(WIP).

    Cloud backend

    The core IoT backend is us.iot.mova-tech.com:13267, a nonstandard port, hosted on Alibaba Cloud IoT. Auth is HTTP Basic (mova_app:…) plus a JWT dreame-auth bearer.

    A device configured for region us and locale en-US contacts both us-east-1.api-iot.aliyuncs.com and cn-shanghai.api-iot.aliyuncs.com. Alibaba account and auth run through living-account.us-east-1.aliyuncs.com and sdk.openaccount.aliyun.com. Support is a Sobot SaaS instance at mova.sobot.com.

    The device API routes all sit on us.iot.mova-tech.com:13267:

    RoutePurpose
    dreame-iot-com-20000/device/sendCommandapp to robot remote command channel
    dreame-user-iot/iotuserbind/queryDevicePermitdevice bind / permission grant
    dreame-user-iot/iotmqttdomain/v2/listreturns the MQTT broker
    iotuserdata/getDeviceDatadevice data
    iotstatus/propsdevice props
    iotfile/getDownloadUrlsigned OSS URLs for maps, AI, firmware
    products/10405/connect-instructionspairing instructions

    The MQTT list route returns regionUrl: 20000.mt.us.iot.mova-tech.com:19974, which resolves to Alibaba US-west 47.89.233.219:19974. That matches the robot's own SYN in the pcap, so the app hands out the same broker the robot dials.

    The backend returns success as 操作成功 to a region=us, en-US device.

    The robot's own direct cloud contacts, all TLS-opaque, are 20000.mt.us.iot.mova-tech.com for MQTT, dreame-us.oss-us-west-1.aliyuncs.com for OSS, and Alibaba US-west IPs on high ports: 47.89.233.219:19974, 47.254.19.234:15541, 47.254.114.15:443.

    Trackers and SDKs

    On the decrypted app side, Umeng, Alibaba's analytics, fired 20-plus calls across oslogs, utoken, errnewlog, ucc, and resolve.umeng.com. Also observed: Tencent Bugly (ios.bugly.qq.com), ByteDance (analytics.oceanengine.com plus AppLog on volceapplog.com and klink./toblog.volceapplog.com), Baidu (www.baidu.com, dig.bdurl.net), Facebook (graph.facebook.com, ep2.facebook.com), Firebase Crashlytics (firebase-settings.crashlytics.com), Google (app-analytics-services.com), install attribution (opinstallinit.com), plus api.ucloudlink.com and license.vodglcdn.com.

    The iOS binary adds more to the static roster: FingerprintJS for device fingerprinting, AMap/Gaode for Chinese location, Baidu ads, and ByteDance analytics with location capability. An AWS IoT kit is bundled alongside the Alibaba stack, present in the binary only. Capture never showed AWS as an active backend for this device and region.

    On Android, the ByteDance analytics and Facebook SDKs begin transmitting device and identifier data before the consent dialog is shown, and ByteDance AppLog sends the account UID and a device profile pre-consent. On iOS, App Tracking Transparency gates the ad identifier behind the system prompt, which is the better posture. None of the ByteDance, Umeng, or Aliyun flows are disclosed in the store privacy labels.

    The in-app shop tab is a Shopify storefront at us.mova.tech. It loads PayPal, DoubleClick/Google Ads, Facebook, Growave, Smile.io, NoFraud, Bazaarvoice, and Zendesk. A storefront pulling PayPal and a fraud collector like NoFraud at checkout is expected.

    Per-feature consent plumbing

    The app fetches per-feature privacy policies at runtime from us.iot.mova-tech.com:13267/dreame-product/public/privacy?category=<cat>. Categories seen live: realPhoto for AI obstacle photos (v3), petSnap for pet snapshots (v4), dreamegpt (empty, not enabled), and plugin general (v6/v7).

    The consent pages themselves are served from us.iot.nexlawn.com:8080, a React SPA that pulls content from /api/v1/richtext/htmlUrl behind auth. The SPA AES-CBC-decrypts the policy text client-side with a hardcoded key, dream_app_..., shipped in the JS bundle.

    MOVAhome's policy is operated by Spacewalker Technology (Suzhou) Co., Ltd, a Dreame entity in China. The storefront is operated by Spacewalker Technology USA Inc, a Delaware entity. The backend platform is Dreame.

    Voice runs locally

    Two capture runs, one with real commands and one with deliberate nonsense, produced no audio egress and hit no speech or ASR endpoint. No nls-* host, no speech gateway, no new host appeared. Recognition runs on-device against roughly 20 predefined commands. Static review corroborates: the on-device audio libs are libGVoiceUtils.so and libmygvoice.so (Tencent GVoice) plus libiotvoicechanger.so.

    Live camera video is cloud-relayed

    Viewing the camera from the next room, with both devices on the same subnet, produced zero bytes robot to phone over the LAN. The robot instead sent 6.8 MB to 47.252.97.6:8000 (Alibaba Cloud US) at roughly 73 KB/s average, 91 KB/s peak, about 580 kbps.

    WebRTC negotiation went through Alibaba STUN/TURN: 47.245.98.233:3478 and :3480, and 8.219.102.239:3478. ICE found a valid local candidate, but media relayed through the cloud TURN server rather than going P2P. A new signaling endpoint appeared only at video start, a1408bou2fi.iot-as-mqtt.us-east-1.aliyuncs.com, an Alibaba IoT MQTT host. Both Agora RTC (libagora-rtc-sdk.so) and Tencent TRTC (libliteavsdk.so) are bundled statically.

    The remote camera unlock flow starts in the app, then requires physically pressing the robot's Home button three times, then setting a PIN. When the camera is live, the robot announces it out loud every 10 to 15 seconds.

    Detection is on-device, the map is not

    The binary carries libyolov12.so for YOLOv12 obstacle detection and libncnn.so, Tencent's ncnn inference runtime, so object detection runs on the robot.

    AI obstacle photos, the realPhoto feature, are opt-in and off by default. An A/B run proved it. With realPhoto off, a full mapping run uploaded map and state only, the dreame-us OSS blob at /iot/tmp/000002/ali_dreame/SH<uid>/<did>/0 plus MQTT deltas, and zero image objects crossed the wire.

    After enabling realPhoto and re-running the clean, a new endpoint appeared that was absent before: dreame-us-oss1d.oss-us-west-1.aliyuncs.com (47.88.111.44:443), a separate OSS bucket (oss1d) from the map bucket (dreame-us). The upload was about 366 KB as a contiguous binary POST: 124 back-to-back full 1248 B packets plus 6240, 4992, and 3744 B jumbo segments. That shape is an image or image-batch upload, categorically unlike the trickle of map deltas.

    The floor map is uploaded to Alibaba OSS even at baseline with every AI feature off, to dreame-us.oss-us-west-1.aliyuncs.com at the account-keyed path above. The Android static scorecard over-generalized here, reading "map data kept locally, not transmitted." That only holds for YOLOv12/ncnn inference; the wire shows the map itself is cloud-stored.

    Provisioning stayed local: the robot joined the audit Wi-Fi by QR-code method, and the phone did not ship Wi-Fi credentials to a remote server during setup.

    App-layer code findings

    These come from static review of the release builds and are reported as observed, not as validated exploits.

    The release APK ships hardcoded Alibaba IoT production credentials: ALIFY_APP_KEY_PROD=3344... and ALIFY_APP_SECRET_PROD=40bca29..., plus a separate UAT key and secret. This is the app's identity to Alibaba Cloud IoT, extractable straight from the package.

    The IoT header crypto reuses one static value for two jobs. HEADER_ENCRYPT_KEY="gigx...ZUF" is used as both the AES-GCM key and the GCM nonce/IV on every p-tag and Dreame-RLC header (HeaderIntercept.java). The same value sits in assets/flutter_assets/assets/resource/app_config.json as encrptyKey/rlcEncrptyKey. A fixed GCM nonce reused across messages is a known-broken configuration, in the CVE-2025-8393 class. No end-to-end exploit was validated; the configuration is broken on its face.

    Hostname verification is bypassed for the rest of the session after a single DNS failure, on the useProxy path, and the login flow can proceed through invalid-cert warnings. That is consistent with why the MITM decrypted cleanly.

    Exported and callable by any co-installed app with no permission check, DfuService initiates a Nordic DFU BLE firmware flash with no caller check. DeviceUpdateReceiver sends clean, charge, and flush commands to the robot via the cloud API. BleService does an external BLE connect and GATT write. WebViewActivity and PrivacyPolicyWebView load arbitrary URLs with file access enabled.

    sp_account.xml stores access_token, refresh_token, uid, email, phone, and birthday in plaintext at rest, unencrypted. The refresh_token survives a password change. Those tokens reach cn.iot.mova-tech.com:13267 for the device roster, maps, and control. The same store embeds OAuth secrets for QQ, Weibo, and Douyin, and OEM push secrets for Vivo, Umeng, Meizu, OPPO, and Xiaomi.

    A full region matrix is compiled into the app, which explains the Shanghai contact: cn.iot.mova-tech.com:13267, eu.iot.mova-tech.com:13267, cn-dev/cn-pre/cn-uat, *.mt.cn.iot.mova-tech.com:19974, app.mova-tech.com, ugrow.dreame.tech, and oss.iot.dreame.tech/public/app/app_config.json. App-version metadata is pulled from Xiaomi's FDS CDN at cdn.cnbj2.fds.api.mi-img.com/dreame-public/....

    The iOS build keeps session tokens in the Keychain and integrates device-integrity checks plus App Tracking Transparency, a data-handling posture ahead of Android's.

    Store labels contradict each other and the wire

    The Google Play data-safety label reads "No data collected." The Apple label declares Photos/Videos and Audio Data handled for App Functionality and Personalization. The two labels contradict each other, and neither matches the observed traffic. The iOS App Store label specifically omits the ByteDance, Umeng, and Aliyun flows the static review found.

    Status and next step

    The robot-to-cloud payloads are still TLS-encrypted. The capture yields destinations, IPs, ports, cadence, and volumes, but not decrypted robot payloads. The app-side plaintext correlates with them but is not the robot's own payload.

    Root work is in progress over hardware FEL/fastboot on the Allwinner MR813 with SecureBoot enforced. The device internals were read over UART, and the flash and root toolchain is staged and mid-process, with a build-config step pending an external turnaround.

    The next step is full root access to the device, to decrypt what the robot itself sends.

    • security
    • android
    • ios
    • reverse-engineering
    • privacy
    • mobile-security