How Electron Apps Identify Your Mac (and How Easy It Is to Spoof)
Published on
Disclaimer
This article is written for educational and research purposes only. I don't encourage using this to abuse free trials or bypass licensing. If you find an app useful, please support the creators by paying for it. The goal here is to raise awareness among developers about a common weakness in device identification on macOS.
Background
I was recently investigating how a popular Electron-based desktop app registers devices with its backend. During a source code recovery exercise (the app ships as a webpack bundle inside an Electron .asar archive), I found something interesting: the app uses the node-machine-id npm package to get a unique identifier for the computer.
This is a very common approach. If you search npm for "machine id" you'll find quite a few packages, but node-machine-id is by far the most popular one, with millions of weekly downloads. Many Electron apps rely on it for things like:
- Device registration (linking a license to a specific computer)
- Trial enforcement (preventing users from restarting a free trial on the same machine)
- Analytics and telemetry
How It Works on macOS
On macOS, node-machine-id runs this shell command:
ioreg -rd1 -c IOPlatformExpertDevice
This dumps a bunch of info about your Mac, including the IOPlatformUUID, which is a hardware-level UUID that stays constant unless you reinstall the OS or swap the logic board. The library extracts that UUID, optionally hashes it with SHA-256, and returns it as the "machine ID".
The important detail here is that the library calls ioreg without an absolute path. It relies on PATH resolution to find the ioreg binary. And that's the weakness.
The Bypass
Because the command is just ioreg (not /usr/sbin/ioreg), you can create a wrapper script that intercepts the call and returns a random UUID instead of the real one.
Create a directory for the wrapper:
mkdir -p ~/bin
Create the fake ioreg script:
cat > ~/bin/ioreg << 'EOF'
#!/bin/bash
if [[ "$*" == *"IOPlatformExpertDevice"* ]]; then
UUID=$(uuidgen)
cat <<IOREG
+-o Root <class IORegistryEntry, id 0x100000100, retain 11>
{
"IOPlatformUUID" = "$"
}
IOREG
else
/usr/sbin/ioreg "$@"
fi
EOF
chmod +x ~/bin/ioreg
Now launch the app with your ~/bin directory first in the PATH:
PATH=~/bin:$PATH open '/Applications/Some Electron App.app'
That's it. Every time the app launches with this PATH, it gets a brand new random UUID. The app thinks it's running on a different machine.
For non-IOPlatformExpertDevice queries, the script falls through to the real /usr/sbin/ioreg, so everything else works normally.
I Tested It
I tried this with Wispr Flow, a dictation app built on Electron. After looking at their bundled source code, I confirmed they use node-machine-id and call ioreg without an absolute path. The device registration endpoint (POST /api/v1/user/register_device) receives a device_id field derived from this machine ID.
When I launched the app with the spoofed PATH, it registered as a completely new device. Worked on the first try, no other modifications needed.
Why This Matters for Developers
This isn't some sophisticated exploit. It's a one-liner shell trick. Anyone who can Google "reset trial mac" and follow basic terminal instructions can do this. Here's why developers should care:
node-machine-idisn't tamper-resistant. It was designed for convenience, not security. The npm page itself doesn't claim any anti-tampering features.Relative PATH resolution is the root cause. If the library called
/usr/sbin/ioregwith the full path, this particular trick wouldn't work (though there are other ways to hook it, likeDYLD_INSERT_LIBRARIES).Device ID alone isn't enough for licensing. If your business model depends on tying a license to a device, consider combining multiple signals (hardware serial, disk UUID, network interfaces) and doing server-side anomaly detection rather than trusting a single easily-spoofed value.
Trial enforcement on the client side is fundamentally weak. Anything running on the user's machine can be modified by the user. Server-side rate limiting and abuse detection will always be more robust than client-side checks.
The wikiHow article on resetting trial periods on Mac lists other common tricks like deleting plist files and preference folders. The PATH interception approach is unique in that it doesn't require modifying any app files or system preferences at all — it's completely external.
Possible Mitigations
If you're shipping an Electron app on macOS and you use node-machine-id or similar, here are some things you could do:
- Use the absolute path
/usr/sbin/ioregwhen calling system utilities. Fork or patch the library if needed. - Combine multiple identifiers. Hardware serial number, disk UUIDs, and MAC addresses are harder to spoof simultaneously.
- Validate on the server. Look for suspicious patterns: the same account registering dozens of "different" devices, device IDs that change on every launch, etc.
- Don't rely solely on device ID for trial enforcement. Pair it with account-level limits, payment verification, or time-limited tokens issued by your server.
Conclusion
Many Electron apps on macOS trust a single ioreg call to identify the machine. Because node-machine-id uses a relative command path, spoofing it takes about 30 seconds and a 10-line shell script. If you're a developer relying on this for anything security-sensitive, it's worth reconsidering your approach. And if you're a user — please just pay for the software you use.
Please, share (Tweet) this article if you found it helpful or entertaining!