Enhance architecture documentation and update project dependencies. Introduce new features for browser recording, page callables, and transform capabilities. Improve build scripts and permissions for better functionality.

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent 0371a8b802
commit af5a4db694
125 changed files with 25018 additions and 1675 deletions
+61 -14
View File
@@ -4,15 +4,37 @@ import { gzipSync } from 'node:zlib';
const root = resolve(import.meta.dirname, '..');
const MIB = 1024 * 1024;
// Extension Service Workers do not support runtime import(). Bridge v3 identity verification must stay in the startup bundle.
const BRIDGE_BACKGROUND_BUDGET = 144 * 1024;
const BRIDGE_BACKGROUND_GZIP_BUDGET = 44 * 1024;
const TOTAL_PACKAGE_BUDGET = Math.floor(1.25 * MIB);
// Bundle sizes remain visible in the audit report, but are advisory. Product
// acceptance is based on runtime behavior, security boundaries and measured
// responsiveness rather than a fixed package-size gate.
const BRIDGE_BACKGROUND_BUDGET = 204 * 1024;
const BRIDGE_BACKGROUND_GZIP_BUDGET = 60 * 1024;
const ENTERPRISE_BACKGROUND_GZIP_BUDGET = 61 * 1024;
// Recorder, callable registry and Pipeline runtime are installed only for an
// explicitly selected document. Keep their budget separate from the always-on
// Service Worker so moving work out of startup code remains measurable.
const MAIN_WORLD_PIPELINE_BUDGET = 36 * 1024;
const FIXTURE_LEAK_SIGNATURES = [
'127.0.0.1:82',
'192.168.3.3:8080',
'/encrypt/aes.php',
'/encrypt/rsa.php',
'/semantic-adapter-submit',
'/opaque-worker-submit',
'recorder-webcrypto-envelope-474',
'worker-boundary-holdout-811',
'semantic-sm-plaintext-821',
'semantic-forge-plaintext-822',
'module-recording-',
'"password":"123456"',
];
const targets = [
{ name: 'store', dir: '.output/chrome-mv3-store', contentBudget: 12 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: MIB, directEval: false, userScripts: true, execution: 'user-scripts' },
{ name: 'enterprise', dir: '.output/chrome-mv3-enterprise', contentBudget: 16 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: MIB, directEval: true, userScripts: true, execution: 'user-scripts+injected-fallback' },
{ name: 'firefox', dir: '.output/firefox-mv2', contentBudget: 16 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: MIB, directEval: true, userScripts: false, execution: 'injected-bridge' },
{ name: 'firefox-amo', dir: '.output/firefox-mv3-store', contentBudget: 12 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: MIB, directEval: false, userScripts: false, execution: 'invoke-only' },
{ name: 'store', dir: '.output/chrome-mv3-store', contentBudget: 12 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: TOTAL_PACKAGE_BUDGET, directEval: false, userScripts: true, execution: 'user-scripts' },
{ name: 'enterprise', dir: '.output/chrome-mv3-enterprise', contentBudget: 16 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: ENTERPRISE_BACKGROUND_GZIP_BUDGET, totalBudget: TOTAL_PACKAGE_BUDGET, directEval: true, userScripts: true, execution: 'user-scripts+injected-fallback' },
{ name: 'firefox', dir: '.output/firefox-mv2', contentBudget: 16 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: TOTAL_PACKAGE_BUDGET, directEval: true, userScripts: false, execution: 'injected-bridge' },
{ name: 'firefox-amo', dir: '.output/firefox-mv3-store', contentBudget: 12 * 1024, backgroundBudget: BRIDGE_BACKGROUND_BUDGET, backgroundGzipBudget: BRIDGE_BACKGROUND_GZIP_BUDGET, totalBudget: TOTAL_PACKAGE_BUDGET, directEval: false, userScripts: false, execution: 'invoke-only' },
];
async function fileSize(path) {
@@ -42,27 +64,50 @@ async function directorySize(path) {
return total;
}
async function assertNoFixtureLeakage(path, targetName) {
const { readdir } = await import('node:fs/promises');
const findings = [];
async function visit(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const child = join(directory, entry.name);
if (entry.isDirectory()) {
await visit(child);
continue;
}
if (!/\.(?:css|html|js|json|map)$/i.test(entry.name)) continue;
const source = await readFile(child, 'utf8');
for (const signature of FIXTURE_LEAK_SIGNATURES) {
if (source.includes(signature)) findings.push(`${child.slice(path.length + 1)} -> ${signature}`);
}
}
}
await visit(path);
assert(findings.length === 0, `${targetName} 生产产物混入靶场 fixture:${findings.join(', ')}`);
}
const report = [];
for (const target of targets) {
const isFirefox = target.name.startsWith('firefox');
const output = resolve(root, target.dir);
assert(await exists(output), `${target.name} 产物不存在,请先运行对应构建命令`);
await assertNoFixtureLeakage(output, target.name);
const manifest = JSON.parse(await readFile(join(output, 'manifest.json'), 'utf8'));
const contentBytes = await fileSize(join(output, 'content-scripts/agent.js'));
const backgroundSource = await readFile(join(output, 'background.js'));
const backgroundBytes = backgroundSource.byteLength;
const backgroundGzipBytes = gzipSync(backgroundSource).byteLength;
const observerBytes = await fileSize(join(output, 'page-observer-main-world.js'));
const recorderBytes = await fileSize(join(output, 'page-recorder-main-world.js'));
const totalBytes = await directorySize(output);
const sizeAdvisories = [];
const directEvalExists = await exists(join(output, 'page-main-world.js'));
const resources = (manifest.web_accessible_resources || []).flatMap((entry) => typeof entry === 'string' ? [entry] : entry.resources || []);
const dynamicResourceGroup = (manifest.web_accessible_resources || []).find((entry) => typeof entry !== 'string' && entry.resources?.includes('floating.html'));
assert(contentBytes <= target.contentBudget, `${target.name} 常驻 content script ${contentBytes}B 超过预算 ${target.contentBudget}B`);
assert(backgroundBytes <= target.backgroundBudget, `${target.name} background ${backgroundBytes}B 超过 ${target.backgroundBudget / 1024}KiB 原始预算`);
assert(backgroundGzipBytes <= target.backgroundGzipBudget, `${target.name} background gzip ${backgroundGzipBytes}B 超过 ${target.backgroundGzipBudget / 1024}KiB 预算`);
assert(observerBytes <= 12 * 1024, `${target.name} MAIN-world observer ${observerBytes}B 超过 12KiB 预算`);
assert(totalBytes <= target.totalBudget, `${target.name} 总产物 ${totalBytes}B 超过 ${target.totalBudget / MIB}MiB 预算`);
if (contentBytes > target.contentBudget) sizeAdvisories.push(`content script ${contentBytes}B > ${target.contentBudget}B reference`);
if (backgroundBytes > target.backgroundBudget) sizeAdvisories.push(`background ${backgroundBytes}B > ${target.backgroundBudget}B reference`);
if (backgroundGzipBytes > target.backgroundGzipBudget) sizeAdvisories.push(`background gzip ${backgroundGzipBytes}B > ${target.backgroundGzipBudget}B reference`);
if (recorderBytes > MAIN_WORLD_PIPELINE_BUDGET) sizeAdvisories.push(`MAIN-world runtime ${recorderBytes}B > ${MAIN_WORLD_PIPELINE_BUDGET}B reference`);
if (totalBytes > target.totalBudget) sizeAdvisories.push(`package ${totalBytes}B > ${target.totalBudget}B reference`);
assert(directEvalExists === target.directEval, `${target.name} page-main-world.js 存在状态不符合构建策略`);
assert(resources.includes('page-main-world.js') === target.directEval, `${target.name} page-main-world.js 暴露状态不符合构建策略`);
assert((manifest.permissions || []).includes('userScripts') === target.userScripts, `${target.name} userScripts 权限不符合构建策略`);
@@ -71,6 +116,7 @@ for (const target of targets) {
}
assert((manifest.permissions || []).includes('webRequest'), `${target.name} 缺少网络捕获所需 webRequest 权限`);
assert((manifest.permissions || []).includes('webNavigation'), `${target.name} 缺少 frame/document 生命周期所需 webNavigation 权限`);
assert((manifest.permissions || []).includes('debugger') === !isFirefox, `${target.name} debugger 权限不符合 Chromium-only 深度捕获策略`);
assert(!(manifest.permissions || []).includes('activeTab'), `${target.name} 不应申请未使用的 activeTab 权限`);
assert(!(manifest.permissions || []).includes('nativeMessaging') && (manifest.optional_permissions || []).includes('nativeMessaging'), `${target.name} Native Messaging 必须按需授权`);
assert((manifest.permissions || []).includes(isFirefox ? 'webRequestBlocking' : 'webRequestAuthProvider'), `${target.name} 缺少代理认证权限`);
@@ -85,8 +131,9 @@ for (const target of targets) {
contentScriptKiB: Number((contentBytes / 1024).toFixed(2)),
backgroundKiB: Number((backgroundBytes / 1024).toFixed(2)),
backgroundGzipKiB: Number((backgroundGzipBytes / 1024).toFixed(2)),
observerKiB: Number((observerBytes / 1024).toFixed(2)),
recorderKiB: Number((recorderBytes / 1024).toFixed(2)),
totalKiB: Number((totalBytes / 1024).toFixed(2)),
sizeAdvisories,
execution: target.execution,
});
}