mirror of
https://github.com/yuaotian/go-cursor-help.git
synced 2026-03-07 14:24:37 +00:00
```
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 - 修正字符串转义处理逻辑,提升函数特征匹配准确性 ```
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
* 3. @vscode/deviceid - 拦截 devDeviceId 获取
|
||||
* 4. @vscode/windows-registry - 拦截注册表读取
|
||||
* 5. os.networkInterfaces - 拦截 MAC 地址获取
|
||||
* 6. fs.writeFileSync/writeFile - 拦截 storage.json 写入,保护 telemetry 字段
|
||||
*
|
||||
* 📦 使用方式:
|
||||
* 将此代码注入到 main.js 文件顶部(Sentry 初始化之后)
|
||||
@@ -208,6 +209,10 @@ var __cursor_hook_config__ = {
|
||||
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);
|
||||
@@ -310,6 +315,121 @@ var __cursor_hook_config__ = {
|
||||
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) {
|
||||
@@ -501,11 +621,53 @@ var __cursor_hook_config__ = {
|
||||
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__
|
||||
};
|
||||
|
||||
|
||||
@@ -1505,175 +1505,175 @@ EOF
|
||||
if [ "$(basename "$file")" = "main.js" ]; then
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
local b6_result
|
||||
b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY'
|
||||
if True:
|
||||
import re, sys
|
||||
|
||||
def diag(msg):
|
||||
print(f"[方案B][诊断] {msg}", file=sys.stderr)
|
||||
|
||||
path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
|
||||
# ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界
|
||||
marker = "out-build/vs/base/node/id.js"
|
||||
marker_index = data.find(marker)
|
||||
if marker_index < 0:
|
||||
print("NOT_FOUND")
|
||||
diag(f"未找到模块标记: {marker}")
|
||||
raise SystemExit(0)
|
||||
|
||||
window_end = min(len(data), marker_index + 200000)
|
||||
window = data[marker_index:window_end]
|
||||
|
||||
def find_matching_brace(text, open_index, max_scan=20000):
|
||||
limit = min(len(text), open_index + max_scan)
|
||||
depth = 1
|
||||
in_single = in_double = in_template = False
|
||||
in_line_comment = in_block_comment = False
|
||||
escape = False
|
||||
i = open_index + 1
|
||||
while i < limit:
|
||||
ch = text[i]
|
||||
nxt = text[i + 1] if i + 1 < limit else ""
|
||||
|
||||
if in_line_comment:
|
||||
if ch == "\n":
|
||||
in_line_comment = False
|
||||
i += 1
|
||||
continue
|
||||
if in_block_comment:
|
||||
if ch == "*" and nxt == "/":
|
||||
in_block_comment = False
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if in_single:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\\\":
|
||||
escape = True
|
||||
elif ch == "'":
|
||||
in_single = False
|
||||
i += 1
|
||||
continue
|
||||
if in_double:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\\\":
|
||||
escape = True
|
||||
elif ch == '"':
|
||||
in_double = False
|
||||
i += 1
|
||||
continue
|
||||
if in_template:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\\\":
|
||||
escape = True
|
||||
elif ch == "`":
|
||||
in_template = False
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == "/" and nxt == "/":
|
||||
in_line_comment = True
|
||||
i += 2
|
||||
continue
|
||||
if ch == "/" and nxt == "*":
|
||||
in_block_comment = True
|
||||
i += 2
|
||||
continue
|
||||
|
||||
if ch == "'":
|
||||
in_single = True
|
||||
i += 1
|
||||
continue
|
||||
if ch == '"':
|
||||
in_double = True
|
||||
i += 1
|
||||
continue
|
||||
if ch == "`":
|
||||
in_template = True
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
|
||||
i += 1
|
||||
return None
|
||||
|
||||
# 🔧 修复:避免 raw string + 单引号 + ['"] 字符组导致的语法错误;同时修正正则转义,提升 b6 特征匹配命中率
|
||||
hash_re = re.compile(r"""createHash\(["']sha256["']\)""")
|
||||
sig_re = re.compile(r'^async function (\w+)\((\w+)\)')
|
||||
|
||||
hash_matches = list(hash_re.finditer(window))
|
||||
diag(f"marker_index={marker_index} window_len={len(window)} sha256_createHash={len(hash_matches)}")
|
||||
|
||||
for idx, hm in enumerate(hash_matches, start=1):
|
||||
hash_pos = hm.start()
|
||||
func_start = window.rfind("async function", 0, hash_pos)
|
||||
if func_start < 0:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未找到 async function 起点")
|
||||
continue
|
||||
|
||||
open_brace = window.find("{", func_start)
|
||||
if open_brace < 0:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未找到函数起始花括号")
|
||||
continue
|
||||
|
||||
end_brace = find_matching_brace(window, open_brace, max_scan=20000)
|
||||
if end_brace is None:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 花括号配对失败(扫描上限内未闭合)")
|
||||
continue
|
||||
|
||||
func_text = window[func_start:end_brace + 1]
|
||||
if len(func_text) > 8000:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 函数体过长 len={len(func_text)},已跳过")
|
||||
continue
|
||||
|
||||
sm = sig_re.match(func_text)
|
||||
if not sm:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未解析到函数签名(async function name(param))")
|
||||
continue
|
||||
name, param = sm.group(1), sm.group(2)
|
||||
|
||||
# 特征校验:sha256 + hex digest + return param ? raw : hash
|
||||
has_digest = re.search(r"""\.digest\(["']hex["']\)""", func_text) is not None
|
||||
has_return = re.search(r'return\s+' + re.escape(param) + r'\?\w+:\w+\}', func_text) is not None
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: {name}({param}) len={len(func_text)} digest={has_digest} return={has_return}")
|
||||
if not has_digest:
|
||||
continue
|
||||
if not has_return:
|
||||
continue
|
||||
|
||||
replacement = f'async function {name}({param}){{return {param}?"{machine_guid}":"{machine_id}";}}'
|
||||
abs_start = marker_index + func_start
|
||||
abs_end = marker_index + end_brace
|
||||
new_data = data[:abs_start] + replacement + data[abs_end + 1:]
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(new_data)
|
||||
diag(f"命中并重写: {name}({param}) len={len(func_text)}")
|
||||
print("PATCHED")
|
||||
break
|
||||
else:
|
||||
diag("未找到满足特征的候选函数")
|
||||
print("NOT_FOUND")
|
||||
b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY'
|
||||
# 🔧 修复:使用标准 4 空格缩进,避免 IndentationError
|
||||
import re, sys
|
||||
|
||||
def diag(msg):
|
||||
print(f"[方案B][诊断] {msg}", file=sys.stderr)
|
||||
|
||||
path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
|
||||
# ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界
|
||||
marker = "out-build/vs/base/node/id.js"
|
||||
marker_index = data.find(marker)
|
||||
if marker_index < 0:
|
||||
print("NOT_FOUND")
|
||||
diag(f"未找到模块标记: {marker}")
|
||||
raise SystemExit(0)
|
||||
|
||||
window_end = min(len(data), marker_index + 200000)
|
||||
window = data[marker_index:window_end]
|
||||
|
||||
def find_matching_brace(text, open_index, max_scan=20000):
|
||||
limit = min(len(text), open_index + max_scan)
|
||||
depth = 1
|
||||
in_single = in_double = in_template = False
|
||||
in_line_comment = in_block_comment = False
|
||||
escape = False
|
||||
i = open_index + 1
|
||||
while i < limit:
|
||||
ch = text[i]
|
||||
nxt = text[i + 1] if i + 1 < limit else ""
|
||||
|
||||
if in_line_comment:
|
||||
if ch == "\n":
|
||||
in_line_comment = False
|
||||
i += 1
|
||||
continue
|
||||
if in_block_comment:
|
||||
if ch == "*" and nxt == "/":
|
||||
in_block_comment = False
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if in_single:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\":
|
||||
escape = True
|
||||
elif ch == "'":
|
||||
in_single = False
|
||||
i += 1
|
||||
continue
|
||||
if in_double:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\":
|
||||
escape = True
|
||||
elif ch == '"':
|
||||
in_double = False
|
||||
i += 1
|
||||
continue
|
||||
if in_template:
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\":
|
||||
escape = True
|
||||
elif ch == "`":
|
||||
in_template = False
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == "/" and nxt == "/":
|
||||
in_line_comment = True
|
||||
i += 2
|
||||
continue
|
||||
if ch == "/" and nxt == "*":
|
||||
in_block_comment = True
|
||||
i += 2
|
||||
continue
|
||||
|
||||
if ch == "'":
|
||||
in_single = True
|
||||
i += 1
|
||||
continue
|
||||
if ch == '"':
|
||||
in_double = True
|
||||
i += 1
|
||||
continue
|
||||
if ch == "`":
|
||||
in_template = True
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
|
||||
i += 1
|
||||
return None
|
||||
|
||||
# 🔧 修复:避免 raw string + 单引号 + ['"] 字符组导致的语法错误;同时修正正则转义,提升 b6 特征匹配命中率
|
||||
hash_re = re.compile(r"""createHash\(["']sha256["']\)""")
|
||||
sig_re = re.compile(r'^async function (\w+)\((\w+)\)')
|
||||
|
||||
hash_matches = list(hash_re.finditer(window))
|
||||
diag(f"marker_index={marker_index} window_len={len(window)} sha256_createHash={len(hash_matches)}")
|
||||
|
||||
for idx, hm in enumerate(hash_matches, start=1):
|
||||
hash_pos = hm.start()
|
||||
func_start = window.rfind("async function", 0, hash_pos)
|
||||
if func_start < 0:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未找到 async function 起点")
|
||||
continue
|
||||
|
||||
open_brace = window.find("{", func_start)
|
||||
if open_brace < 0:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未找到函数起始花括号")
|
||||
continue
|
||||
|
||||
end_brace = find_matching_brace(window, open_brace, max_scan=20000)
|
||||
if end_brace is None:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 花括号配对失败(扫描上限内未闭合)")
|
||||
continue
|
||||
|
||||
func_text = window[func_start:end_brace + 1]
|
||||
if len(func_text) > 8000:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 函数体过长 len={len(func_text)},已跳过")
|
||||
continue
|
||||
|
||||
sm = sig_re.match(func_text)
|
||||
if not sm:
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: 未解析到函数签名(async function name(param))")
|
||||
continue
|
||||
name, param = sm.group(1), sm.group(2)
|
||||
|
||||
# 特征校验:sha256 + hex digest + return param ? raw : hash
|
||||
has_digest = re.search(r"""\.digest\(["']hex["']\)""", func_text) is not None
|
||||
has_return = re.search(r'return\s+' + re.escape(param) + r'\?\w+:\w+\}', func_text) is not None
|
||||
if idx <= 3:
|
||||
diag(f"候选#{idx}: {name}({param}) len={len(func_text)} digest={has_digest} return={has_return}")
|
||||
if not has_digest:
|
||||
continue
|
||||
if not has_return:
|
||||
continue
|
||||
|
||||
replacement = f'async function {name}({param}){{return {param}?"{machine_guid}":"{machine_id}";}}'
|
||||
abs_start = marker_index + func_start
|
||||
abs_end = marker_index + end_brace
|
||||
new_data = data[:abs_start] + replacement + data[abs_end + 1:]
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(new_data)
|
||||
diag(f"命中并重写: {name}({param}) len={len(func_text)}")
|
||||
print("PATCHED")
|
||||
break
|
||||
else:
|
||||
diag("未找到满足特征的候选函数")
|
||||
print("NOT_FOUND")
|
||||
PY
|
||||
)
|
||||
if [ "$b6_result" = "PATCHED" ]; then
|
||||
|
||||
Reference in New Issue
Block a user