Skip to content

Commit fd222ac

Browse files
authored
✨ 打包变量屏蔽正式版 agent 入口并移除 debugger 权限(bump 1.4.0) (#1518)
* ✨ 新增打包变量 SC_ENABLE_AGENT:正式版屏蔽 agent 入口并移除 debugger 权限 - scripts/build-config.js:isAgentEnabled(dev/beta 启用) + applyAgentManifest(正式版剥离 debugger) - rspack.config.ts / pack.js:注入 process.env.SC_ENABLE_AGENT,按版本处理 manifest 权限 - const.ts 暴露 EnableAgent,门控侧边栏 agent 菜单与 /agent/* 路由 - MainLayout/install 安装页屏蔽 Skill ZIP、SkillScript、.cat.md 等安装入口 - service_worker/script.ts 屏蔽 .skill.js/.cat.md 的 DNR/webNavigation 拦截与识别 - agent 运行时(CAT_*/AgentService)保持不变;vitest 默认 SC_ENABLE_AGENT=true * chore: bump version to 1.4.0 * ✅ e2e 强制开启 agent:新增 SC_ENABLE_AGENT env 覆盖 - build-config 新增 resolveAgentEnabled:env 覆盖优先,否则按版本派生 - rspack.config.ts / pack.js 改用 resolveAgentEnabled - test.yaml e2e 构建设 SC_ENABLE_AGENT=true,使正式版下 agent 用例仍可运行 - 正式版默认仍屏蔽 agent(无 env 时不变)
1 parent b114a19 commit fd222ac

14 files changed

Lines changed: 226 additions & 79 deletions

File tree

.github/workflows/test.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ jobs:
8383
run: pnpm test:e2e:install
8484

8585
- name: Build extension
86+
# 正式版构建默认屏蔽 agent 入口;e2e 需覆盖 agent 用例,故强制开启
8687
run: pnpm build
88+
env:
89+
SC_ENABLE_AGENT: "true"
8790

8891
- name: Run E2E tests
8992
run: pnpm test:e2e

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptcat",
3-
"version": "1.4.0-beta.4",
3+
"version": "1.4.0",
44
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
55
"author": "CodFrm",
66
"license": "GPLv3",

rspack.config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ZipExecutionPlugin } from "./rspack-plugins/ZipExecutionPlugin";
44
import { readFileSync } from "fs";
55
import { v4 as uuidv4 } from "uuid";
66
import { toChromeVersion } from "./scripts/version.js";
7+
import { resolveAgentEnabled, applyAgentManifest } from "./scripts/build-config.js";
78

89
const pkg = JSON.parse(readFileSync("./package.json", "utf-8"));
910

@@ -12,6 +13,9 @@ const dirname = path.resolve();
1213
const isDev = process.env.NODE_ENV === "development";
1314
const isBeta = version.includes("-");
1415
const isReactTools = process.env.REACT_DEVTOOLS === "true";
16+
// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限。
17+
// 可用环境变量 SC_ENABLE_AGENT 强制覆盖(如 e2e 在正式版上强制开启)。
18+
const enableAgent = resolveAgentEnabled({ isDev, isBeta, envValue: process.env.SC_ENABLE_AGENT });
1519

