New listing inherits state from a deleted one — every upload rejected by the automated security scan

I am the developer of Fasting Timer (widget). Every package I upload to my listing is rejected by the automated security scan, with no detail about the finding, and I have found something that suggests the problem is the listing itself rather than my code. Posting here in case a Garmin engineer can look at it, and in case anyone else has hit the same thing.

What happens

Three rejections in two days, each about 40 minutes after upload:

  • Sept 10, 4:10 pm — v2.4
  • Sept 11, 4:18 pm — v2.5
  • Sept 11, 5:46 pm — v2.5.1

The email is always identical: "An automated scan identified a security threat in the Fasting Timer app." No detail about what was found.

v2.5.1 was a byte-identical binary to v2.5

The same .iq file, re-uploaded without recompiling (SHA-256 0ee2c5e06a0367078be787090ad4f93462e5d5e253666c98c4a8a5a0f9441a16). The only thing I changed between the two submissions was outside the package. It was rejected again. So the rejection is deterministic and does not depend on what is inside the binary.

What I have ruled out

  • Payment / donation references. v2.4 had a paypal.me link embedded. I removed it in v2.5 along with the screen that displayed it. Zero payment strings in any of the 41 device binaries. Rejected anyway.
  • Suspicious content in the binary. strings over all 41 .prg files returns exactly one URL — my own site, which renders the user's exported history from the URL fragment. No base64, no high-entropy blobs, no matches for passw / token / api_key / secret / eval / exec / shell / crypto / wallet / payload.
  • Domain reputation. That domain was registered in 2018 and is flagged clean by 0 of 89 vendors on VirusTotal.
  • Permissions. Background Activity and On-Device Alerts & Notifications only. No Positioning, no Communications.
  • Package integrity. Every upload reports Status: Verified, Signature: Verified, 41 compatible devices, SDK 9.2.0.

The part that points at the listing, not the package

On September 9 I accidentally deleted my original listing, and discovered that the App ID of a deleted app stays permanently blocked ("Another application is already using the manifest application ID"). I had to create a new listing with a new App ID. Every submission to that new listing has been rejected. Not one has ever passed.

Today I tried to leave a rating on my own new listing. The store refused it with "you have already rated this app". That listing shows zero reviews and I have never rated it — the rating I left was on the deleted listing, a different listing with a different App ID.

So the review system treats the two listings as the same application, while the store page shows them as separate. Something in the backend still links them. If review state carried over from the deleted listing, other state may have carried over too — which would explain a deterministic rejection that ignores the contents of the binary.

Questions

  1. Can a Garmin engineer check whether this listing inherited any status or flag from the deleted one?
  2. Is there any way for a developer to see the actual finding of the security scan? The email gives none, and every blind attempt costs a day.
  3. Has anyone else recreated a listing after a deletion and then hit permanent scan rejections?

Two things that made this much harder to diagnose

Step 2 of the submission (description, screenshots, release notes) publishes successfully even when the binary is rejected. My store page advertised a new version for two days while the store kept serving an old binary to watches. That is why it took me several attempts to realise the uploads were failing at all.

