Instant weight loss: how to strip Universal Apps

No sooner do we get an improvement like Universal Apps, which run native seamlessly on both Intel and Apple Silicon Macs, than users want to remove executable code from them, and make them Intel- or ARM-only. This article looks at what is involved in stripping a Universal App so that it only runs on one architecture, and whether this is feasible or breaks their signature.

Apple’s last processor transition started in 2006, when Intel Macs became available. The following year, in Mac OS X 10.5, code signing was introduced, and it was first checked by Gatekeeper as late as 2012, in Mac OS X 10.8. So the whole of the last transition, with its Universal Binaries, was completed before code signing became prevalent or enforced. With Catalina and Big Sur, security has changed beyond all recognition: although they can still run unsigned code, that’s now the exception to the rule that all executable code has to be signed, and normally notarized too.

During that last transition, many users stripped their Universal Binaries, either of PowerPC or Intel code, according to the architecture of their Macs at the time. As executable code is often only a small part of total application size, the storage space which they gained often simply wasn’t worth the effort. At the time I was critical of this stripping, but users did it all the same.

Is it feasible with today’s Universal Apps and modern security protection? Will tampering with Universal Binaries break code signatures and notarization, and cause mayhem?

Stripping a Universal App

I’m sure that someone is already building a utility to do this, but for the moment I’ll use the command tool lipo, which is designed for this and related tasks with Mach-O files. As a test, I chose one of my own Universal Apps, Mints, which has a simple structure. It consists of one Mach-O executable binary which is located in its standard folder Contents/MacOS/Mints within the app. More complex apps will contain Mach-O binaries in other locations, such as helper apps. Apps almost invariably also contain dylibs, in /Contents/Frameworks, which can also be Universal Binaries, but here I’ll ignore them.

Before stripping Universal Binaries, confirm which architectures that Mach-O file supports using a command like
lipo -archs Mints.app/Contents/MacOS/Mints

In this case, only two are supported, as shown in the response
x86_64 arm64

Let’s remove the arm64 code and save the stripped executable to a new file, with
lipo -remove arm64 Mints.app/Contents/MacOS/Mints -output Mints.app/Contents/MacOS/Mints2
If you wanted to strip the Intel executable instead, use x86_64 instead of arm64 in that command.

Then check the supported architectures again:
lipo -archs Mints.app/Contents/MacOS/Mints2
which confirms what we wanted with the sole response
x86_64

All you then need to do is remove the Universal Mints.app/Contents/MacOS/Mints and rename Mints2 to Mints. That shrinks the Mach-O executable from 442 KB to 214 KB, a total saving of 228 KB.

Testing the stripped app

Launching the stripped version of Mints is a good test here, as the app, like almost all of mine, checks its own signature every time that it’s opened. To my surprise, this worked fine, and Mints was none the worse for having part of its innards removed. Examining it with my free ArchiChect confirmed that it was no longer a Universal App, and only supported Intel Macs. There’s an interesting detail in the codesign check, where it’s reported that its format is “app bundle with Mach-O universal (x86_64)”. This means that the Mach-O executable still has the structure of a Universal Binary with its headers, but only contains the single executable.

stripuniversal1

Finally, I added a quarantine flag using xattred and moved the stripped app to a different volume, to trigger full Gatekeeper checks.

stripuniversal2

The stripped Mints app came through with flying colours: not just signed properly, but its notarization still holds good.

Inside the Mach-O Universal Binary

To explain how the stripped app still had its code signatures intact, refer back to my first look at the Mach-O Universal Binary format. This refers to each of the included executables containing their own embedded certificates. When macOS checks signatures thoroughly, it ensures that each supplied executable (and other signed contents) is signed, and because the Universal Binary was stripped properly using lipo, this remains the case. It’s just that the executable for another architecture has been removed, something which clearly doesn’t result in a code signing error.

Conclusion

If you really want to, and provided that you use lipo to do the job properly, in the great majority of apps stripping Universal Binaries to support fewer architectures should be perfectly safe. There will always be some apps in which the resulting bundle won’t work properly, I suspect, and you don’t want to do this to your only copy of any app. But you’ve got to ask yourself whether mutilating apps in this way is worth the effort: in the case of Mints, it saved just 228 KB from a total app size of 17.2 MB. That’s just 1.3%. Even applied across a large Applications folder, that seems pretty futile.