1620
// Target browsers, see: https://github.com/browserslist/browserslist
1721
// 依照 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#browser_compatibility
@@ -132,6 +136,7 @@ export default {
132136
new rspack.DefinePlugin({
133137
"process.env.VI_TESTING": "'false'",
134138
"process.env.SC_RANDOM_KEY": `'${uuidv4()}'`,
139+
"process.env.SC_ENABLE_AGENT": `'${enableAgent}'`,
135140
}),
136141
new rspack.CopyRspackPlugin({
137142
patterns: [
@@ -140,7 +145,10 @@ export default {
140145
to: `${dist}/ext`,
141146
// 将manifest.json内版本号替换为package.json中版本号
142147
transform(content: Buffer) {
143-
const manifest = JSON.parse(content.toString()) as chrome.runtime.ManifestV3;
148+
const manifest = applyAgentManifest(
149+
JSON.parse(content.toString()) as chrome.runtime.ManifestV3,
150+
enableAgent
151+
);
144152
manifest.version = toChromeVersion(version);
145153
if (isDev || isBeta) {
146154
manifest.name = "__MSG_scriptcat_beta__";

scripts/build-config.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* agent 功能仅在开发版与 beta 预发布版本中提供,正式版本屏蔽。
3+
* @param {{ isDev: boolean, isBeta: boolean }} env
4+
* @returns {boolean}
5+
*/
6+
export function isAgentEnabled({ isDev, isBeta }) {
7+
return Boolean(isDev || isBeta);
8+
}
9+
10+
/**
11+
* 解析 agent 开关:环境变量 SC_ENABLE_AGENT 优先(用于 e2e/CI 或手动强制覆盖),
12+
* 未设置时按版本派生(dev/beta 启用,正式版屏蔽)。
13+
* @param {{ isDev: boolean, isBeta: boolean, envValue: string | undefined }} param
14+
* @returns {boolean}
15+
*/
16+
export function resolveAgentEnabled({ isDev, isBeta, envValue }) {
17+
if (envValue !== undefined) return envValue === "true";
18+
return isAgentEnabled({ isDev, isBeta });
19+
}
20+
21+
/**
22+
* 正式版本不提供 agent,移除仅 agent 使用的 debugger 权限;
23+
* 其余权限保持不变。启用 agent 时原样返回,禁用时返回新对象,不修改入参。
24+
* @template {{ permissions?: string[] }} T
25+
* @param {T} manifest
26+
* @param {boolean} agentEnabled
27+
* @returns {T}
28+
*/
29+
export function applyAgentManifest(manifest, agentEnabled) {
30+
if (agentEnabled) return manifest;
31+
return {
32+
...manifest,
33+
permissions: (manifest.permissions || []).filter((permission) => permission !== "debugger"),
34+
};
35+
}

scripts/build-config.test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, it, expect } from "vitest";
2+
import { isAgentEnabled, resolveAgentEnabled, applyAgentManifest } from "./build-config.js";
3+
4+
describe("构建配置 - agent 开关", () => {
5+
describe("isAgentEnabled", () => {
6+
it("开发版本应启用 agent", () => {
7+
expect(isAgentEnabled({ isDev: true, isBeta: false })).toBe(true);
8+
});
9+
10+
it("beta 版本应启用 agent", () => {
11+
expect(isAgentEnabled({ isDev: false, isBeta: true })).toBe(true);
12+
});
13+
14+
it("正式版本应禁用 agent", () => {
15+
expect(isAgentEnabled({ isDev: false, isBeta: false })).toBe(false);
16+
});
17+
});
18+
19+
describe("resolveAgentEnabled - 环境变量覆盖", () => {
20+
it("未设置环境变量时按版本派生(正式版禁用)", () => {
21+
expect(resolveAgentEnabled({ isDev: false, isBeta: false, envValue: undefined })).toBe(false);
22+
});
23+
24+
it("未设置环境变量时按版本派生(beta 启用)", () => {
25+
expect(resolveAgentEnabled({ isDev: false, isBeta: true, envValue: undefined })).toBe(true);
26+
});
27+
28+
it("环境变量 'true' 可在正式版强制启用 agent(用于 e2e)", () => {
29+
expect(resolveAgentEnabled({ isDev: false, isBeta: false, envValue: "true" })).toBe(true);
30+
});
31+
32+
it("环境变量 'false' 可在 beta 强制禁用 agent", () => {
33+
expect(resolveAgentEnabled({ isDev: false, isBeta: true, envValue: "false" })).toBe(false);
34+
});
35+
});
36+
37+
describe("applyAgentManifest", () => {
38+
const makeManifest = () => ({
39+
permissions: ["tabs", "debugger", "storage"],
40+
optional_permissions: ["background", "userScripts"],
41+
});
42+
43+
it("启用 agent 时原样返回 manifest 且保留 debugger 权限", () => {
44+
const manifest = makeManifest();
45+
const result = applyAgentManifest(manifest, true);
46+
expect(result).toBe(manifest);
47+
expect(result.permissions).toContain("debugger");
48+
});
49+
50+
it("禁用 agent 时移除 debugger 权限", () => {
51+
const result = applyAgentManifest(makeManifest(), false);
52+
expect(result.permissions).not.toContain("debugger");
53+
expect(result.permissions).toEqual(["tabs", "storage"]);
54+
});
55+
56+
it("禁用 agent 时不改动其它权限", () => {
57+
const result = applyAgentManifest(makeManifest(), false);
58+
expect(result.optional_permissions).toEqual(["background", "userScripts"]);
59+
});
60+
61+
it("禁用 agent 时不修改入参 manifest", () => {
62+
const manifest = makeManifest();
63+
applyAgentManifest(manifest, false);
64+
expect(manifest.permissions).toContain("debugger");
65+
});
66+
});
67+
});

scripts/pack.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import manifest from "../src/manifest.json" with { type: "json" };
77
import packageInfo from "../package.json" with { type: "json" };
88
import semver from "semver";
99
import { toChromeVersion } from "./version.js";
10+
import { resolveAgentEnabled, applyAgentManifest } from "./build-config.js";
1011

1112
// ============================================================================
1213

@@ -28,6 +29,13 @@ const addZipFile = async (zip, path, content) => {
2829

2930
// 判断是否为beta版本
3031
const version = semver.parse(packageInfo.version);
32+
// agent 功能仅在 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限
33+
// (与 rspack 构建一致,支持环境变量 SC_ENABLE_AGENT 覆盖)
34+
const agentEnabled = resolveAgentEnabled({
35+
isDev: false,
36+
isBeta: version.prerelease.length > 0,
37+
envValue: process.env.SC_ENABLE_AGENT,
38+
});
3139
manifest.version = toChromeVersion(packageInfo.version);
3240
if (version.prerelease.length) {
3341
manifest.name = `__MSG_scriptcat_beta__`;
@@ -58,8 +66,8 @@ execSync("pnpm run build", { stdio: "inherit" });
5866
// 处理firefox和chrome的zip压缩包
5967

6068
// 浅拷贝防止后续修改
61-
const firefoxManifest = { ...manifest, background: { ...manifest.background } };
62-
const chromeManifest = { ...manifest, background: { ...manifest.background } };
69+
const firefoxManifest = applyAgentManifest({ ...manifest, background: { ...manifest.background } }, agentEnabled);
70+
const chromeManifest = applyAgentManifest({ ...manifest, background: { ...manifest.background } }, agentEnabled);
6371

6472
chromeManifest.optional_permissions = chromeManifest.optional_permissions.filter((val) => val !== "userScripts");
6573
delete chromeManifest.background.scripts;

src/app/const.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { version } from "../../package.json";
22

33
export const ExtVersion = version;
4+
5+
// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽相关入口。
6+
// 由打包变量 process.env.SC_ENABLE_AGENT 注入(见 rspack.config.ts / scripts/build-config.js)。
7+
export const EnableAgent = process.env.SC_ENABLE_AGENT === "true";
48
export const Discord = "https://discord.gg/JF76nHCCM7";
59
export const DocumentationSite = "https://docs.scriptcat.org";
610

src/app/service/service_worker/script.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { CompiledResourceDAO } from "@App/app/repo/resource";
4747
import { initRegularUpdateCheck } from "./regular_updatecheck";
4848
import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script";
4949
import { TempStorageDAO, TempStorageItemType } from "@App/app/repo/tempStorage";
50+
import { EnableAgent } from "@App/app/const";
5051

5152
export type TCheckScriptUpdateOption = Partial<
5253
{ checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record<string, any>)
@@ -98,8 +99,11 @@ export class ScriptService {
9899
}
99100
// 处理url, 实现安装脚本
100101
let targetUrl: string;
101-
// 判断是否为 file:///*/*.user.js 或 file:///*/*.skill.js
102-
if (req.url.startsWith("file://") && (req.url.endsWith(".user.js") || req.url.endsWith(".skill.js"))) {
102+
// 判断是否为 file:///*/*.user.js 或 file:///*/*.skill.js(skill 仅 agent 启用时)
103+
if (
104+
req.url.startsWith("file://") &&
105+
(req.url.endsWith(".user.js") || (EnableAgent && req.url.endsWith(".skill.js")))
106+
) {
103107
targetUrl = req.url;
104108
} else {
105109
const reqUrl = new URL(req.url);
@@ -166,8 +170,13 @@ export class ScriptService {
166170
{ schemes: ["http", "https"], hostEquals: "docs.scriptcat.org", pathPrefix: "/en/docs/script_installation/" },
167171
{ schemes: ["http", "https"], hostEquals: "www.tampermonkey.net", pathPrefix: "/script_installation.php" },
168172
{ schemes: ["file"], pathSuffix: ".user.js" },
169-
{ schemes: ["file"], pathSuffix: ".skill.js" },
170-
{ schemes: ["file"], pathSuffix: ".cat.md" },
173+
// Skill 安装入口仅在 agent 启用时拦截(正式版屏蔽)
174+
...(EnableAgent
175+
? [
176+
{ schemes: ["file"], pathSuffix: ".skill.js" },
177+
{ schemes: ["file"], pathSuffix: ".cat.md" },
178+
]
179+
: []),
171180
],
172181
}
173182
);
@@ -252,21 +261,26 @@ export class ScriptService {
252261
isUrlFilterCaseSensitive: false,
253262
requestDomains: ["bitbucket.org"], // Chrome 101+
254263
},
255-
// SkillScript (.skill.js) 安装检测
256-
{
257-
regexFilter: "^([^?#]+?\\.skill\\.js)",
258-
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
259-
requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod],
260-
isUrlFilterCaseSensitive: false,
261-
excludedRequestDomains: ["github.com", "gitlab.com", "gitea.com", "bitbucket.org"],
262-
},
263-
// Skill 包 (.cat.md) 安装检测
264-
{
265-
regexFilter: "^([^?#]+?\\.cat\\.md)",
266-
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
267-
requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod],
268-
isUrlFilterCaseSensitive: false,
269-
},
264+
// Skill 安装入口仅在 agent 启用时拦截(正式版屏蔽)
265+
...(EnableAgent
266+
? [
267+
// SkillScript (.skill.js) 安装检测
268+
{
269+
regexFilter: "^([^?#]+?\\.skill\\.js)",
270+
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
271+
requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod],
272+
isUrlFilterCaseSensitive: false,
273+
excludedRequestDomains: ["github.com", "gitlab.com", "gitea.com", "bitbucket.org"],
274+
},
275+
// Skill 包 (.cat.md) 安装检测
276+
{
277+
regexFilter: "^([^?#]+?\\.cat\\.md)",
278+
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
279+
requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod],
280+
isUrlFilterCaseSensitive: false,
281+
},
282+
]
283+
: []),
270284
];
271285
const installPageURL = chrome.runtime.getURL("src/install.html");
272286
const rules = conditions.map((condition, idx) => {
@@ -948,8 +962,8 @@ export class ScriptService {
948962
logger?.error("prepare script failed", Logger.E(e));
949963
}
950964
}
951-
// 检测是否为 SkillScript
952-
const skillScriptMeta = parseSkillScriptMetadata(code);
965+
// 检测是否为 SkillScript(仅 agent 启用时,正式版不识别 skill 安装)
966+
const skillScriptMeta = EnableAgent ? parseSkillScriptMetadata(code) : null;
953967
if (skillScriptMeta) {
954968
const si = await createTempCodeEntry(false, uuid, code, url, upsertBy, {} as SCMetadata, options);
955969
si[1].skillScript = true;

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "__MSG_scriptcat__",
4-
"version": "1.4.0.1500",
4+
"version": "1.4.0",
55
"author": "CodFrm",
66
"description": "__MSG_scriptcat_description__",
77
"options_ui": {

src/pages/components/layout/MainLayout.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import "./index.css";
3535
import { arcoLocale } from "@App/locales/arco";
3636
import { prepareScriptByCode, parseMetadata } from "@App/pkg/utils/script";
3737
import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script";
38+
import { EnableAgent } from "@App/app/const";
3839
import { saveHandle } from "@App/pkg/utils/filehandle-db";
3940
import { makeBlobURL, openInCurrentTab } from "@App/pkg/utils/utils";
4041
import ScrollBoundary from "@App/pages/components/layout/ScrollBoundary";
@@ -221,8 +222,8 @@ const MainLayout: React.FC<{
221222
Promise.all(
222223
acceptedFiles.map(async (aFile) => {
223224
try {
224-
// ZIP 文件走 Skill 安装流程
225-
if (aFile.name.endsWith(".zip")) {
225+
// ZIP 文件走 Skill 安装流程(仅 agent 启用时)
226+
if (EnableAgent && aFile.name.endsWith(".zip")) {
226227
await handleZipSkillInstall(aFile);
227228
stat.success++;
228229
return;
@@ -261,9 +262,11 @@ const MainLayout: React.FC<{
261262
// 先尝试 UserScript 解析
262263
const metadata = parseMetadata(code);
263264
if (metadata) return prepareScriptByCode(code, `file:///*resp-check*/${file.name}`);
264-
// 再尝试 SkillScript 解析
265-
const skillScriptMeta = parseSkillScriptMetadata(code);
266-
if (skillScriptMeta) return { script: {} as any };
265+
// 再尝试 SkillScript 解析(仅 agent 启用时)
266+
if (EnableAgent) {
267+
const skillScriptMeta = parseSkillScriptMetadata(code);
268+
if (skillScriptMeta) return { script: {} as any };
269+
}
267270
throw new Error("not a valid UserScript or SkillScript");
268271
}),
269272
simpleDigestMessage(`f=${file.name}\ns=${file.size},m=${file.lastModified}`),

0 commit comments

Comments
 (0)