The Download button in "Manage Your App" does not download anything. It opens a Terms of Use dialog, then a device picker, then a permissions dialog, and ends with "will be installed the next time your device syncs". There appears to be no way for a developer to obtain the binary the store is currently serving, which is exactly what I needed to diagnose this.

  • When you use the download button from your dashboard the app will get installed

    1) with Garmin Express

    or

    2) using the CIQ mobile app and the proper device selected

    I use both methods, including for beta apps (which will never be approved)

    ----

    try using a very simple name and description and "what's new" in case there is something with the text or characters (like emojis) you are using.  You using any terms/device names that Garmin you longer allows?

    ----

    how did you generate your new app id? Might your app require you include your own privacy policy?

    ----

  • Thanks jim, a couple of those questions turned out to be really useful. I've got an update, but first something I'd love a Garmin engineer to look at, because it's not about my app, it's about the store itself.

    The reviews thing

    Back on Sept 9 I deleted my own listing by accident. Turns out the app id of a deleted app is blocked forever, so I had to start over with a brand new listing and a brand new id.

    A few days ago I went to leave a rating on the new listing, and the store told me I'd already rated it.

    I hadn't. That listing has zero reviews and I've never touched it. The only rating I ever left was on the old one, which is a different listing with a different app id and doesn't exist anymore.

    So somewhere in the backend the two listings resolve to the same app, even though the store shows them as completely separate. Feels like an indexing thing, where the new listing is still keyed to whatever the deleted one owned.

    Which means two things, and both seem worth fixing. Anyone who rated my old listing can't rate the new one and would have no clue why. And the reviews from the deleted listing are apparently still sitting there, visible to nobody.

    If you've ever deleted a listing and recreated it, I'd be curious whether you can rate your own new one. That'd tell us if this is general or just me.

    Your questions

    The app id: I generated it as a random 32 character hex string instead of letting the SDK do it. Having actually looked at it now, it's not a well formed UUID. The version nibble says 4 but the variant nibble is 7, which isn't RFC 4122. The portal took it and the manifest validates, so I doubt that's the culprit, but good call asking.

    The characters idea was the one I liked best, so I went and checked it properly. It doesn't hold up, and the way I found that out is the interesting part.

    Figuring out which binary the store is actually serving

    This had me stuck for three days. The Download button doesn't give you a file, and there's no other way to see what the store is serving. But once the app is installed through Garmin Express, the watch's app list shows you the size of each app. Mine said 95.3 KB.

    So I rebuilt a handful of old commits for my device with -r and compared. One matched exactly: 97596 bytes, which is 95.3 KiB. That's the commit right before I added achievements, the one I uploaded on Sept 9 when I made the new listing.

    So that binary passed the scan and went live, and every single thing I've uploaded since has bounced. That gives me a proper bisection point, and honestly it changes everything:

    The listing isn't broken. It accepted a package just fine.

    The problem's in the binary, somewhere inside one commit.

    The description text is definitely not it. The listing that passed already had the same box drawing separator (U+2500, 104 times), the same bullets, 46 distinct non-ASCII characters, and the same device names — fenix, Forerunner, Enduro, Venu, vivoactive, eight or nine times each. Garmin approved that text exactly as it was.

    The CSV export isn't it either. That URL was already in the binary that passed.

    What's left in that commit is the achievements code, a small thank-you screen, and dropping the fenix 6 package. I'm going after the thank-you screen first, since it's the only bit that adds anything external — it stuck a second copy of my own domain in the binary as a plain string.

    If anyone else ends up stuck like this, the size trick is the only way I found to identify what the store is serving when you can't download it.

    Couple of open questions

    Is there a current list anywhere of terms and device names Garmin doesn't allow in descriptions? I couldn't find one.

    And does an app that exports your own data through a QR code need a privacy policy on the listing? Nothing gets uploaded, the data rides in the URL fragment which browsers never send to the server, but I can see how it looks from outside and I'd rather just add one than argue about it.

    Either way I'll post what happens with the next upload.

  • A few days ago I went to leave a rating on the new listing, and the store told me I'd already rated it.

    It could be the store prevents you from rating your own app.  That couldn't surprise, as it stops you from bosting the ratings of your own app.

  • Fair thought, but I rated the old listing myself without any trouble. Same account, and it was just as much my own app. So the store does let you rate your own.

    And the wording is specific: it says I have already rated this app, not that I can't rate it. Those are two different messages.

    That's what makes me think it's resolving the new listing to the old one, rather than blocking self-ratings.

  • Could be they tightened the rules, as rating you own app is kind of a cheat....  I've been doing CIQ for a decade, and I don't think I ever tried to rate my own app for that very reason.

    It also used to be you could rate an app you've never download which did lead to cheating..

  • Quick update: it's fixed, and the cause was something I'd never have guessed.

    It was a thank-you screen. Three lines of text and a constant with my own domain in it, SITE = "website.com", a bare domain with no scheme. I pulled that screen out, uploaded the same code otherwise untouched, and it sailed through. No rejection email, and the new version is live. I installed it on my watch by searching for it in the Connect IQ app rather than through the dashboard, so it's the public build and not my own upload coming back to me.

    What's odd is the other URL in the binary, "webside", had been in there for weeks and never bothered anything. The difference seems to be the bare domain with no scheme, which made my domain appear twice per package instead of once. I can't prove which of the two it was without risking another 40 minute round trip, so I'm leaving it alone.

    How I narrowed it down, in case it helps anyone

    Once the app is installed, the watch's app list shows the size of each app. Mine said 95.3 KB. I rebuilt old commits for my device with -r and compared until one matched exactly, 97596 bytes. That told me which binary the store was serving, which gave me a known-good point and a known-bad point one commit apart.

    Before that I'd burned three days on theories that were all wrong: the PayPal link I used to have in there, the content of the web page my URL points at, domain reputation, and the listing text with its odd characters. None of it.

    So if you're stuck on the same thing, that's the shortcut. Find the last binary that passed, by size, and bisect from there. Guessing costs 40 minutes a go and rules nothing out.

    And an update on the review thing

    I can now rate my own app. Five stars, went through fine.

    So they haven't tightened anything, jim. It let me do it on the old listing, it wouldn't let me on the new one, and now it does again. Something was genuinely stuck, and publishing an approved version seems to have unstuck it. Which fits the original symptom, the new listing resolving to the deleted one somewhere in the backend. Worth knowing if you ever have to recreate a listing.

    Thanks for the pointers, they helped more than the dead ends did.

  • Assuming we're talking about a beta app, I usually put the version in the AppName string, so I can see in the app list on the device if the correct version is installed or not. MANY times you think you installed the new version from Connect IQ pho0ne app, and you still have the old version.

    For production apps I tend to include the version somewhere visible. Like in the bottom of the About menu.