Hello,
We're developing endpoint security software using the Endpoint Security framework, and we've encountered challenges with the behavior change in macOS 15 regarding provisioning UDIDs in cloned VMs.
The Change
Prior to macOS 15, cloning a VM preserved its UDID (format: 0000FE00-9C4ED9F68BBDC72D). Starting with macOS 15, cloned VMs receive a new UDID generated from the host's Secure Enclave (format: b043d27202c7ac37ca3c6b82673302225485cae9), making each clone effectively a new device.
Our Workflow
We maintain a clean base VM image and clone it for each test run. We add the base VM's UDID to our provisioning profile once, then create clones which (previously) retained that same UDID, allowing us to start new testing cycles without re-registering devices. This is essential because our product involves low-level system integration through the Endpoint Security framework, and if something goes wrong during development, it has the potential to affect system stability. To prevent any cascading issues between test runs or different product versions, we need each test to start from a known clean state rather than reusing the same VM.
The Challenge
With each VM clone generating a new UDID, we're hitting Apple's device registration limits quickly. This particularly impacts:
- New team members who spin up VMs for the first time and can't run signed builds
- Our CI/CD pipeline where multiple test environments need provisioning profiles
- Developers testing different branches who need separate clean environments
Current Workaround
We've found that VMs created on macOS 14 and upgraded to macOS 15+ retain their original UDID format. However, we're concerned this workaround may stop working in future macOS versions, which would leave us without a viable path forward.
If the workaround stops working, our fallback would be signing each CI build with a Developer ID signature to allow running on any device. However, we'd prefer to avoid this as it would significantly increase load on Apple's signing infrastructure for what are essentially internal test builds.
We completely understand the security reasoning behind tying UDIDs to the host's Secure Enclave for Apple Account support. However, for development workflows that don't require Apple Account features in VMs but do require clean, isolated test environments, the previous behavior was quite valuable.
Question
Is there a recommended approach for teams in our situation? We're happy to explore alternative workflows if there's a pattern we're missing, or we'd be glad to provide more context if this is a use case Apple is considering for future updates.
Thanks for any guidance you can provide!
Feedback case: FB21389730
It’s hard to say exactly what’s going on without knowing what UTM is doing at the API level. You wrote:
I suspect that the UTM app is just …
Yeah, I tend to agree, but it’d be nice to be sure.
make sure we use appropriate approach to be aligned with the expectations
I think I can help with that (-:
macOS guests use three different provisioning UDID formats:
Type | Example
---- | -------
Intel | 564DB953-09B7-B72B-09CC-364469A86761
Apple silicon, old | 0000FE00-5275292241EAAFFD
Apple silicon, new | 16e55725fdced2252674f0fb03c22d44d0b63b35
My focus here is that last row. This is what you get when you create a macOS 15 (or later) guest on a macOS 15 (or later) host, and it’s what allows the guest to sign in to an Apple Account.
This UDID is tied to the Mac on which it was created. Consider this sequence:
- Create guest G1 on Mac H1.
- Boot it. Note the UDID is U1.
- Shut it down cleanly by choosing Shut Down from the Apple menu.
- Boot it again on H1. It will get U1 again.
- Shut it down cleanly.
- Copy all the files that make up G1, resulting in G1x.
- Boot G1x on H1. It will still get U1.
- Kill it without shutting it down by calling the
stop()method. - Delete the files that make up G1x.
- Repeat steps 6 through 9 as often as you like.
And now this sequence:
- Copy the files of G1 to H2, creating G2.
- Boot G2 on H2. It will get a different UDID, U2.
- Shut it down cleanly.
- Boot it again on H2. It will get U2 again.
- Shut it down cleanly.
- Copy all the files that make up G2, resulting in G2x.
- Boot G2x on H2. It will still get U2.
- Kill it without shutting it down.
- Delete the files that make up G2x.
- Repeat steps 6 through 9 as often as you like.
Note This all asumes you’re using the Virtualization framework in the most obvious way.
These sequences both make sense, and yield useful results, but it’s easy to do things in a way that causes problems. For example, in the second case, if you skip steps 2 through 5, each new guest will get a new UDID, and that’s probably not what you want O-:
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"