Security research artifact — disclosed to Cursor / Windsurf engineering for review of the editors' trial-validation and device-telemetry vector. Provided for reproduction and remediation discussion; not for redistribution.
This repository demonstrates two complementary techniques that, together, bypass per-device trial / abuse identifiers used by the Cursor editor (and by extension other VS Code / Electron forks that inherit the same telemetry stack):
- Static configuration spoofing — overwriting the editor's
storage.jsonwith fabricated identifiers and locking it read-only so the editor cannot self-correct on next boot. Seescripts/run/. - Runtime execution hooking — patching the Electron
main.jsto load a CommonJS shim (cursor_hook.js) that interceptschild_process.execSync(registry /ioreg//etc/machine-idreads),crypto.createHash(SHA-256 of the platform UUID),@vscode/deviceid,@vscode/windows-registry, andos.networkInterfaces. Seescripts/hook/.
The two stages reinforce each other: the static pass prevents the editor from rewriting the spoofed file at startup, and the runtime hook ensures that any code path which re-derives an ID from hardware queries still returns the spoofed value.
The toolkit rotates the following identifiers consumed by the editor's trial-tracking and analytics pipelines:
telemetry.machineIdtelemetry.macMachineIdtelemetry.devDeviceIdtelemetry.sqmIdMachineGuid(Windows:HKLM\SOFTWARE\Microsoft\Cryptography)IOPlatformUUID(macOS:ioreg -d2 -c IOPlatformExpertDevice)/etc/machine-id(Linux)- Primary interface MAC address (
os.networkInterfaces)
scripts/
hook/
cursor_hook.js # Runtime hook: intercepts ID-deriving Node APIs
inject_hook_unix.sh # Patches Cursor's main.js on macOS / Linux
inject_hook_win.ps1 # Patches Cursor's main.js on Windows
run/
cursor_mac_id_modifier.sh # Static spoof + permission lock (macOS)
cursor_linux_id_modifier.sh # Static spoof + permission lock (Linux)
cursor_win_id_modifier.ps1 # Static spoof + registry update (Windows)
lib/
read_existing_ids.py # Reads current telemetry IDs from storage.json
b6_patcher.py # Rewrites the b6() machineId helper in main.js
- Windows:
%APPDATA%\Cursor\User\globalStorage\storage.json - macOS:
~/Library/Application Support/Cursor/User/globalStorage/storage.json - Linux:
~/.config/Cursor/User/globalStorage/storage.json
The shim is injected near the top of the editor's main.js (after Sentry init). On load it generates — or reads from ~/.cursor_ids.json — a stable set of fake identifiers, then monkey-patches:
| Module | Hooked surface | Effect |
|---|---|---|
child_process |
execSync, execFileSync |
Returns synthetic output for reg query … MachineGuid, ioreg … IOPlatformExpertDevice, and machine-id / hostname reads |
crypto |
createHash('sha256'), randomUUID |
Returns the spoofed machineId when input looks like a platform UUID; returns spoofed devDeviceId for the first two randomUUID() calls |
os |
networkInterfaces |
Returns a single fabricated Ethernet adapter with the spoofed MAC |
@vscode/deviceid |
getDeviceId |
Returns the spoofed devDeviceId directly |
@vscode/windows-registry |
GetStringRegKey |
Spoofs MachineId / MachineGuid registry reads |
Override via env vars (CURSOR_MACHINE_ID, CURSOR_MAC_MACHINE_ID, CURSOR_DEV_DEVICE_ID, CURSOR_SQM_ID, CURSOR_MAC_ADDRESS) or by editing ~/.cursor_ids.json. Set __cursor_hook_config__.debug = true for verbose [CursorHook] logging.
macOS
sudo bash scripts/run/cursor_mac_id_modifier.sh
# optional runtime hook:
bash scripts/hook/inject_hook_unix.shLinux
sudo bash scripts/run/cursor_linux_id_modifier.sh
bash scripts/hook/inject_hook_unix.shWindows (admin PowerShell)
.\scripts\run\cursor_win_id_modifier.ps1
.\scripts\hook\inject_hook_win.ps1inject_hook_* accept --rollback / -Rollback to restore the original main.js from the auto-saved backup, and --force / -Force to re-inject after a Cursor update overwrites the patched file.
main.js→<resources>/app/out/backups/main.js.original(plus timestamped copies)- Windows
MachineGuid→%APPDATA%\Cursor\User\globalStorage\backups\MachineGuid.backup_YYYYMMDD_HHMMSS
Restore paths are documented inline in each script.
A successful end-to-end run on macOS looks like this (truncated; the Linux and Windows transcripts are structurally identical):
========================================
Cursor device-ID reset (macOS)
========================================
[INFO] Target user: olzhas
[INFO] Target user Home: /Users/olzhas
[INFO] Target user's primary group: staff
[INFO] Found Cursor app: /Applications/Cursor.app
[INFO] Found Cursor main.js: /Applications/Cursor.app/Contents/Resources/app/out/main.js
[INFO] Config file path: /Users/olzhas/Library/Application Support/Cursor/User/globalStorage/storage.json
[Check] Cursor process...
[Close] Closing Cursor process so the file can be modified...
[INFO] Cursor processes stopped
[Backup] Backing up original configuration...
[INFO] Created original backup: .../backups/storage.json.original
[INFO] Created timestamped backup: .../backups/storage.json.backup_20260524_222342
[Prepare] Device identifiers ready
machineId: 0157e4b48fc7573057235f3be7e5275eaf09411a0f9836b...
machineGuid: 7368873d-8ee0-43f4-a47a-d61cb22fadfd
devDeviceId: 03491173-9cc4-44ac-8463-c0f73c44ad88
macMachineId: a6d80553-cdde-43eb-91d8-39c089a4f06d
sqmId: {C23EF132-7B7E-4C6B-B003-C0FF7CB44A88}
[Inject] Injecting hook into main.js...
[INFO] Hook injected successfully
[Save] New ID config saved to: /Users/olzhas/.cursor_ids.json
[Hook] External hook deployed: /Users/olzhas/.cursor_hook.js
[Complete] All device-ID modifications complete
[INFO] You can now launch Cursor
[INFO] ID config file location: ~/.cursor_ids.json
Tips:
- To roll back: bash scripts/hook/inject_hook_unix.sh --rollback
- To force re-inject: bash scripts/hook/inject_hook_unix.sh --force
- To enable debug log: bash scripts/hook/inject_hook_unix.sh --debug
Identifier values shown above are illustrative; each run generates a fresh set and persists them to ~/.cursor_ids.json.
- Move trial / abuse signals to server-derived attestation rather than client-reported telemetry fields that any user-space process can rewrite.
- Treat
storage.jsonas untrusted on read; verify against a server-side record bound to the account, not the device. - Detect
main.jsintegrity drift at startup (signed hash check on packaged resources) and refuse to boot — or fall back to a server check — when modified. - Cross-check device IDs against multiple independent OS surfaces (TPM-backed where available) rather than a single registry/
ioreg/machine-idsource. - Flag accounts whose first-session telemetry rotates
devDeviceIdmore frequently than physical hardware change rates would predict.
MIT — see LICENSE. This repository is shared with vendor security teams for review of the disclosed vector; please do not redistribute.
