refactor(authorization): simplify cross-instance testing
CI / Test and build (push) Canceled after 0s

This commit is contained in:
go0p
2026-09-04 16:43:11 +08:00
parent 71a14eb17a
commit dd61541c3e
36 changed files with 475 additions and 9178 deletions
-207
View File
@@ -92,213 +92,6 @@ describe('Bridge v3 protocol', () => {
})).toThrow();
});
it('binds authorization context capture to a proof, slot and exact document target', () => {
expect(parseCapabilityParams('browser.authorization.context.capture', {
tabId: 12,
frameId: 0,
documentId: 'document-1',
isolationProofId: 'proof-1',
slotId: 'left',
accountLabel: '低权限账号',
})).toEqual({
tabId: 12,
frameId: 0,
documentId: 'document-1',
isolationProofId: 'proof-1',
slotId: 'left',
accountLabel: '低权限账号',
});
expect(parseCapabilityParams('browser.authorization.context.get', {
id: 'auth-context-1',
})).toEqual({ id: 'auth-context-1' });
expect(parseCapabilityParams('browser.authorization.context.attest', {
tabId: 12,
frameId: 0,
documentId: 'document-1',
})).toEqual({
tabId: 12,
frameId: 0,
documentId: 'document-1',
});
expect(parseCapabilityParams('browser.authorization.context.attestation.get', {
id: 'attestation-1',
})).toEqual({ id: 'attestation-1' });
expect(parseCapabilityParams('browser.isolation.container.open', {
url: 'https://example.test/login',
name: '身份 B',
})).toEqual({
url: 'https://example.test/login',
name: '身份 B',
});
expect(parseCapabilityParams('browser.isolation.container.list', {})).toEqual({});
expect(parseCapabilityParams('browser.isolation.container.remove', {
cookieStoreId: 'firefox-container-7',
})).toEqual({ cookieStoreId: 'firefox-container-7' });
expect(parseCapabilityParams('browser.authorization.baseline.capture', {
tabId: 12,
frameId: 0,
documentId: 'document-1',
authContextKind: 'handle',
authContextId: 'auth-context-1',
networkRequestId: 'network-request-1',
comparisonKey: 'A'.repeat(43),
})).toMatchObject({
authContextKind: 'handle',
authContextId: 'auth-context-1',
networkRequestId: 'network-request-1',
});
expect(parseCapabilityParams('browser.authorization.baseline.get', {
id: 'baseline-1',
})).toEqual({ id: 'baseline-1' });
expect(parseCapabilityParams('browser.authorization.baseline.logical.bind', {
id: 'baseline-1',
profileId: 'profile-left',
comparisonKey: 'A'.repeat(43),
})).toEqual({
id: 'baseline-1',
profileId: 'profile-left',
comparisonKey: 'A'.repeat(43),
});
expect(parseCapabilityParams('browser.authorization.baseline.candidates', {
tabId: 12,
frameId: 0,
authContextKind: 'attestation',
authContextId: 'attestation-1',
limit: 50,
})).toMatchObject({
authContextKind: 'attestation',
authContextId: 'attestation-1',
limit: 50,
});
expect(parseCapabilityParams('browser.authorization.baseline.resource.get', {
id: 'baseline-1',
selector: { source: 'wire', location: 'query', path: 'query.orderId' },
})).toEqual({
id: 'baseline-1',
selector: { source: 'wire', location: 'query', path: 'query.orderId' },
});
expect(parseCapabilityParams('browser.authorization.baseline.compile', {
id: 'baseline-1',
selector: { source: 'wire', location: 'query', path: 'query.orderId' },
replacement: {
version: 1,
baselineId: 'baseline-2',
source: 'wire',
location: 'query',
path: 'query.orderId',
valueType: 'string',
byteLength: 2,
valueBase64: 'NDI=',
valueFingerprint: `workspace-hmac-sha256:${'a'.repeat(64)}`,
},
comparisonKey: 'A'.repeat(43),
})).toMatchObject({
id: 'baseline-1',
replacement: { baselineId: 'baseline-2', valueBase64: 'NDI=' },
});
expect(parseCapabilityParams('browser.authorization.baseline.compile', {
id: 'baseline-1',
selector: {
source: 'wire',
location: 'body',
path: 'body.variables.orderId',
},
replacement: {
version: 1,
baselineId: 'baseline-2',
source: 'wire',
location: 'body',
path: 'body.variables.orderId',
valueType: 'number',
byteLength: 2,
valueBase64: 'ODQ=',
valueFingerprint: `workspace-hmac-sha256:${'b'.repeat(64)}`,
},
comparisonKey: 'A'.repeat(43),
})).toMatchObject({
replacement: { valueType: 'number', valueBase64: 'ODQ=' },
});
expect(parseCapabilityParams('browser.authorization.baseline.transform.inspect', {
id: 'baseline-1',
profileId: 'profile-left',
})).toEqual({
id: 'baseline-1',
profileId: 'profile-left',
});
expect(parseCapabilityParams('browser.authorization.baseline.packet.compile', {
id: 'baseline-1',
})).toEqual({ id: 'baseline-1' });
expect(parseCapabilityParams('browser.authorization.baseline.transform.compile', {
id: 'baseline-1',
selector: { source: 'wire', location: 'query', path: 'query.orderId' },
replacement: {
version: 1,
baselineId: 'baseline-2',
source: 'wire',
location: 'query',
path: 'query.orderId',
valueType: 'string',
byteLength: 2,
valueBase64: 'NDI=',
valueFingerprint: `workspace-hmac-sha256:${'a'.repeat(64)}`,
},
comparisonKey: 'A'.repeat(43),
profileId: 'profile-left',
bindingFingerprint: `sha256:${'b'.repeat(64)}`,
})).toMatchObject({
id: 'baseline-1',
profileId: 'profile-left',
bindingFingerprint: `sha256:${'b'.repeat(64)}`,
});
expect(() => parseCapabilityParams('browser.authorization.context.capture', {
tabId: 12,
isolationProofId: 'proof-1',
slotId: 'middle',
})).toThrow();
expect(() => parseCapabilityParams('browser.isolation.container.remove', {
cookieStoreId: 'firefox-default',
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.context.get', {
id: '',
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.context.attestation.get', {
id: '',
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.baseline.capture', {
tabId: 12,
authContextKind: 'handle',
authContextId: 'auth-context-1',
networkRequestId: 'network-request-1',
comparisonKey: 'short',
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.baseline.candidates', {
tabId: 12,
authContextKind: 'handle',
authContextId: 'auth-context-1',
limit: 201,
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.baseline.resource.get', {
id: 'baseline-1',
selector: { location: 'body', path: 'body.orderId' },
})).toThrow();
expect(() => parseCapabilityParams('browser.authorization.baseline.compile', {
id: 'baseline-1',
selector: { source: 'wire', location: 'query', path: 'query.orderId' },
replacement: {
version: 1,
baselineId: 'baseline-2',
source: 'wire',
location: 'query',
path: 'query.orderId',
valueType: 'string',
byteLength: 2,
valueBase64: 'not base64',
valueFingerprint: `workspace-hmac-sha256:${'a'.repeat(64)}`,
},
comparisonKey: 'A'.repeat(43),
})).toThrow();
});
it('accepts automatic selected-frame capture and rejects the legacy expression contract', () => {
expect(parseCapabilityParams('browser.callable.create', {
source: 'deep-capture', strategy: 'selected-frame', callFrameId: 'frame-1', name: 'Envelope',
-69
View File
@@ -33,7 +33,6 @@ export interface BridgePairingEnvelope {
}
const id = v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(160));
const comparisonKey = v.pipe(v.string(), v.regex(/^[A-Za-z0-9_-]{43}$/));
const sha256Fingerprint = v.pipe(v.string(), v.regex(/^sha256:[a-f0-9]{64}$/));
const tabId = v.pipe(v.number(), v.safeInteger(), v.minValue(1));
const httpUrl = v.pipe(
@@ -80,23 +79,6 @@ const deepCaptureMatcher = v.variant('kind', [
const captureId = v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(160));
const nodeId = v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(80));
const valuePath = v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(512));
const authorizationSelector = v.strictObject({
source: v.picklist(['wire', 'logical']),
location: v.picklist(['header', 'path', 'query', 'body']),
path: valuePath,
});
const authorizationResourceValue = v.strictObject({
version: v.literal(1),
baselineId: id,
source: v.picklist(['wire', 'logical']),
location: v.picklist(['header', 'path', 'query', 'body']),
path: valuePath,
valueType: v.picklist(['string', 'number', 'boolean']),
byteLength: v.pipe(v.number(), v.safeInteger(), v.minValue(0), v.maxValue(8 * 1_024)),
valueBase64: v.pipe(v.string(), v.maxLength(11_000), v.regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)),
valueFingerprint: v.pipe(v.string(), v.regex(/^workspace-hmac-sha256:[a-f0-9]{64}$/)),
logicalBindingFingerprint: v.optional(sha256Fingerprint),
});
export const capabilityParams = {
'system.ping': v.optional(v.strictObject({})),
'browser.tabs': v.optional(v.strictObject({})),
@@ -119,57 +101,6 @@ export const capabilityParams = {
'browser.isolation.container.remove': v.strictObject({
cookieStoreId: v.pipe(v.string(), v.regex(/^firefox-container-[0-9]+$/)),
}),
'browser.authorization.context.capture': v.strictObject({
...targetFields,
isolationProofId: id,
slotId: v.picklist(['left', 'right']),
accountLabel: v.optional(v.pipe(v.string(), v.trim(), v.maxLength(80))),
}),
'browser.authorization.context.get': v.strictObject({ id }),
'browser.authorization.context.attest': v.strictObject(targetFields),
'browser.authorization.context.attestation.get': v.strictObject({ id }),
'browser.authorization.baseline.capture': v.strictObject({
...targetFields,
authContextKind: v.picklist(['handle', 'attestation']),
authContextId: id,
networkRequestId: id,
comparisonKey,
}),
'browser.authorization.baseline.candidates': v.strictObject({
...targetFields,
authContextKind: v.picklist(['handle', 'attestation']),
authContextId: id,
limit: v.optional(v.pipe(v.number(), v.safeInteger(), v.minValue(1), v.maxValue(200))),
}),
'browser.authorization.baseline.get': v.strictObject({ id }),
'browser.authorization.baseline.logical.bind': v.strictObject({
id,
profileId: id,
comparisonKey,
}),
'browser.authorization.baseline.resource.get': v.strictObject({
id,
selector: authorizationSelector,
}),
'browser.authorization.baseline.compile': v.strictObject({
id,
selector: authorizationSelector,
replacement: authorizationResourceValue,
comparisonKey,
}),
'browser.authorization.baseline.packet.compile': v.strictObject({ id }),
'browser.authorization.baseline.transform.inspect': v.strictObject({
id,
profileId: id,
}),
'browser.authorization.baseline.transform.compile': v.strictObject({
id,
selector: authorizationSelector,
replacement: authorizationResourceValue,
comparisonKey,
profileId: id,
bindingFingerprint: sha256Fingerprint,
}),
'browser.context': v.optional(v.strictObject({
...targetFields,
includeDom: v.optional(v.boolean()),
-81
View File
@@ -69,87 +69,6 @@ describe('versioned Bridge capability catalog', () => {
access: 'dangerous',
scopes: ['browser.isolation.manage'],
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.capture')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.attest')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.attestation.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.capture')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.logical.bind')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: expect.arrayContaining(['browser.network.sensitive.read', 'browser.transform.execute']),
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.candidates')).toMatchObject({
domain: 'authorization',
access: 'read',
scopes: ['browser.network.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.resource.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.packet.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.transform.inspect')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: expect.arrayContaining(['browser.transform.read']),
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.transform.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: expect.arrayContaining(['browser.network.replay', 'browser.transform.execute']),
targetMode: 'none',
});
expect(capabilityBaseScope('browser.authorization.baseline.compile')).toBe('browser.network.replay');
expect(capabilityBaseScope('browser.authorization.baseline.packet.compile')).toBe('browser.network.replay');
expect(capabilityBaseScope('browser.authorization.baseline.transform.compile')).toBe('browser.network.replay');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.isolation.container.open')).toBeUndefined();
expect(capabilityBaseScope('missing.capability')).toBeUndefined();
});
-65
View File
@@ -70,71 +70,6 @@ const CAPABILITY_METADATA = {
domain: 'isolation', access: 'dangerous', summary: '关闭并删除由 Yakit 创建的临时 Firefox Container',
scopes: ['browser.isolation.manage'], targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.context.capture': {
domain: 'authorization', access: 'sensitive-read', summary: '为隔离身份生成不含原始凭据的短时认证上下文句柄',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.context.get': {
domain: 'authorization', access: 'sensitive-read', summary: '实时复核并读取当前浏览器实例中的短时认证上下文句柄',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.context.attest': {
domain: 'authorization', access: 'sensitive-read', summary: '为单个隔离页面生成不含原始凭据的跨设备认证证明',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.context.attestation.get': {
domain: 'authorization', access: 'sensitive-read', summary: '实时复核跨设备认证证明及其目标文档',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.capture': {
domain: 'authorization', access: 'sensitive-read', summary: '将已捕获请求封存为不暴露凭据值的短时授权基线',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.candidates': {
domain: 'authorization', access: 'read', summary: '列出不包含 Header 或 Body 值的授权基线请求候选',
scopes: ['browser.network.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.get': {
domain: 'authorization', access: 'sensitive-read', summary: '实时复核授权基线及其认证上下文',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.logical.bind': {
domain: 'authorization', access: 'dangerous', summary: '在本机验证明文网关生成结构,并将短时逻辑字段 HMAC 绑定到授权基线',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read', 'browser.transform.execute'],
targetMode: 'none', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.authorization.baseline.resource.get': {
domain: 'authorization', access: 'sensitive-read', summary: '读取已确认资源选择器的单个短时值,用于跨身份矩阵',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.compile': {
domain: 'authorization', access: 'dangerous', summary: '在实时复核身份后编译一次供 Yak 受限执行器使用的短时请求',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.authorization.baseline.packet.compile': {
domain: 'authorization', access: 'dangerous', summary: '在实时复核身份后编译不可变的完整操作模板或认证骨架',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.authorization.baseline.transform.inspect': {
domain: 'authorization', access: 'sensitive-read', summary: '验证身份页面的明文网关是否完整覆盖授权请求动态字段',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read', 'browser.transform.read'],
targetMode: 'none', defaultTimeoutMs: READ_TIMEOUT_MS,
},
'browser.authorization.baseline.transform.compile': {
domain: 'authorization', access: 'dangerous', summary: '在发起身份自己的页面环境重算签名、Nonce 与时间字段后编译请求',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read', 'browser.transform.execute'],
targetMode: 'none', defaultTimeoutMs: REPLAY_TIMEOUT_MS,
},
'browser.frames': {
domain: 'page', access: 'read', summary: '列出浏览器实例指定标签页中的 Frame',
scopes: ['browser.tabs.read'], targetMode: 'tab', defaultTimeoutMs: READ_TIMEOUT_MS,
+4 -4
View File
@@ -82,11 +82,11 @@ describe('extension request schemas', () => {
expect(parseExtensionRequest({
action: 'authorization.engine.task',
payload: {
schema: 'authorization.workspace.create',
schema: 'authorization.capture.start',
payload: {
mode: 'horizontal',
left: { tabId: 12, frameId: 0, accountLabel: 'A' },
right: { tabId: 13, frameId: 0, accountLabel: 'B' },
left: { deviceId: 'browser-a', tabId: 12 },
right: { deviceId: 'browser-b', tabId: 13 },
side: 'left',
},
timeoutMs: 60_000,
},
+3 -11
View File
@@ -287,20 +287,12 @@ const payloadSchemas = {
}),
'authorization.engine.task': v.strictObject({
schema: v.picklist([
'authorization.workspace.create',
'authorization.workspace.inspect',
'authorization.capture.start',
'authorization.capture.status',
'authorization.capture.stop',
'authorization.baseline.candidates',
'authorization.baseline.bind',
'authorization.logical.bind',
'authorization.plan.create',
'authorization.plan.execute',
'authorization.evidence.inspect',
'authorization.evidence.packet',
'authorization.evidence.diff',
'authorization.evidence.validate',
'authorization.requests',
'authorization.pair.inspect',
'authorization.execute',
]),
payload: v.record(v.string(), v.unknown()),
timeoutMs: v.optional(v.pipe(v.number(), v.safeInteger(), v.minValue(5_000), v.maxValue(120_000))),