Files
go-cursor-help/scripts/hook/cursor_hook.js
煎饼果子卷鲨鱼辣椒 b9eeebc6fb ```
feat(cursor_hook): 新增fs模块Hook保护storage.json中的telemetry字段

- 新增fs.writeFileSync/writeFile拦截功能,防止storage.json中
  telemetry相关字段被覆盖
- 实现storage.json写入保护机制,维护telemetry.machineId、
  telemetry.macMachineId、telemetry.devDeviceId、telemetry.sqmId等
  关键字段的完整性
- 支持同步和异步两种写入方式的拦截处理
- 添加动态导入fs模块的Hook支持
- 在文档注释中添加第6项功能说明

fix(cursor_linux_id_modifier): 修复Python脚本缩进问题

- 将Python代码从混合缩进改为标准4空格缩进,避免IndentationError
- 修正字符串转义处理逻辑,提升函数特征匹配准确性
```
2026-02-03 19:53:10 +08:00

692 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Cursor 设备标识符 Hook 模块
*
* 🎯 功能:从底层拦截所有设备标识符的生成,实现一劳永逸的机器码修改
*
* 🔧 Hook 点:
* 1. child_process.execSync - 拦截 REG.exe 查询 MachineGuid
* 2. crypto.createHash - 拦截 SHA256 哈希计算
* 3. @vscode/deviceid - 拦截 devDeviceId 获取
* 4. @vscode/windows-registry - 拦截注册表读取
* 5. os.networkInterfaces - 拦截 MAC 地址获取
* 6. fs.writeFileSync/writeFile - 拦截 storage.json 写入,保护 telemetry 字段
*
* 📦 使用方式:
* 将此代码注入到 main.js 文件顶部Sentry 初始化之后)
*
* ⚙️ 配置方式:
* 1. 环境变量CURSOR_MACHINE_ID, CURSOR_MAC_MACHINE_ID, CURSOR_DEV_DEVICE_ID, CURSOR_SQM_ID
* 2. 配置文件:~/.cursor_ids.json
* 3. 自动生成:如果没有配置,则自动生成并持久化
*/
// ==================== 配置区域 ====================
// 使用 var 确保在 ES Module 环境中也能正常工作
var __cursor_hook_config__ = {
// 是否启用 Hook设置为 false 可临时禁用)
enabled: true,
// 是否输出调试日志(设置为 true 可查看详细日志)
debug: false,
// 配置文件路径(相对于用户目录)
configFileName: '.cursor_ids.json',
// 标记:防止重复注入
injected: false
};
// ==================== Hook 实现 ====================
// 使用 IIFE 确保代码立即执行
(function() {
'use strict';
// 防止重复注入
if (globalThis.__cursor_patched__ || __cursor_hook_config__.injected) {
return;
}
globalThis.__cursor_patched__ = true;
__cursor_hook_config__.injected = true;
// 调试日志函数
const log = (...args) => {
if (__cursor_hook_config__.debug) {
console.log('[CursorHook]', ...args);
}
};
// ==================== ID 生成和管理 ====================
// 生成 UUID v4
const generateUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
// 生成 64 位十六进制字符串(用于 machineId
const generateHex64 = () => {
let hex = '';
for (let i = 0; i < 64; i++) {
hex += Math.floor(Math.random() * 16).toString(16);
}
return hex;
};
// 生成 MAC 地址格式的字符串
const generateMacAddress = () => {
const hex = '0123456789ABCDEF';
let mac = '';
for (let i = 0; i < 6; i++) {
if (i > 0) mac += ':';
mac += hex[Math.floor(Math.random() * 16)];
mac += hex[Math.floor(Math.random() * 16)];
}
return mac;
};
// 加载或生成 ID 配置
// 注意:该 Hook 由脚本注入的 Loader 通过 CommonJS(require) 方式加载,
// 为避免出现 import.meta 等仅 ESM 支持的语法导致 Cursor 启动期解析失败,这里保持纯 CommonJS 写法。
const loadOrGenerateIds = () => {
const fs = require('fs');
const path = require('path');
const os = require('os');
const configPath = path.join(os.homedir(), __cursor_hook_config__.configFileName);
let ids = null;
// 尝试从环境变量读取
if (process.env.CURSOR_MACHINE_ID) {
ids = {
machineId: process.env.CURSOR_MACHINE_ID,
// machineGuid 用于模拟注册表 MachineGuid/IOPlatformUUID
machineGuid: process.env.CURSOR_MACHINE_GUID || generateUUID(),
macMachineId: process.env.CURSOR_MAC_MACHINE_ID || generateHex64(),
devDeviceId: process.env.CURSOR_DEV_DEVICE_ID || generateUUID(),
sqmId: process.env.CURSOR_SQM_ID || `{${generateUUID().toUpperCase()}}`,
macAddress: process.env.CURSOR_MAC_ADDRESS || generateMacAddress(),
sessionId: process.env.CURSOR_SESSION_ID || generateUUID(),
firstSessionDate: process.env.CURSOR_FIRST_SESSION_DATE || new Date().toISOString()
};
log('从环境变量加载 ID 配置');
return ids;
}
// 尝试从配置文件读取
try {
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
ids = JSON.parse(content);
// 补全缺失字段,保持向后兼容
let updated = false;
if (!ids.machineGuid) {
ids.machineGuid = generateUUID();
updated = true;
}
if (!ids.macAddress) {
ids.macAddress = generateMacAddress();
updated = true;
}
if (!ids.sessionId) {
ids.sessionId = generateUUID();
updated = true;
}
if (!ids.firstSessionDate) {
ids.firstSessionDate = new Date().toISOString();
updated = true;
}
if (updated) {
try {
fs.writeFileSync(configPath, JSON.stringify(ids, null, 2), 'utf8');
log('已补全并更新 ID 配置:', configPath);
} catch (e) {
log('补全配置文件失败:', e.message);
}
}
log('从配置文件加载 ID 配置:', configPath);
return ids;
}
} catch (e) {
log('读取配置文件失败:', e.message);
}
// 生成新的 ID
ids = {
machineId: generateHex64(),
machineGuid: generateUUID(),
macMachineId: generateHex64(),
devDeviceId: generateUUID(),
sqmId: `{${generateUUID().toUpperCase()}}`,
macAddress: generateMacAddress(),
sessionId: generateUUID(),
firstSessionDate: new Date().toISOString(),
createdAt: new Date().toISOString()
};
// 保存到配置文件
try {
fs.writeFileSync(configPath, JSON.stringify(ids, null, 2), 'utf8');
log('已生成并保存新的 ID 配置:', configPath);
} catch (e) {
log('保存配置文件失败:', e.message);
}
return ids;
};
// 加载 ID 配置
const __cursor_ids__ = loadOrGenerateIds();
// 统一获取 MachineGuid缺失时回退到 machineId 的前 36 位
const getMachineGuid = () => __cursor_ids__.machineGuid || __cursor_ids__.machineId.substring(0, 36);
log('当前 ID 配置:', __cursor_ids__);
// ==================== Module Hook ====================
const Module = require('module');
const originalRequire = Module.prototype.require;
// 缓存已 Hook 的模块
const hookedModules = new Map();
Module.prototype.require = function(id) {
// 兼容 node: 前缀
const normalizedId = (typeof id === 'string' && id.startsWith('node:')) ? id.slice(5) : id;
const result = originalRequire.apply(this, arguments);
// 如果已经 Hook 过,直接返回缓存
if (hookedModules.has(normalizedId)) {
return hookedModules.get(normalizedId);
}
let hooked = result;
// Hook child_process 模块
if (normalizedId === 'child_process') {
hooked = hookChildProcess(result);
}
// Hook os 模块
else if (normalizedId === 'os') {
hooked = hookOs(result);
}
// Hook fs 模块 (新增:保护 storage.json)
else if (normalizedId === 'fs') {
hooked = hookFs(result);
}
// Hook crypto 模块
else if (normalizedId === 'crypto') {
hooked = hookCrypto(result);
}
// Hook @vscode/deviceid 模块
else if (normalizedId === '@vscode/deviceid') {
hooked = hookDeviceId(result);
}
// Hook @vscode/windows-registry 模块
else if (normalizedId === '@vscode/windows-registry') {
hooked = hookWindowsRegistry(result);
}
// 缓存 Hook 结果
if (hooked !== result) {
hookedModules.set(normalizedId, hooked);
log(`已 Hook 模块: ${normalizedId}`);
}
return hooked;
};
// ==================== child_process Hook ====================
function hookChildProcess(cp) {
const originalExecSync = cp.execSync;
const originalExecFileSync = cp.execFileSync;
cp.execSync = function(command, options) {
const cmdStr = String(command).toLowerCase();
// 拦截 MachineGuid 查询
if (cmdStr.includes('reg') && cmdStr.includes('machineguid')) {
log('拦截 MachineGuid 查询');
// 返回格式化的注册表输出
return Buffer.from(`\r\n MachineGuid REG_SZ ${getMachineGuid()}\r\n`);
}
// 拦截 ioreg 命令 (macOS)
if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) {
log('拦截 IOPlatformUUID 查询');
return Buffer.from(`"IOPlatformUUID" = "${getMachineGuid().toUpperCase()}"`);
}
// 拦截 machine-id 读取 (Linux)
if (cmdStr.includes('machine-id') || cmdStr.includes('hostname')) {
log('拦截 machine-id 查询');
return Buffer.from(__cursor_ids__.machineId.substring(0, 32));
}
return originalExecSync.apply(this, arguments);
};
// 兼容 execFileSync部分版本会直接调用可执行文件
if (typeof originalExecFileSync === 'function') {
cp.execFileSync = function(file, args, options) {
const cmdStr = [file].concat(args || []).join(' ').toLowerCase();
if (cmdStr.includes('reg') && cmdStr.includes('machineguid')) {
log('拦截 MachineGuid 查询(execFileSync)');
return Buffer.from(`\r\n MachineGuid REG_SZ ${getMachineGuid()}\r\n`);
}
if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) {
log('拦截 IOPlatformUUID 查询(execFileSync)');
return Buffer.from(`"IOPlatformUUID" = "${getMachineGuid().toUpperCase()}"`);
}
if (cmdStr.includes('machine-id') || cmdStr.includes('hostname')) {
log('拦截 machine-id 查询(execFileSync)');
return Buffer.from(__cursor_ids__.machineId.substring(0, 32));
}
return originalExecFileSync.apply(this, arguments);
};
}
return cp;
}
// ==================== os Hook ====================
function hookOs(os) {
const originalNetworkInterfaces = os.networkInterfaces;
os.networkInterfaces = function() {
log('拦截 networkInterfaces 调用');
// 返回虚拟的网络接口,使用固定的 MAC 地址
return {
'Ethernet': [{
address: '192.168.1.100',
netmask: '255.255.255.0',
family: 'IPv4',
mac: __cursor_ids__.macAddress || '00:00:00:00:00:00',
internal: false
}]
};
};
return os;
}
// ==================== fs Hook (新增) ====================
// 🔧 拦截 storage.json 写入操作,保护 telemetry 字段不被覆盖
// 需要保护的 telemetry 字段列表
const PROTECTED_TELEMETRY_KEYS = [
'telemetry.machineId',
'telemetry.macMachineId',
'telemetry.devDeviceId',
'telemetry.sqmId'
];
// 检查路径是否为 storage.json
function isStorageJsonPath(filePath) {
if (!filePath || typeof filePath !== 'string') return false;
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
return normalized.includes('globalStorage/storage.json') ||
normalized.includes('globalstorage/storage.json');
}
// 保护 storage.json 中的 telemetry 字段
function protectStorageJson(content, filePath) {
if (!isStorageJsonPath(filePath)) return content;
try {
const fs = require('fs');
let newData = typeof content === 'string' ? JSON.parse(content) : content;
// 如果写入的内容不是有效的 JSON 对象,直接返回
if (typeof newData !== 'object' || newData === null) {
return content;
}
// 读取当前文件中的受保护字段
let existingProtected = {};
try {
if (fs.existsSync(filePath)) {
const existing = JSON.parse(fs.readFileSync(filePath, 'utf8'));
for (const key of PROTECTED_TELEMETRY_KEYS) {
if (existing[key] !== undefined) {
existingProtected[key] = existing[key];
}
}
}
} catch (e) {
// 文件不存在或解析失败,使用 Hook 配置的 ID
}
// 强制使用 Hook 配置的 ID优先级最高
const protectedValues = {
'telemetry.machineId': __cursor_ids__.machineId,
'telemetry.macMachineId': __cursor_ids__.macMachineId,
'telemetry.devDeviceId': __cursor_ids__.devDeviceId,
'telemetry.sqmId': __cursor_ids__.sqmId
};
// 检测并修正被覆盖的字段
let modified = false;
for (const key of PROTECTED_TELEMETRY_KEYS) {
const protectedValue = protectedValues[key];
if (newData[key] !== undefined && newData[key] !== protectedValue) {
log(`[fs Hook] 拦截 ${key} 覆盖: ${newData[key].substring(0, 16)}... -> ${protectedValue.substring(0, 16)}...`);
newData[key] = protectedValue;
modified = true;
} else if (newData[key] === undefined && existingProtected[key]) {
// 如果新数据没有该字段,但旧文件有,保留旧值
newData[key] = existingProtected[key];
modified = true;
}
}
if (modified) {
log('[fs Hook] storage.json telemetry 字段已保护');
return typeof content === 'string' ? JSON.stringify(newData, null, '\t') : newData;
}
} catch (e) {
log('[fs Hook] 处理 storage.json 失败:', e.message);
}
return content;
}
function hookFs(fsModule) {
const originalWriteFileSync = fsModule.writeFileSync;
const originalWriteFile = fsModule.writeFile;
// Hook writeFileSync
fsModule.writeFileSync = function(filePath, data, options) {
const protectedData = protectStorageJson(data, filePath);
return originalWriteFileSync.call(this, filePath, protectedData, options);
};
// Hook writeFile (异步版本)
fsModule.writeFile = function(filePath, data, options, callback) {
// 处理参数重载: writeFile(path, data, callback) 或 writeFile(path, data, options, callback)
if (typeof options === 'function') {
callback = options;
options = undefined;
}
const protectedData = protectStorageJson(data, filePath);
return originalWriteFile.call(this, filePath, protectedData, options, callback);
};
// Hook promises API (fs.promises.writeFile)
if (fsModule.promises) {
const originalPromisesWriteFile = fsModule.promises.writeFile;
fsModule.promises.writeFile = async function(filePath, data, options) {
const protectedData = protectStorageJson(data, filePath);
return originalPromisesWriteFile.call(this, filePath, protectedData, options);
};
}
log('[fs Hook] 已启用 storage.json 写入保护');
return fsModule;
}
// ==================== crypto Hook ====================
function hookCrypto(crypto) {
const originalCreateHash = crypto.createHash;
const originalRandomUUID = crypto.randomUUID;
// Hook createHash - 用于拦截 machineId 的 SHA256 计算
crypto.createHash = function(algorithm) {
const hash = originalCreateHash.apply(this, arguments);
if (algorithm.toLowerCase() === 'sha256') {
const originalUpdate = hash.update.bind(hash);
const originalDigest = hash.digest.bind(hash);
let inputData = '';
hash.update = function(data, encoding) {
inputData += String(data);
return originalUpdate(data, encoding);
};
hash.digest = function(encoding) {
// 检查是否是 machineId 相关的哈希计算
if (inputData.includes('MachineGuid') ||
inputData.includes('IOPlatformUUID') ||
inputData.length === 32 ||
inputData.length === 36) {
log('拦截 SHA256 哈希计算,返回固定 machineId');
if (encoding === 'hex') {
return __cursor_ids__.machineId;
}
return Buffer.from(__cursor_ids__.machineId, 'hex');
}
return originalDigest(encoding);
};
}
return hash;
};
// Hook randomUUID - 用于拦截 devDeviceId 生成
if (originalRandomUUID) {
let uuidCallCount = 0;
crypto.randomUUID = function() {
uuidCallCount++;
// 第一次调用返回固定的 devDeviceId
if (uuidCallCount <= 2) {
log('拦截 randomUUID 调用,返回固定 devDeviceId');
return __cursor_ids__.devDeviceId;
}
return originalRandomUUID.apply(this, arguments);
};
}
return crypto;
}
// ==================== @vscode/deviceid Hook ====================
function hookDeviceId(deviceIdModule) {
log('Hook @vscode/deviceid 模块');
return {
...deviceIdModule,
getDeviceId: async function() {
log('拦截 getDeviceId 调用');
return __cursor_ids__.devDeviceId;
}
};
}
// ==================== @vscode/windows-registry Hook ====================
function hookWindowsRegistry(registryModule) {
log('Hook @vscode/windows-registry 模块');
const originalGetStringRegKey = registryModule.GetStringRegKey;
return {
...registryModule,
GetStringRegKey: function(hive, path, name) {
// 拦截 MachineId 读取
if (name === 'MachineId' || path.includes('SQMClient')) {
log('拦截注册表 MachineId/SQMClient 读取');
return __cursor_ids__.sqmId;
}
// 拦截 MachineGuid 读取
if (name === 'MachineGuid' || path.includes('Cryptography')) {
log('拦截注册表 MachineGuid 读取');
return getMachineGuid();
}
return originalGetStringRegKey?.apply(this, arguments) || '';
}
};
}
// ==================== 动态 import Hook ====================
// Cursor 使用动态 import() 加载模块,我们需要 Hook 这些模块
// 由于 ES Module 的限制,我们通过 Hook 全局对象来实现
// 存储已 Hook 的动态导入模块
const hookedDynamicModules = new Map();
// Hook crypto 模块的动态导入
const hookDynamicCrypto = (cryptoModule) => {
if (hookedDynamicModules.has('crypto')) {
return hookedDynamicModules.get('crypto');
}
const hooked = { ...cryptoModule };
// Hook createHash
if (cryptoModule.createHash) {
const originalCreateHash = cryptoModule.createHash;
hooked.createHash = function(algorithm) {
const hash = originalCreateHash.apply(this, arguments);
if (algorithm.toLowerCase() === 'sha256') {
const originalDigest = hash.digest.bind(hash);
let inputData = '';
const originalUpdate = hash.update.bind(hash);
hash.update = function(data, encoding) {
inputData += String(data);
return originalUpdate(data, encoding);
};
hash.digest = function(encoding) {
// 检测 machineId 相关的哈希
if (inputData.includes('MachineGuid') ||
inputData.includes('IOPlatformUUID') ||
(inputData.length >= 32 && inputData.length <= 40)) {
log('动态导入: 拦截 SHA256 哈希');
return encoding === 'hex' ? __cursor_ids__.machineId : Buffer.from(__cursor_ids__.machineId, 'hex');
}
return originalDigest(encoding);
};
}
return hash;
};
}
hookedDynamicModules.set('crypto', hooked);
return hooked;
};
// Hook @vscode/deviceid 模块的动态导入
const hookDynamicDeviceId = (deviceIdModule) => {
if (hookedDynamicModules.has('@vscode/deviceid')) {
return hookedDynamicModules.get('@vscode/deviceid');
}
const hooked = {
...deviceIdModule,
getDeviceId: async () => {
log('动态导入: 拦截 getDeviceId');
return __cursor_ids__.devDeviceId;
}
};
hookedDynamicModules.set('@vscode/deviceid', hooked);
return hooked;
};
// Hook @vscode/windows-registry 模块的动态导入
const hookDynamicWindowsRegistry = (registryModule) => {
if (hookedDynamicModules.has('@vscode/windows-registry')) {
return hookedDynamicModules.get('@vscode/windows-registry');
}
const originalGetStringRegKey = registryModule.GetStringRegKey;
const hooked = {
...registryModule,
GetStringRegKey: function(hive, path, name) {
if (name === 'MachineId' || path?.includes('SQMClient')) {
log('动态导入: 拦截 SQMClient');
return __cursor_ids__.sqmId;
}
if (name === 'MachineGuid' || path?.includes('Cryptography')) {
log('动态导入: 拦截 MachineGuid');
return getMachineGuid();
}
return originalGetStringRegKey?.apply(this, arguments) || '';
}
};
hookedDynamicModules.set('@vscode/windows-registry', hooked);
return hooked;
};
// Hook fs 模块的动态导入 (新增:保护 storage.json)
const hookDynamicFs = (fsModule) => {
if (hookedDynamicModules.has('fs')) {
return hookedDynamicModules.get('fs');
}
const hooked = { ...fsModule };
const originalWriteFileSync = fsModule.writeFileSync;
const originalWriteFile = fsModule.writeFile;
hooked.writeFileSync = function(filePath, data, options) {
const protectedData = protectStorageJson(data, filePath);
return originalWriteFileSync.call(this, filePath, protectedData, options);
};
hooked.writeFile = function(filePath, data, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
const protectedData = protectStorageJson(data, filePath);
return originalWriteFile.call(this, filePath, protectedData, options, callback);
};
// Hook promises API
if (fsModule.promises) {
const originalPromisesWriteFile = fsModule.promises.writeFile;
hooked.promises = {
...fsModule.promises,
writeFile: async function(filePath, data, options) {
const protectedData = protectStorageJson(data, filePath);
return originalPromisesWriteFile.call(this, filePath, protectedData, options);
}
};
}
log('动态导入: 已 Hook fs 模块');
hookedDynamicModules.set('fs', hooked);
return hooked;
};
// 将 Hook 函数暴露到全局,供后续使用
globalThis.__cursor_hook_dynamic__ = {
crypto: hookDynamicCrypto,
deviceId: hookDynamicDeviceId,
windowsRegistry: hookDynamicWindowsRegistry,
fs: hookDynamicFs, // 新增 fs Hook
ids: __cursor_ids__
};
log('Cursor Hook 初始化完成');
log('machineId:', __cursor_ids__.machineId.substring(0, 16) + '...');
log('machineGuid:', getMachineGuid().substring(0, 16) + '...');
log('devDeviceId:', __cursor_ids__.devDeviceId);
log('sqmId:', __cursor_ids__.sqmId);
})();
// ==================== 导出配置(供外部使用) ====================
if (typeof module !== 'undefined' && module.exports) {
module.exports = { __cursor_hook_config__ };
}
// ==================== ES Module 兼容性 ====================
// 如果在 ES Module 环境中,也暴露配置
if (typeof globalThis !== 'undefined') {
globalThis.__cursor_hook_config__ = __cursor_hook_config__;
}