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:

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:

  1. node-machine-id isn't tamper-resistant. It was designed for convenience, not security. The npm page itself doesn't claim any anti-tampering features.

  2. Relative PATH resolution is the root cause. If the library called /usr/sbin/ioreg with the full path, this particular trick wouldn't work (though there are other ways to hook it, like DYLD_INSERT_LIBRARIES).

  3. 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.

  4. 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:

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!