← all notes
Security Privacy Disclosure

Six years after X-Mode, Muslim Pro didn't need a data broker

The 2020 pipe is gone from the client. In 2026 the API handed out the same community for free, no account required.

Mahmoud
writes his own harnesses
September 6, 2026
13 min read

What happened in 2020

In November 2020, Motherboard reported that the US military was buying location data harvested from ordinary phone apps. One of those apps was Muslim Pro, a prayer times and Quran app with around 98 million downloads at the time. Reporters watched both the Android and iOS builds send granular location data to an endpoint belonging to X-Mode Social, a location data broker that sold to defense contractors and, through them, to the military. US Special Operations Command had bought access to a related product called Locate X. Senator Ron Wyden said X-Mode had confirmed it was selling data collected from phones inside the United States to military customers.

The reaction was fast. Bitsmedia, the Singapore company behind Muslim Pro, cut ties with X-Mode and called the claim that it sold user data to the US military untrue. In December, Apple and Google both banned X-Mode from their app stores. In January 2024 the FTC issued its first ever order prohibiting a data broker from selling sensitive location data, against X-Mode and its successor Outlogic, explicitly naming visits to places of religious worship as the kind of data at issue. The order was finalized that April.

That is the story people remember, and it is the reason I opened the app at all.

What I found in 2026

I tested Muslim Pro 17.7 on iOS with a proxy in August 2026, 532 flows in one session, and pulled apart the 17.7.1 Android binary alongside it. I also looked at Qalbox, the streaming product Bitsmedia runs on the same Firebase project.

The first thing I did was go looking for the ghost. I grepped the full traffic capture and the Android binary for X-Mode, Outlogic, Predicio, Cuebiq, Safestats, Lifesight, and the usual attribution SDKs that sit next to them. Nothing. Not one hit in 532 flows, not one string in the APK. Remote Config had third party SDKs switched off. Whatever else is true, the 2020 pipe is gone from the client.

I want to be careful about what that proves. Server side sharing is invisible from where I sit, and I cannot rule it out. What I can say is that the claim “this app still talks to data brokers” is not supported by anything on the device.

What I found instead was worse in a specific way. Muslim Pro in 2026 did not need a broker to expose who its users were, because the API handed a large piece of it to anyone who asked, for free, with no account.

The chain

Start with no credentials at all. GET /v1/community/posts/guest answered a request with no Authorization header, returning a hundred posts in one page. Each post carried a user_id, a username, usually a gender, frequently location.city, and the full text.

The text is the part that matters. This is the Ummah feed, the social layer of a prayer app, so people write what people write in a prayer app. Requests for healing. A marriage falling apart. Fear about a parent. Next to that, the city. The page I captured had entries from Philadelphia, Jakarta, Abuja, Hamburg, and Medina. My own test post got Gladbeck attached automatically, which I had never asked for or configured.

Take one of those user IDs and go to GET /v1/community/profiles/{uid}/followers. Also no token, also 200, returning follower user_id, username, gender, premium flag, and a last_key, which meant the list paginated. I requested exactly one row. That restraint was mine, not an access control.

The last step needed an account, but any account. GET /v1/users/{uid}/profile, called with any valid Firebase session against a stranger’s UID, returned that person’s name and birthday. The neighbouring routes got it right. /account and /settings/prayer-times returned 403 for a foreign UID. The boundary was not missing across the board. It had been forgotten in exactly one place, which is how these things usually go.

So: scrape the guest feed, pick a UID, register one throwaway account, get a legal name and a date of birth, attached to a city, a gender, a social graph, and a post about someone’s divorce.

Setting your profile to private did not take you out of this. is_private_profile=true appeared as a field in the response instead of acting on anything. I tested that on my own account: flipped it to private, then without any token pulled the individual post, the profile JSON, and the follower list. All 200, city and username intact. Then I flipped it back.

The promise from 2020, and the coordinates from 2026

Here is the part that connects most directly to the old scandal.

On a cold start, before any account exists on the device, the client POSTs raw coordinates with roughly ten decimal places to api2.muslimpro.com/api/v1/gmaps/location-details and gets elevation and timezone back. I saw it on iOS first and confirmed it on Android in a second capture, with no account present on the device at all.

Bitsmedia’s privacy policy, version 3 dated 22 June 2022, says the opposite in section 1.2.d: without an account, location stays on the device and is not sent to their servers. That sentence was there when I filed my report on 17 August. It is still there today.

