From 41addaf49a14cd7c1cdd6f6eca6ed9c921c08514 Mon Sep 17 00:00:00 2001 From: Clarence <1297529513@qq.com> Date: Tue, 23 Jun 2026 20:56:20 +0800 Subject: [PATCH 1/2] Fix Fast mode for API key auth --- scripts/patch-fast-mode.js | 302 ++++++++++++++++++++++++++----------- 1 file changed, 216 insertions(+), 86 deletions(-) diff --git a/scripts/patch-fast-mode.js b/scripts/patch-fast-mode.js index 9229139e..b88fca8d 100644 --- a/scripts/patch-fast-mode.js +++ b/scripts/patch-fast-mode.js @@ -5,87 +5,175 @@ * The speed selector is gated by authMethod === "chatgpt" checks. * API-key users never see it because their authMethod differs. * - * This patch locates BinaryExpression nodes matching: - * X.authMethod !== "chatgpt" - * inside functions that also reference "fast_mode", and replaces - * the comparison with !1 (always false), removing the auth gate. + * This patch locates auth gates inside functions that also reference + * "fast_mode", and either removes legacy negative gates or extends + * positive ChatGPT-only gates to include API-key auth. * - * Target: permissions-mode-helpers-*.js (or any chunk with the pattern) + * Target: service-tier / permissions chunks that contain the pattern. */ const fs = require("fs"); const path = require("path"); const { parse } = require("acorn"); -const { locateBundles, relPath, SRC_DIR } = require("./patch-util"); +const { relPath, SRC_DIR } = require("./patch-util"); -function walk(node, visitor) { +const CHATGPT_AUTH = "chatgpt"; +const APIKEY_AUTH = "apikey"; +const ALWAYS_FALSE = "!1"; + +function walk(node, visitor, parent = null) { if (!node || typeof node !== "object") return; - if (node.type) visitor(node); + if (node.type) visitor(node, parent); for (const key of Object.keys(node)) { if (key === "type" || key === "start" || key === "end") continue; const child = node[key]; if (Array.isArray(child)) { for (const item of child) { - if (item && typeof item === "object" && item.type) walk(item, visitor); + if (item && typeof item === "object" && item.type) { + walk(item, visitor, node); + } } } else if (child && typeof child === "object" && child.type) { - walk(child, visitor); + walk(child, visitor, node); } } } +function sourceFor(source, node) { + return source.slice(node.start, node.end); +} + +function isFunctionNode(node) { + return ( + node.type === "FunctionDeclaration" || + node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression" + ); +} + +function isStringLiteral(node, value) { + if (node.type === "Literal") return node.value === value; + if (node.type !== "TemplateLiteral") return false; + return ( + node.expressions.length === 0 && + node.quasis.length === 1 && + node.quasis[0].value.cooked === value + ); +} + +function isFalseLiteral(node, source) { + if (node.type === "Literal") return node.value === false; + return sourceFor(source, node) === ALWAYS_FALSE; +} + +function getChatGptEqualityOperand(node, source) { + if (node.type !== "BinaryExpression" || node.operator !== "===") return null; + if (isStringLiteral(node.right, CHATGPT_AUTH)) return sourceFor(source, node.left); + if (isStringLiteral(node.left, CHATGPT_AUTH)) return sourceFor(source, node.right); + return null; +} + +function buildApiKeyAuthExpression(operand) { + return `${operand}===\`${CHATGPT_AUTH}\`||${operand}===\`${APIKEY_AUTH}\``; +} + +function hasApiKeyEquality(node, source, operand) { + let found = false; + walk(node, (child) => { + if (found || child.type !== "BinaryExpression" || child.operator !== "===") { + return; + } + const left = sourceFor(source, child.left); + const right = sourceFor(source, child.right); + found = + (left === operand && isStringLiteral(child.right, APIKEY_AUTH)) || + (right === operand && isStringLiteral(child.left, APIKEY_AUTH)); + }); + return found; +} + +function isAlreadyApiKeyAuthGate(node, parent, source, operand) { + if (parent?.type !== "LogicalExpression" || parent.operator !== "||") { + return false; + } + return hasApiKeyEquality(parent, source, operand); +} + +function addPatch(patches, patch) { + if (patches.some((p) => p.start === patch.start)) return; + patches.push(patch); +} + +function collectLegacyNegativeGatePatches(node, source, patches) { + if (node.type !== "BinaryExpression" || node.operator !== "!==") return; + + const original = sourceFor(source, node); + if (!original.includes("authMethod") || !original.includes(CHATGPT_AUTH)) return; + if (original === ALWAYS_FALSE) return; + + addPatch(patches, { + id: "fast_mode_legacy_auth_gate", + start: node.start, + end: node.end, + replacement: ALWAYS_FALSE, + original, + }); +} + +function collectPositiveAuthGatePatches(node, parent, source, patches) { + const original = sourceFor(source, node); + const operand = getChatGptEqualityOperand(node, source); + if (operand == null || !original.includes("authMethod")) return; + if (isAlreadyApiKeyAuthGate(node, parent, source, operand)) return; + + addPatch(patches, { + id: "fast_mode_positive_auth_gate", + start: node.start, + end: node.end, + replacement: buildApiKeyAuthExpression(operand), + original, + }); +} + +function collectConditionalAuthGatePatches(node, source, patches) { + if (node.type !== "ConditionalExpression") return; + if (!sourceFor(source, node.consequent).includes("fast_mode")) return; + if (!sourceFor(source, node).includes("authMethod")) return; + if (!isFalseLiteral(node.alternate, source)) return; + + const operand = getChatGptEqualityOperand(node.test, source); + if (operand == null) return; + + addPatch(patches, { + id: "fast_mode_conditional_auth_gate", + start: node.test.start, + end: node.test.end, + replacement: buildApiKeyAuthExpression(operand), + original: sourceFor(source, node.test), + }); +} + function collectPatches(ast, source) { const patches = []; walk(ast, (node) => { // Match function bodies containing both authMethod and fast_mode - const isFn = - node.type === "FunctionDeclaration" || - node.type === "FunctionExpression" || - node.type === "ArrowFunctionExpression"; - if (!isFn) return; + if (!isFunctionNode(node)) return; - const fnSrc = source.slice(node.start, node.end); + const fnSrc = sourceFor(source, node); if (!fnSrc.includes("authMethod") || !fnSrc.includes("fast_mode")) return; - // Inside this function, find: X.authMethod !== `chatgpt` - walk(node, (child) => { - if (child.type !== "BinaryExpression" || child.operator !== "!==") return; - - const childSrc = source.slice(child.start, child.end); - if (!childSrc.includes("authMethod") || !childSrc.includes("chatgpt")) - return; - - if (childSrc === "!1") return; - - // Avoid duplicate patches at same offset - if (patches.some((p) => p.start === child.start)) return; - - patches.push({ - id: "fast_mode_auth_gate", - start: child.start, - end: child.end, - replacement: "!1", - original: childSrc, - }); + // Inside this function, find auth gates tied to Fast mode. + walk(node, (child, parent) => { + collectLegacyNegativeGatePatches(child, source, patches); + collectPositiveAuthGatePatches(child, parent, source, patches); + collectConditionalAuthGatePatches(child, source, patches); }); }); return patches; } -function main() { - const args = process.argv.slice(2); - const isCheck = args.includes("--check"); - const platform = args.find((a) => - ["mac-arm64", "mac-x64", "win"].includes(a), - ); - - const platforms = platform - ? [platform] - : ["mac-arm64", "mac-x64", "win"].filter((p) => - fs.existsSync(path.join(SRC_DIR, p, "_asar", "webview", "assets")), - ); - +function findTargets(platforms) { const targets = []; for (const plat of platforms) { const assetsDir = path.join(SRC_DIR, plat, "_asar", "webview", "assets"); @@ -99,57 +187,99 @@ function main() { } } } + return targets; +} - if (targets.length === 0) { - console.log(" [skip] No chunk contains fast_mode gate logic"); - return; +function parseBundle(bundle, source) { + try { + return parse(source, { ecmaVersion: "latest", sourceType: "module" }); + } catch (error) { + throw new Error(`${relPath(bundle.path)} parse failed: ${error.message}`); } +} - let totalPatched = 0; - - for (const bundle of targets) { - const source = fs.readFileSync(bundle.path, "utf-8"); - - const t0 = Date.now(); - let ast; - try { - ast = parse(source, { ecmaVersion: "latest", sourceType: "module" }); - } catch { - continue; - } - - const patches = collectPatches(ast, source); +function applyPatches(source, patches) { + let code = source; + for (const patch of patches.sort((a, b) => b.start - a.start)) { + console.log(` * ${patch.original} -> ${patch.replacement}`); + code = + code.slice(0, patch.start) + + patch.replacement + + code.slice(patch.end); + } + return code; +} - if (patches.length === 0) continue; +function getPlatforms(platform) { + if (platform) return [platform]; + return ["mac-arm64", "mac-x64", "win"].filter((p) => + fs.existsSync(path.join(SRC_DIR, p, "_asar", "webview", "assets")), + ); +} - console.log( - ` [${bundle.platform}] ${relPath(bundle.path)} (parse ${Date.now() - t0}ms)`, - ); +function processBundle(bundle, isCheck) { + const source = fs.readFileSync(bundle.path, "utf-8"); + const t0 = Date.now(); + const ast = parseBundle(bundle, source); + const patches = collectPatches(ast, source); - if (isCheck) { - for (const p of patches) { - console.log(` [?] offset ${p.start}: ${p.original} -> ${p.replacement}`); - } - continue; - } + if (patches.length === 0) return { candidates: 0, patched: 0 }; - patches.sort((a, b) => b.start - a.start); + console.log( + ` [${bundle.platform}] ${relPath(bundle.path)} (parse ${Date.now() - t0}ms)`, + ); - let code = source; - for (const p of patches) { - console.log(` * ${p.original} -> ${p.replacement}`); - code = code.slice(0, p.start) + p.replacement + code.slice(p.end); + if (isCheck) { + for (const patch of patches) { + console.log(` [?] offset ${patch.start}: ${patch.original} -> ${patch.replacement}`); } - - fs.writeFileSync(bundle.path, code, "utf-8"); - totalPatched += patches.length; + return { candidates: patches.length, patched: 0 }; } - if (totalPatched > 0) { + fs.writeFileSync(bundle.path, applyPatches(source, patches), "utf-8"); + return { candidates: patches.length, patched: patches.length }; +} + +function printSummary({ isCheck, totalCandidates, totalPatched }) { + if (isCheck && totalCandidates > 0) { + console.log(` [ok] ${totalCandidates} auth gate(s) would be patched`); + } else if (isCheck) { + console.log(" [ok] no fast_mode auth gates need patching"); + } else if (totalPatched > 0) { console.log(` [ok] ${totalPatched} auth gate(s) removed`); } else { console.log(" [ok] fast_mode auth gates already patched or absent"); } } -main(); +function main() { + const args = process.argv.slice(2); + const isCheck = args.includes("--check"); + const platform = args.find((a) => + ["mac-arm64", "mac-x64", "win"].includes(a), + ); + + const targets = findTargets(getPlatforms(platform)); + + if (targets.length === 0) { + console.log(" [skip] No chunk contains fast_mode gate logic"); + return; + } + + let totalPatched = 0; + let totalCandidates = 0; + + for (const bundle of targets) { + const result = processBundle(bundle, isCheck); + totalCandidates += result.candidates; + totalPatched += result.patched; + } + + printSummary({ isCheck, totalCandidates, totalPatched }); +} + +if (require.main === module) { + main(); +} + +module.exports = { collectPatches }; From f0ff261fd4ec02f096d1ca726c0d03c0f17d9dcd Mon Sep 17 00:00:00 2001 From: Clarence <1297529513@qq.com> Date: Tue, 23 Jun 2026 21:06:06 +0800 Subject: [PATCH 2/2] Clarify Fast mode patch summary output --- scripts/patch-fast-mode.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/patch-fast-mode.js b/scripts/patch-fast-mode.js index b88fca8d..90431255 100644 --- a/scripts/patch-fast-mode.js +++ b/scripts/patch-fast-mode.js @@ -246,7 +246,11 @@ function printSummary({ isCheck, totalCandidates, totalPatched }) { } else if (isCheck) { console.log(" [ok] no fast_mode auth gates need patching"); } else if (totalPatched > 0) { - console.log(` [ok] ${totalPatched} auth gate(s) removed`); + const candidateSuffix = + totalCandidates && totalCandidates !== totalPatched + ? ` (out of ${totalCandidates} candidate auth gate(s))` + : ""; + console.log(` [ok] ${totalPatched} auth gate(s) patched${candidateSuffix}`); } else { console.log(" [ok] fast_mode auth gates already patched or absent"); }