The Cloudflare Appstore (Appflare) Malware Audit
A link shortener's password-reset page should be a fairly boring place. You open the link, choose a password, and get back to work. In one app we reviewed, opening that page also handed the reset token to an upstream analytics service.
That was the most serious result of an agent-assisted audit of Appflare, an app store for your own Cloudflare account. I asked Codex to look through the catalog for code that had no business running in an app someone would install. The brief was deliberately narrow: credential theft, hidden data collection, backdoors, unrelated account changes. A list of routine hardening suggestions wouldn't answer it.
The review covered 139 local entries and 138 published bundles. It flagged two apps, rdyrct and 888wiki, for concrete data leaks and unexpected external storage. Those findings don't establish that anyone planted malware. They do give me a reason to hold those versions out of a self-hosted catalog until the behavior is fixed.
What Appflare puts in your account
Appflare handles the tedious part of trying a Workers app. Pick something from its catalog, fill in its settings, and the manager creates the Worker and the resources it needs, such as a D1 database or an R2 bucket. You get a dashboard for updates, custom domains, access control, and removal. The installed apps can keep running independently of the manager. The Appflare repository contains the manager and installer; the app entries live in a separate catalog repository.
It's a useful idea. A password vault, email inbox, wiki, or analytics dashboard can live under an account you control without becoming a weekend of deployment work. Appflare is an independent open-source project, not an official Cloudflare app store.
The public catalog shows the breadth of that promise. This is the project's catalog page, captured during the review; the findings below refer to the pinned versions we audited, rather than whatever the site lists next.
Owning the Cloudflare resources tells you where the server runs. To find out where the data goes, you still have to follow the code into the browser, third-party APIs, and upload handlers.
Following the code people would install
The first surprise was mundane: the manager checkout didn't contain the apps. Catalog entries point to upstream repositories and pin a commit. Reviewing the catalog JSON alone would tell us what the installer asks for, while missing most of what the installed software does.
Codex split the entries across three review agents. They fetched the exact source commits and inspected build hooks, authentication paths, scheduled work, payment destinations, and outbound requests. Suspicious matches needed context. An email client calling an email provider is doing its job. A notes app sending an attachment to an unrelated file host needs an explanation.
The review also downloaded every release bundle named by our index snapshot. All 138 manifest digests matched the index, and all 16,203 manifest-listed files matched their recorded sizes and SHA-256 hashes. Eleven catalog source pins had moved ahead of their published releases, so the review checked both sets of commits and their differences.
Those checks tied the investigation to the bytes being distributed. They didn't establish that the bytes were safe. There was no independent signature verification or reproducible rebuild, and pattern searches plus targeted code review can't clear every line of every dependency.
Our per-app coverage file records the scope. The count of 139 includes our local Mainbrella Voice entry, which wasn't in the 138-app published index. The snapshot is from October 16, 2026; it isn't a claim about later releases.
rdyrct sends reset tokens to analytics
In rdyrct 1.0.0's HTML, a script tag loads a fixed tracker from stats.brnr.dev. The same tag is in the published frontend. It runs separately from the app's PostHog consent flow, and the Worker's production security policy explicitly allows that host.
A third-party script tag was a reason to investigate, not enough to accuse the app of stealing credentials. The important part was what the script sent. The copy fetched during the audit immediately posts the current pathname and query string to its collector and repeats that on navigation.
The reset page reads its credential from ?token=. It later passes that token and the chosen password to the password-reset call. The analytics request happens when the page opens, while that credential is still useful.
The browser makes the extra trip. The app can run entirely on your Cloudflare resources and still arrange for your browser to send a reset link elsewhere.
To check the data flow, the reviewed tracker ran in a small mock browser context with synthetic state. Every network call was intercepted. The captured request included this body:
{
"name": "pageview",
"path": "/reset-password?token=SYNTHETIC_RESET_TOKEN",
"referrer": "",
"domain": "my-rdyrct.example"
}
Its destination was the upstream stats.brnr.dev/api/event/… collector. No real reset token was used, no collector received the synthetic request, and we didn't perform an account takeover. The combination of the captured payload and the reset code establishes the exposure: someone with access to that collector's data could try the token before it expires or gets consumed. Invitation URLs carry tokens too, and those paths are collected by the same script.
Calling this ordinary pageview analytics doesn't make the token less of a credential. I'd remove the fixed tracker from self-hosted builds. Merely disabling PostHog wouldn't remove this separate script.
888wiki's private pages have an upstream audience
The second app was 888wiki, a notebook with Markdown, block, canvas, and whiteboard editors. The reviewed release, 0.0.0-20261013.b9ed1c1, defaults to a fixed Flow tracker at flow.aiurl.tw. Its configuration supplies the author's tracking URL, and its templates include the script on editor and admin pages as well as shared pages.
The fetched tracker was configured to collect query strings and fragments and to ignore Do Not Track. The intercepted reproduction captured a private-looking note path, its title, and the synthetic query and fragment, all addressed to flow.aiurl.tw/collect. The captured payloads are available to inspect; the title and link values are invented test data.
For an unprotected note, knowing the path is enough to open the editor. The route serves that note before asking for authentication when neither password is set. Sending the path to an analytics collector therefore exposes access to those notes. Individually password-protected notes keep their password check, although their titles and locations still leak. Nothing in this reproduction demonstrates a password bypass.
There's an important wrinkle here. The upstream README mentions Flow and describes it as privacy-preserving. This isn't evidence of a secret component concealed from every reader. It is evidence that a feature described that way sends private page information to an upstream collector, by default, in an independent install.
Flow can be disabled by setting SCN_FLOW_TRACKER_URL to the string 0, false, or none. Leaving it empty selects the upstream default again. The audited catalog entry doesn't expose that setting or turn the tracker off.
An image stays; a PDF leaves
888wiki's file handler made the storage problem especially easy to see. Its block-editor upload function branches on the file's MIME type. Images go to /upload, the app's own endpoint. Every other file goes into a multipart POST to box.david888.com, with two other external hosts as fallbacks.
That callback is wired directly into the generic file upload. It doesn't ask whether the note is private or whether the owner chose external storage. Two attachments added to the same note can take very different routes.
The reproduction exercised the extracted upload function with a synthetic PDF and an intercepted fetch. The resulting POST contained the whole test file, not just its filename or a hash. No document was sent to the live service.
Some of 888wiki's other controls explicitly say “Upload to 888box,” and its README discloses external storage for large attachments and published audio. Those labeled flows weren't the basis for this finding. The generic block-editor upload doesn't identify its destination before handing over the file. Putting a password on the note doesn't bring those bytes back inside your account or give your Worker control over them.
I'd route these files through the install's R2 bucket. If external storage is part of the product, the operator should choose it and the person uploading should see where the document is going.
What survived the false-positive checks
Following the settings mattered as much as finding a hostname. ImgBed has an upstream Sentry reporting path that copies request headers, including authentication headers, into reporting data. That deserves care, but the catalog disables it by default and explicitly warns about header sharing. It didn't belong in the same bucket as an unconditional token leak.
Other suspicious-looking calls turned out to be user-configured integrations, disclosed aggregate telemetry, or Durable Object requests whose URL-like strings never leave the account. Counting every fetch() as an exfiltration attempt would have produced an impressive-looking pile of nonsense.
The review didn't find an obvious miner, hidden attacker administrator, payment diversion, or unrelated account-changing payload in the paths it examined. That doesn't clear the remaining apps. The agents used broad static searches and focused source review, not a human reading every line or a behavioral sandbox running every app. Native and transitive dependencies weren't independently audited.
The reproduction notes and snapshot identifiers distinguish what was read, what was exercised, and what remains untested. The remote trackers are mutable; their hashes identify the copies fetched on the audit date. We also haven't established that anyone used the leaked values. The behavior is enough to recommend fixing these versions without inventing a compromise history or assigning intent to their authors.
Appflare makes a collection of useful Workers apps much easier to install. I'd like that to succeed. For a catalog built around running software in your own account, though, the install page needs to account for the data that leaves it. A reset credential in a pageview and a private document on an upstream file host are concrete things to remove before asking users to trust that promise.