I am not claiming this is X-Mode again. It is first party collection going to Bitsmedia’s own infrastructure, which is a different thing. But the promise the company made after 2020 was about location leaving the phone, and the phone of someone who never signs up still sends precise coordinates on the first launch. The policy and the binary disagree, and the policy is the version the user reads.

Google Analytics gets a related picture. From first launch, screen names go out: prayers, qibla, quran, community. After login the Firebase UID goes with them, plus a city parameter. Bitsmedia’s own policy treats inferred religious affiliation as sensitive personal information. A usage pattern made of those exact screens, keyed to the same UID that was sitting in the open feed, infers it fairly well.

Reporting it, and what came back

17 August 2026. Full report to dpo@bitsmedia.com, with reproduction steps for each item.

22 August. A reply from Ahmad in product and engineering. Three things were reported as fixed: the name and date of birth IDOR, the guest feed (with user_id removed and the endpoint restricted to Muslim Pro clients), and the follower lists. The rest was acknowledged as open. The policy would be updated to match the pre-login location behaviour. He asked me to verify and to confirm I had deleted the data.

23 August. I rebuilt everything from scratch to check. Fresh Play Store install of 17.7.1 on a clean emulator, new capture of 121 flows, 28 individual API probes.

One of the three was partly fixed. Two were not fixed at all.

The IDOR still returned name and date of birth. The guest feed still returned a hundred posts carrying a hundred user IDs. To test the claim about client restriction, I sent the same request with no headers whatsoever, no mp-platform, no mp-guest-id, no user agent, nothing. The response was byte identical to the one from the real client, 85,123 bytes both times. The follower lists still answered 200 without a token.

The one thing that had genuinely shipped: private accounts were being filtered out of the feed. I verified it as a controlled test rather than assuming, posting while private (not in the feed), then flipping to public and watching the same post appear within seconds. The flag worked there. It did nothing for the individual post route, the profile JSON, or the follower list, all of which still answered without a token.

My reply went out that night at 02:31. It was not a polite email.

One of the three items you reported as fixed is fixed. The other two are not, and I can still reproduce them on production as I write this.

I want to be honest with you: this is not acceptable. Muslim Pro is at close to 200 million downloads. The privacy of the people who open this app to pray is an amaanah in your hands, and a status email does not discharge it. Two of the three things you told me were fixed still hand the same data to anyone with curl and a free account. I had that result in the first ten minutes, which tells me nobody checked against production before your mail went out. I would rather not have to bring up X-Mode again in this correspondence, and telling a researcher that something is closed while it is still open is the fastest way to get us there.

I asked for three things: a dated commitment on the open items, a deploy reference for anything reported as fixed, and a straight answer about whether the three items had been checked against production before the mail went out.

Then nothing. Two weeks of silence.

6 September. I retested anyway, eleven probes, documented as R-01 through R-11.

What is fixed

This time the important parts hold up.

The IDOR is closed. The same foreign UID that returned a name and a date of birth on 23 August now returns 403. I ran my own profile as a positive control in the same session, which still returns 200 with both fields, so the test measured authorization rather than a broken token.

The follower lists are closed. followers?page_size=1 with no token returns 401.

The guest feed has lost its join key. A hundred posts, zero user_id. That is the difference between “this specific person in Hamburg wrote about her divorce” and “someone in Hamburg wrote about a divorce”. It breaks the chain, which was the point of the report.

The public token generator at mp-access-token-generator.web.app is gone, returning 404. That one was never a token oracle, it could not mint other users’ UIDs, but it did something uncomfortable: paste in the production Firebase config, which was lying around publicly, click Generate, and Google’s real consent screen appeared saying “Continue to Muslim Pro”. Finishing that flow with my own Google account created a fresh production user. As a phishing surface aimed at staff tokens, it did not need to exist.

What is still open

The guest feed itself still answers without an account. A hundred posts, 25 with a city, 100 with a gender, all with their text. I repeated it with hand written Android headers as well, same result, so there is still no real client binding.

The privacy toggle still does not protect the profile JSON. On 6 September I briefly set my own profile to private again, pulled it with no token, got 200 with is_private_profile=true sitting inside the response, and set it back. If your UID is known, private does not mean private.

