feat(transform): confirm agent profiles locally
CI / Test and build (push) Canceled after 0s

This commit is contained in:
go0p
2026-09-07 15:36:59 +08:00
parent dd61541c3e
commit e3b1775f8d
13 changed files with 152 additions and 34 deletions
-2
View File
@@ -4,7 +4,6 @@ import type { BridgePublicKey } from '@/types/models';
import {
browserTransformExecuteSchema,
browserTransformPacketSchema,
browserTransformProfileInputSchema,
} from './transform';
export const BRIDGE_PROTOCOL_VERSION = 3;
@@ -237,7 +236,6 @@ export const capabilityParams = {
'browser.deep_capture.resume': v.optional(v.strictObject(targetFields)),
'browser.deep_capture.detach': v.optional(v.strictObject(targetFields)),
'browser.transform.profile.list': v.optional(v.strictObject(targetFields)),
'browser.transform.profile.save': browserTransformProfileInputSchema,
'browser.transform.profile.delete': v.strictObject({ id }),
'browser.transform.recovery.get': v.strictObject({ id }),
'browser.transform.recovery.start': v.strictObject({ id }),
+5
View File
@@ -52,6 +52,11 @@ describe('versioned Bridge capability catalog', () => {
expect(capabilityVisibleToAgent('browser.handoff.resolve')).toBe(false);
expect(capabilityVisibleToAgent('browser.thumbnail')).toBe(false);
expect(capabilityVisibleToAgent('browser.context')).toBe(true);
expect(catalog.capabilities.find((capability) => capability.method === 'browser.transform.profile.save')).toBeUndefined();
expect(catalog.capabilities.find((capability) => capability.method === 'proxy.switch')?.summary)
.toContain('不会生成、启用或执行 Transform Profile');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.profile.validate')?.summary)
.toContain('用户在插件本地确认保存');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.transform.recovery.capture')).toMatchObject({
access: 'dangerous',
scopes: ['browser.transform.manage', 'browser.debugger.control', 'browser.callable.execute'],
+11 -15
View File
@@ -128,7 +128,7 @@ const CAPABILITY_METADATA = {
scopes: ['browser.human.takeover'], targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.network.start': {
domain: 'network', access: 'control', summary: '启动有界网络捕获,可选采集请求头和 Body',
domain: 'network', access: 'control', summary: '启动 DevTools 网络观察,只采集页面请求;不会生成或执行明文网关',
scopes: ['browser.network.capture'],
conditionalScopes: [{ scope: 'browser.network.sensitive.read', when: 'captureHeaders=true or captureBody=true' }],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
@@ -138,7 +138,7 @@ const CAPABILITY_METADATA = {
scopes: ['browser.network.read'], targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.network.list': {
domain: 'network', access: 'read', summary: '列出已捕获请求,敏感字段仍由 Agent 操作审核策略保护',
domain: 'network', access: 'read', summary: '列出 DevTools 已捕获请求,适合观察流量;需要转换加密报文时改用 transform 域',
scopes: ['browser.network.read'], targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.network.clear': {
@@ -162,7 +162,7 @@ const CAPABILITY_METADATA = {
scopes: ['browser.network.sensitive.read'], targetMode: 'document', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.recording.start': {
domain: 'recording', access: 'control', summary: '开始业务 Trace 录制,可选采集有界值预览',
domain: 'recording', access: 'control', summary: '开始业务 Trace 录制;生成新明文网关时先录制一次真实业务操作,再检查候选证据',
scopes: ['browser.recording.control'],
conditionalScopes: [{ scope: 'browser.recording.sensitive.read', when: 'captureValues=true' }],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
@@ -194,7 +194,7 @@ const CAPABILITY_METADATA = {
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.callable.create': {
domain: 'callable', access: 'execute', summary: '从录制句柄或深度捕获 Frame 创建页面函数',
domain: 'callable', access: 'execute', summary: '从录制句柄或深度捕获 Frame 创建页面函数;生成明文网关 Profile 前需要得到可回放函数',
scopes: ['browser.callable.execute'],
conditionalScopes: [{ scope: 'browser.debugger.control', when: 'source=deep-capture' }],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
@@ -240,13 +240,9 @@ const CAPABILITY_METADATA = {
scopes: ['browser.debugger.control'], targetMode: 'document', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.transform.profile.list': {
domain: 'transform', access: 'read', summary: '列出目标页面可见的明文网关 Profile',
domain: 'transform', access: 'read', summary: '明文网关入口:先列出目标页面已有 Profile;已有配置可直接用 transform.execute,无配置再走录制、提案和验证',
scopes: ['browser.transform.read'], targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.transform.profile.save': {
domain: 'transform', access: 'dangerous', summary: '保存或更新完整 Transform Profile',
scopes: ['browser.transform.manage'], targetMode: 'profile', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.transform.profile.delete': {
domain: 'transform', access: 'write', summary: '删除 Transform Profile',
scopes: ['browser.transform.manage'], targetMode: 'profile', defaultTimeoutMs: READ_TIMEOUT_MS,
@@ -278,7 +274,7 @@ const CAPABILITY_METADATA = {
scopes: ['browser.transform.manage'], targetMode: 'profile', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.transform.execute': {
domain: 'transform', access: 'execute', summary: '对 HTTP 报文应用已保存的请求或响应转换',
domain: 'transform', access: 'execute', summary: '使用已保存的 Profile 对 HTTP 报文执行请求加密或响应解密;它不是网络代理切换',
scopes: ['browser.transform.execute'], targetMode: 'profile', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.packet.compare': {
@@ -286,16 +282,16 @@ const CAPABILITY_METADATA = {
scopes: ['browser.transform.read'], targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.profile.propose': {
domain: 'transform', access: 'read', summary: '从候选证据和页面函数确定性编译 Profile 提案',
domain: 'transform', access: 'read', summary: '从录制候选和页面函数编译未保存的 Profile 提案;下一步必须调用 profile.validate',
scopes: ['browser.transform.read', 'browser.recording.read'],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.profile.validation.latest': {
domain: 'transform', access: 'read', summary: '读取当前文档最近的短时验证草稿',
domain: 'transform', access: 'read', summary: '读取当前文档最近的短时验证草稿及本地确认状态;草稿过期后需重新验证',
scopes: ['browser.transform.read'], targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.profile.validate': {
domain: 'transform', access: 'execute', summary: '重新编译并执行 Profile,再与候选或报文证据比较',
domain: 'transform', access: 'execute', summary: '确定性执行 Profile 提案并与证据比较;成功后只生成短时草稿,必须由用户在插件本地确认保存',
scopes: ['browser.transform.execute', 'browser.recording.read'],
targetMode: 'document', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
@@ -310,11 +306,11 @@ const CAPABILITY_METADATA = {
targetMode: 'document', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'proxy.list': {
domain: 'proxy', access: 'read', summary: '列出扩展代理 Profile',
domain: 'proxy', access: 'read', summary: '列出 Chrome 网络代理 Profile,仅用于流量路由;不是页面加解密或明文网关',
scopes: ['browser.proxy.read'], targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'proxy.switch': {
domain: 'proxy', access: 'write', summary: '切换当前代理 Profile',
domain: 'proxy', access: 'write', summary: '切换 Chrome 流量代理,仅改变网络路由;不会生成、启用或执行 Transform Profile',
scopes: ['browser.proxy.write'], targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
} satisfies Record<BridgeCapabilityMethod, CapabilityMetadata>;
+8
View File
@@ -316,6 +316,14 @@ describe('extension request schemas', () => {
action: 'analysis.profile.validation.latest',
payload: { tabId: 12, frameId: 0 },
}).action).toBe('analysis.profile.validation.latest');
expect(parseExtensionRequest({
action: 'analysis.profile.validation.resolve',
payload: { tabId: 12, frameId: 0, validationId: 'validation-1', outcome: 'save' },
}).action).toBe('analysis.profile.validation.resolve');
expect(() => parseExtensionRequest({
action: 'analysis.profile.validation.resolve',
payload: { tabId: 12, frameId: 0, validationId: 'validation-1', outcome: 'approve' },
})).toThrow();
expect(parseExtensionRequest({
action: 'transform.execute',
payload: {
+5
View File
@@ -440,6 +440,11 @@ const payloadSchemas = {
'analysis.profile.propose': capabilityParams['browser.profile.propose'],
'analysis.profile.validate': capabilityParams['browser.profile.validate'],
'analysis.profile.validation.latest': capabilityParams['browser.profile.validation.latest'],
'analysis.profile.validation.resolve': v.strictObject({
...targetFields,
validationId: id,
outcome: v.picklist(['save', 'discard']),
}),
'transform.profile.list': v.strictObject(targetFields),
'transform.profile.save': browserTransformProfileInputSchema,
'transform.profile.delete': v.strictObject({ id }),