# Can You Trust a Super-App? Part 2

This is the second of two posts responding to *"Don't Trust the Super-App: A Case Study of Russia's Max"* (arXiv:2609.11814), a September paper from Michigan, Calgary, Georgia Tech and IIT Delhi.

[Part one](https://devsecmike.hashnode.dev/) dealt with what Max can do to the mini-apps it hosts. The paper's argument there is that a super-app holds total, unauditable power over the web apps running inside it. It can capture their screens, read their storage, inject code into them, rewrite their traffic and issue their users' identities and that none of this is a bug, because all of it follows from how Android hosts web content. I re-tested all the claims on a new build and they held, along with one the authors had not tested: the "block screenshots" protection a mini-app can request turns out to shield it from you, while leaving it fully readable by Max.

This post is about the layer above that. Part one asked what the app can do to the things inside it. This one asks who tells the app what to do and why that changes how you should read the paper's most careful sentence.

Their conclusion contains a sentence that I want to take seriously rather than use as a gotcha:

> While we didn't observe Max actively exploiting these capabilities maliciously…

That is an honest thing for researchers to write, and I believe it. What I want to do in this post is explain why it was never likely to say anything else and that has nothing to do with the quality of their work.

Max is not one program that behaves one way. It is a client whose behaviour is assembled at runtime from configuration its servers push to it, per account. Two people running the identical APK on identical phones can be running materially different software. A researcher with a fresh test account on a clean device is, by construction, shown the default configuration and the default configuration is the one with the interesting switches turned off.

The evidence that follows is mine, gathered in June and again this month. It includes one finding that makes my own earlier work weaker and one that makes it stronger, and I will take them in that order.The claims

| # | Claim | Whose | Verdict |
| --- | --- | --- | --- |
| 1 | Max was not observed exercising the mini-app capabilities maliciously | Theirs | **True** |
| 2 | These capabilities are architectural, not Max-specific bugs | Theirs | **Confirmed** |
| 3 | The fix must come from the OS or app store | Theirs | **Largely agree, with one gap** |
| 4 | `proxy` is a targeted-only server key | **Mine** | **Changed since June** |
| 5 | The word-spotter slot on calls is unimplemented | **Mine** | **Strengthened** |
| 6 | Max operates a server-side kill switch for old clients | **Mine** | **Newly observed** |
| 7 | Sensitive config keys are withheld from ordinary accounts | **Mine** | **Holds** |
| 8 | The session token is stored in plaintext, outside the Keystore | **Mine** | **Still true** |

## What the server decides: whether you may run the app at all

I keep a test rig on an emulator with Max installed, so I can compare behaviour across versions. In June it ran 26.18.2. When I opened it this month to start this work, the app launched, reached the network, and stopped at this:

> **Your version of MAX is out of date** Unable to write and call with this version. Please update the app to continue using MAX

One button: Update. No dismiss, no continue, no reduced-functionality mode. A build that worked perfectly in June was refused in September, not because anything on the device changed, but because a server decided it was too old.

This is completely ordinary behaviour for a messaging app, and I want to be careful not to inflate it. Forcing upgrades is how vendors retire protocol versions and push security fixes. Every major messenger does something like it.

It is worth noticing anyway, for one reason: it is the simplest possible demonstration that the thing on your phone runs at the discretion of a server. Not its features, its existence. Whatever else follows in this post about per-user configuration, the baseline is that the vendor can switch off any client, at any time, for any reason, and the user's only option is to accept whatever the replacement does.

## What the server decides: which features you get

Max ships with a catalogue of server-controlled configuration keys. The client holds the list of key names; the server decides which of them are sent to your account and what values they carry. The client caches the result in a file called `settings.prefs`.

Counting the key names in the catalogue class of each build:

| Build | Config keys in the catalogue |
| --- | --- |
| 26.18.2 (June) | **364** |
| 26.32.1 (September) | **467** |

That is 167 added and 64 removed in three months — a 28% net increase. New arrivals include `call-record-v2`, `failover-hosts`, `net-dns-store-enabled`, `media-logger` and `crit-log-fix`.

The catalogue is only half the picture. The question that matters is which of those keys a normal account actually receives. In June I pulled the live `settings.prefs` from a logged-in, entirely unremarkable account. Of the 360 keys I counted then, 226 had been pushed.

Present by default were the ones you would expect a platform to run everywhere: network reachability checks, VPN-detection related keys, the MyTracker analytics toggle, the mini-app permission lists, a debug-report flag.

Absent were these five:

*   `log-full`
    
*   `log-sensitive`
    
*   `proxy`
    
*   `proxy-domains`
    
*   `calls-sdk-log-audio`
    

That absence is the finding, and it rests on a distinction worth spelling out. A feature switched off for everybody does not vanish from the config; it arrives with a false value. There are plenty of those, 27 of the boolean keys in that June pull were explicitly false, and 30 in the September one. So a key that is missing entirely *i*s not a feature turned off. It is a feature the server did not consider relevant to this account. Those five are provisioned per target rather than shipped to the fleet.

I re-ran the same pull this month, on 26.32.1, with a freshly created session. The cached config had roughly doubled in size. Four of the five were still absent.

The fifth, `proxy` **is** present on an ordinary account on 26.32.1. It might be that the capability has been generalised, or that the key now carries a benign default, or that my June account was simply configured differently from this one. I have not established which.

What's interesting is the mechanism of selective provisioning is demonstrably real, because four other keys still behave exactly that way. The specific example I had chosen moved. That is what happens when you test a live system twice, three months apart, and it is the reason I re-tested rather than republishing June's draft unchanged.

## Where it got stronger

In [a piece earlier this year](https://devsecmike.hashnode.dev/is-max-listening-to-your-calls) I chased the rumour that Max runs a hidden "word spotter" on calls, an on-device model listening for keywords. The answer was no. The calls SDK declared two machine-learning feature types, `ns` for noise suppression and `ws` for the word spotter, but only the noise-suppression delegate had an implementation. The `ws` slot was declared and empty. I said so, and noted that a dormant slot is worth watching in case it ever gained a consumer.

On 26.32.1 it has not gained a consumer. It has been deleted.

The feature type is gone. Across all fifteen classes in the current build that touch the ML feature code, there are zero references to a `ws` feature. The only `"ws"` string left anywhere in the app sits beside `"wt"`, `"websocket"` and `"webtransport"` — it is WebSocket, and unrelated. Noise suppression is still there and still wired up.

So the caveat that made that post's conclusion provisional has expired. The answer was no in June, and it is a firmer no now.

## What the server decides: which cryptography you get

The paper mentions, almost in passing, that they obtained two Max builds, one from a Russian collaborator and one from Google Play and found them "mostly similar, except for the cryptographic layer". They detect the Russian build by probing for a CryptoPro class, and switch on GOST TLS hooks when they find it. GOST is the Russian national cryptography standard.

I went looking for that in my build, and the result surprised me.

Across all eleven APKs in the 26.32.1 bundle, base plus every architecture and density split, there are zero CryptoPro classes, and no GOST native library. What there is, in the base APK, is this:

```plaintext
one.me.net.ssl.impl.GostPmsProperties
gostLicenseCheckEnabled
getGostLicenseCheckEnabled()Z
    Gost: false
```

`PmsProperties` is the naming Max uses for its server-pushed configuration. So the GOST path is not a separate build with different code compiled in. It is a configuration-gated capability**:** the client knows how to ask whether GOST is licensed for this account, and in my case is told no. Sitting alongside it in the September catalogue are three new TLS keys that did not exist in June, `net-ssl-use-api-tm`, `net-ssl-max-ext-tm-cfg` and `net-client-tls-timeout-enabled`.

I have shown the gate exists, that it reports false here, and that no CryptoPro implementation ships in this lineage. I have not shown that flipping a server flag would turn GOST on, because the library would still have to come from somewhere, a different bundle, a downloaded module, something. "Gated and unshipped in this build" is what the evidence supports. "One switch away" is not.

## What the server decides: who it believes you are

In [an earlier post on storage and encryption](https://devsecmike.hashnode.dev/can-max-read-your-messages) I showed that Max keeps its session token in a plain preferences file rather than the Android Keystore, and demonstrated that copying that file to another device logs you straight in, the token is not bound to the device it was issued to.

Fourteen versions later, on the latest build the token is still plain text, in the same file, outside the Keystore. That finding has not aged at all.

Tokens do rotate, the one issued to me in September is a different value from June's, so re-authenticating gets you a new one. But what the server accepts as proof of identity remains a string that anyone with file access to your phone can read and replay.

## So what does "we observed no abuse" mean?

It means what it says, and it is worth more than nothing. But set it against the above.

A researcher creating a fresh account on a clean device receives the default configuration. The default configuration, as I have measured it twice, is the one where the logging keys are absent, the audio-capture key is absent, and the proxy-domains key is absent. They are not observing a system with those features turned off; they are observing a system that was never told about them.

This is not a criticism of the paper. Their claim is architectural, that the capability exists and cannot be constrained or audited and my evidence supports it. The point is that "we looked and saw nothing" is a statement about what a default account is shown, not about what the platform does.

## Do their recommendations reach this layer?

The paper's fixes are aimed at Android and the app stores rather than at Max, which I think is correct, a vendor operating under pressure cannot be the guarantor of its own restraint. There are four:

**Fine-grained permissions.** Let users revoke `INTERNET` and network-state access, or grant them only while in use, and restrict background execution. This would blunt silent collection. It would not touch per-account provisioning.

**Transparency.** Notify the user when the host captures a hosted surface, show an OS-level indicator when you are inside a super-app-mediated context, maintain an OS-level list of installed mini-apps with revocable permissions. The first of these would have caught the screenshot finding in my previous post. Genuinely responsive.

**Auditability.** OS-enforced, tamper-resistant logging of host-to-mini-app actions, every `shouldInterceptRequest` call recorded where the host cannot edit it. Of the four, this is the one that bites hardest on what I actually observed, because it converts an invisible capability into an evidenced one.

**Architectural redesign.** Per-mini-app sandboxing with its own storage partition and security principal, OS-mediated channels a host cannot read, signed mini-app bundles in a transparency log. This addresses the whole of the previous post at the root.

## Why it probably exists

Remote configuration is not sinister. Every app of any size has it. You need feature flags to roll out safely, to run experiments, to kill a broken feature without shipping a build, to vary behaviour by region and regulation. A catalogue of several hundred keys is what that looks like at scale for an app doing messaging, calls, payments and identity.

Selective provisioning is also not automatically sinister. Debug logging pushed only to accounts with an open support ticket is a normal, useful thing.

## Why it warrants attention

Max is being positioned as national infrastructure, with state services and an identity document already inside it. In that setting, the question of who can be shown different software, without their knowledge and without an update, stops being an engineering detail.

The honest summary is this. I have shown that the mechanism for per-account differentiation exists, that specific sensitive keys are withheld from ordinary accounts, that an entire cryptographic stack sits behind a licence check, and that the vendor can retire any client version on command. I have not observed any of this being used against any individual, and I want to state that as plainly as the researchers stated their own version of it. What I am describing is the shape of the control surface, not an allegation about its use.