The Android binary ships a Firebase web API key that is not restricted to the app package. getProjectConfig with that key lists 26 authorized domains, including an admin console, a Socket.IO admin dashboard, and an infrastructure monitoring UI, all of which answer from the open internet with 200 and their respective interfaces. The admin login itself held, I did not bypass it and did not try, but the internal attack surface is discoverable from a public APK. The same key allows createAuthUri, which answers whether an email address has a Muslim Pro account. Mine comes back registered=true, an invented address comes back false.

Think about what that endpoint is for a prayer app. It is an oracle that tells a stranger, one address at a time, whether a given person is likely Muslim. In light of what the FTC said in 2024 about religious worship data, that is not a minor hygiene item.

Two Google keys remain usable from any host on Bitsmedia’s bill, a Geocoding key sent in the clear as a query parameter by the Android client, and a static client key that still gets elevation and timezone out of their own maps proxy. That is money, not user data. The Places key sitting right next to them is correctly restricted, so it is clearly possible.

OTP send still accepts arbitrary email addresses with no auth and confirms delivery. Android still permits cleartext HTTP globally, with a network security config that is visibly still Google’s documentation template, example.com and all. Two notification receivers are exported without a permission. On Qalbox, the catalogue including playback URLs is readable without login, the DRM token carries issuer=client and an eight hour lifetime with no user binding, and preview streams sit unencrypted and unsigned on the CDN, including on premium titles.

And the privacy policy is still dated 22 June 2022, still containing the sentence about guest location that the traffic contradicts. An update was promised on 22 August. Nothing has changed.

Apple has been shipping 17.8 since late August and Google Play lists 2 September as the last update. I cannot say whether that build cleans up the client side findings, because the download failed on a full emulator. The backend results above do not depend on it, they came from direct requests against production.

How I tested this

I think this belongs in the article rather than in a footnote.

The IDOR proof used exactly one foreign UID, the same one across all three rounds. That person’s name and date of birth were never written down in readable form. The field was redacted in my very first note. On the retests I discarded the raw response immediately after checking whether the two fields were present, keeping only the shape: how many name tokens, what date format, no values.

The follower list was pulled with page_size=1. No graph crawl, no second foreign profile, no feed pagination, no reading anyone’s messages. Every write test ran on my own account and was reverted, test post deleted, privacy flag returned to its original value. The recheck script from 6 September holds all responses in memory only and prints structure and counters, nothing else.

I did not query the Widevine or FairPlay license server on Qalbox. Whether that client issued DRM token is accepted without a session is therefore unknown, and I would rather leave it that way.

The point

Bitsmedia closed the worst hole. That belongs at the front of any fair assessment, and I will say it plainly: the path from “no account” to “legal name and date of birth” no longer exists.

It took three weeks and two rounds of verification, and the fix that mattered arrived only after a status email had fallen apart under testing. On 22 August a mail said three things were done. On 23 August, two of them were back within ten minutes. The whole distance between a status report and a fix lives in that gap.

The 2020 story was about a broker in the supply chain, and it got fixed the way supply chain problems get fixed. The SDK came out, the broker got banned from both app stores, and four years later the FTC put it out of that business entirely. It was a clean narrative with a villain in it.

What I found in 2026 has no villain and no broker. It is an API that gives away a religious community’s social graph to anyone with curl, a privacy toggle that is a label rather than a control, a policy sentence that has been wrong for four years, and a key in a public binary that will tell you whether a stranger prays. Nobody sold anything. It was simply left open, which is the part that should worry people more, because there is no SDK to remove and no company for a regulator to name.

Timeline

DateEvent
Nov 2020Motherboard reports Muslim Pro sending location data to X-Mode, which sold to defense contractors. Bitsmedia cuts ties
Dec 2020Apple and Google ban X-Mode from their app stores
Jan and Apr 2024FTC issues, then finalizes, its first order banning a data broker from selling sensitive location data, against X-Mode and Outlogic
17 Aug 2026Report sent to dpo@bitsmedia.com
22 Aug 2026Reply from product and engineering, three items reported fixed
23 Aug 2026Verification: one partly fixed, two still live. Reply sent at 02:31
23 Aug to 6 SepNo response
6 Sep 2026Second verification: IDOR and follower graph closed, user_id removed from the guest feed, token generator gone. Profile JSON, Firebase key, email enumeration, both Google keys and the policy still open
15 Nov 2026End of the 90 day window

Sources